모든 개발자가 알아야 할 Linux 명령어

1. 개요

모든 개발자가 한 번쯤 SSH로 서버에 접속했다가 터미널에서 디버깅하거나 앱을 배포하면서 제대로 된 명령어를 찾느라 고민했을 겁니다. 이 글에서는 일상 업무에서 가장 자주 쓰이는 Linux 명령어를 모았습니다: 파일 관리, 검색, 텍스트 처리, 프로세스 모니터링, 네트워크 확인, 로그 보기 등입니다. 이 글을 읽고 나면 매번 Google에 검색할 필요 없이 빠르게 디버깅할 수 있는 필수 명령어들을 갖게 될 거예요.

주로 Ubuntu/Debian의 bash를 예시로 사용했으며, 대부분의 배포판에서 이 명령어들은 거의 같습니다.

2. 파일과 디렉토리 기본 조작

mkdir -p ~/projects/order-service/src/main/java
cd ~/projects/order-service
touch application.properties
ls -la
  • mkdir -p는 중첩된 디렉토리 트리 전체를 생성하며, 디렉토리가 이미 존재해도 에러를 표시하지 않습니다.
  • touch는 빈 파일을 생성하거나 기존 파일이 있으면 타임스탬프를 갱신합니다.
  • ls -la는 모든 파일(.env, .git 같은 숨김 파일 포함)을 권한, 소유자, 크기와 함께 나열합니다.
drwxr-xr-x  3 nonla nonla 4096 Oct  5 10:00 .
-rw-r--r--  1 nonla nonla    0 Oct  5 10:00 application.properties

파일을 옮기거나 삭제하는 명령어는 주의가 필요합니다:

cp -r src/ src-backup/     # 디렉토리를 재귀적으로 복사
mv OrderService.java OrderServiceImpl.java   # 이름 변경 또는 이동
rm -rf target/            # 재귀적으로 삭제, 확인 메시지 없음

주의: rm -rf는 휴지통을 거치지 않고 바로 삭제됩니다. 항상 명령어를 실행하기 전에 pwd로 현재 디렉토리를 확인하세요. 특히 다른 곳에서 복사한 명령어를 붙여넣을 때는 더욱 조심해야 합니다.

3. find와 grep으로 검색하기

3.1. find — 조건에 따라 파일 찾기

find . -name "*.java" -newer pom.xml

이 명령어는 pom.xml보다 최근에 수정된 모든 .java 파일을 찾습니다 — commit 전에 어떤 파일이 변경되었는지 확인할 때 유용합니다.

find . -type f -size +10M

10MB보다 큰 파일을 찾습니다. 로그나 빌드 산출물로 인한 디스크 공간 정리에 자주 사용됩니다.

3.2. grep — 파일 내용 검색

grep -rn "PaymentService" --include="*.java" src/
  • -r은 하위 디렉토리를 재귀적으로 검색합니다.
  • -n은 줄 번호와 함께 출력합니다.
  • --include는 .java 파일만 검색하도록 제한하여 target/ 같은 디렉토리를 피합니다.
src/main/java/com/nonla/OrderController.java:15:    private final PaymentService paymentService;

더 복잡한 필터링이 필요할 때는 pipe로 grep과 find를 연결합니다:

find . -name "*.log" | xargs grep -l "OutOfMemoryError"

xargs는 find의 결과 파일 목록을 grep -l로 전달합니다 (pattern을 포함한 파일명만 출력, 내용은 출력하지 않음).

4. awk, sed, cut으로 텍스트 처리하기

이 명령어들은 “로그를 씹어먹는” 도구들입니다 — 긴 output에서 데이터를 추출할 때 정말 강력합니다.

cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -5
  • awk '{print $1}'은 매 줄마다 첫 번째 필드(보통 IP)를 추출합니다.
  • sort | uniq -c는 그룹화하고 출현 횟수를 세어줍니다.
  • sort -rn은 내림차순으로 정렬하고, head -5는 상위 5개를 출력합니다.
    152 192.168.1.10
     98 10.0.0.5
     47 172.16.0.3

이것이 script를 작성하지 않고 가장 많은 request를 호출한 IP를 찾는 가장 빠른 방법입니다.

sed는 파일의 텍스트를 직접 교체합니다:

sed -i 's/spring.profiles.active=dev/spring.profiles.active=prod/' application.properties

-i는 파일 자리에서 수정합니다 (in-place). 더 안전하게 하려면 sed -i.bak '...'로 자동으로 .bak 백업 파일을 생성할 수 있습니다.

cut은 구분자에 따라 열을 분리하며, 단순히 하나의 열만 필요할 때는 awk보다 가볍습니다:

cat /etc/passwd | cut -d: -f1 | head -3

5. 프로세스와 시스템 리소스 모니터링

ps aux | grep java

모든 프로세스를 출력한 후 java를 포함한 줄을 필터링합니다 — Spring Boot 앱이 실행 중인지 그리고 PID가 무엇인지 빠르게 확인할 때 쓰입니다.

nonla    12345  2.1  5.3 2345678 432100 ?      Sl   10:00   0:15 java -jar order-service.jar

두 번째 열이 PID입니다. 이 PID를 사용해서 프로세스를 종료하거나 자세한 정보를 볼 수 있습니다:

kill -15 12345     # SIGTERM을 보내 앱을 정상 종료
kill -9 12345       # force kill, -15가 작동하지 않을 때만 사용

주의: kill -9는 앱에 shutdown hook을 실행할 기회를 주지 않습니다 (예: connection pool 종료, 로그 flush). 항상 먼저 -15를 시도하세요.

실시간으로 리소스 사용량을 봅니다:

top -o %MEM

-o %MEM은 가장 많은 RAM을 사용하는 프로세스 순으로 정렬합니다 — memory leak을 의심할 때 유용합니다. 더 좋은 인터페이스를 원하면 htop을 설치하세요.

디스크 확인:

df -h
du -sh /var/log/*

df -h는 각 파티션의 용량을 human-readable 형식으로 봅니다. du -sh는 /var/log 내 각 하위 디렉토리가 얼마나 차지하는지 봅니다 — 서버에서 “no space left on device” 에러가 뜰 때 매우 필요합니다.

6. 네트워크와 포트 확인

ss -tulnp | grep 8080
  • ss는 오래된 netstat를 대체하며 더 빠릅니다.
  • -t는 TCP, -u는 UDP, -l은 listening 상태, -n은 숫자 형식 (DNS resolution 안 함), -p는 프로세스 정보를 포함합니다.
tcp   LISTEN  0  128  0.0.0.0:8080  0.0.0.0:*  users:(("java",pid=12345,fd=45))

port 8080이 PID 12345인 Java 프로세스에 의해 점유 중임을 즉시 알 수 있습니다 — Address already in use 에러가 날 때 매우 유용합니다.

curl로 API를 빠르게 테스트합니다:

curl -s -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"customerId":1,"amount":99.5}'

-s는 silent 모드 (진행 표시줄 숨김), -X POST는 메서드 선택, -d는 JSON body를 지정합니다. response headers도 보려면 -i를 추가하세요.

lsof 명령어는 프로세스가 열고 있는 파일과 socket을 정확히 확인할 때 쓰입니다:

lsof -i :8080

7. 파일 접근 권한

chmod +x deploy.sh
chown nonla:nonla application.properties
chmod 640 .env
  • chmod +x는 스크립트 실행 권한을 줍니다.
  • chown user:group은 소유자를 변경합니다 — 다른 사용자의 파일일 때는 sudo가 필요할 수 있습니다.
  • chmod 640은 소유자가 읽기/쓰기 (6), 그룹은 읽기만 (4), 다른 사용자는 권한 없음 (0) — .env나 SSH key 같은 민감한 파일의 표준입니다.
-rw-r----- 1 nonla nonla 128 Oct  5 10:00 .env

주의: 프로덕션에서는 절대 chmod 777을 사용하지 마세요. 이는 시스템 모든 사용자가 민감한 파일을 읽고 수정할 수 있게 하는 가장 빠른 방법이며, 특히 .env나 SSH key 파일에서는 심각한 보안 구멍입니다.

8. 압축과 압축 해제

tar -czvf order-service.tar.gz order-service/
tar -xzvf order-service.tar.gz -C /opt/apps/
  • -c는 archive 생성, -x는 압축 해제, -z는 gzip 사용, -v는 verbose 모드, -f는 파일명 지정합니다.
  • -C는 압축 해제 시 대상 디렉토리를 지정합니다 (현재 디렉토리 대신).

배포할 때 scp나 rsync와 함께 사용해 파일을 서버로 전송합니다:

scp order-service.tar.gz user@10.0.0.5:/opt/apps/
rsync -avz --progress target/*.jar user@10.0.0.5:/opt/apps/

rsync는 변경된 부분만 전송하므로 (delta transfer), 큰 파일을 여러 번 업데이트할 때 scp보다 훨씬 빠릅니다.

9. 실시간 로그 보기

tail -f /var/log/order-service/app.log

-f (follow) 옵션은 터미널을 열린 상태로 유지하고 파일에 새 줄이 추가되면 바로 출력합니다 — 프로덕션에서 디버깅할 때 반드시 알아야 할 명령어입니다.

pattern으로 로그를 필터링하면서 tail합니다:

tail -f app.log | grep --line-buffered "ERROR"

--line-buffered는 필수입니다. grep은 기본적으로 블록 단위로 buffer하기 때문에, 이 옵션 없으면 tail -f와 pipe 사용 시 output이 지연될 수 있습니다.

앱이 systemd 아래에서 실행 중이면, 로그 파일을 직접 찾는 대신 journalctl을 사용하세요:

journalctl -u order-service -f --since "10 minutes ago"

-u는 service unit 지정, -f는 follow 모드, --since는 시간 범위를 제한합니다 — 예전 로그 수백만 줄을 읽지 않아도 됩니다.

10. Java에서 Linux 명령어 실행하기

종종 Java 코드에서 직접 shell 명령어를 호출해야 할 때가 있습니다. 예를 들어 큰 파일을 쓰기 전에 디스크 용량을 확인하는 경우입니다:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;

public class ShellRunner {

    public static String run(List<String> command) throws Exception {
        ProcessBuilder builder = new ProcessBuilder(command);
        builder.redirectErrorStream(true);
        Process process = builder.start();

        try (BufferedReader reader =
                new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String output = reader.lines().reduce("", (a, b) -> a + b + "\n");
            process.waitFor();
            return output;
        }
    }
}
  • ProcessBuilder는 argument 리스트를 받습니다 (명령어를 손으로 만든 문자열이 아니므로 shell injection을 피함).
  • redirectErrorStream(true)는 stderr를 stdout로 리다이렉트하여 하나의 스트림만 읽습니다.
  • process.waitFor()는 현재 thread를 차단하고 자식 프로세스가 끝날 때까지 기다립니다.

JUnit 5 + AssertJ로 테스트합니다:

import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

class ShellRunnerTest {

    @Test
    void shouldReturnCurrentDirectoryListing() throws Exception {
        String output = ShellRunner.run(List.of("ls", "-la"));

        assertThat(output).contains("total");
    }
}

이 테스트는 시스템에서 실제로 ls -la를 실행하고 output에 total 문자열이 있는지 확인합니다 (ls -la의 첫 줄은 항상 블록 총합을 출력함) — ProcessBuilder가 실제로 shell 명령어를 호출하는지 확인할 수 있습니다.

주의: Java 프로덕션 코드에서 shell 명령어를 호출하는 것은 양날의 칼입니다 — OS에 의존하기 쉽고, 테스트하기 어렵고, 사용자 입력에서 가져온 argument면 injection 위험이 있습니다. 내부 도구나 운영 스크립트에만 사용하고, 주요 비즈니스 로직에는 피하세요.

11. 흔한 실수들

  • rm -rf를 실행할 때 환경변수가 비어있으면 실수로 예상 외 디렉토리를 삭제할 수 있습니다. 예: rm -rf $DIR/*에서 $DIR이 설정되지 않았을 때. 스크립트에서 set -u를 사용하거나 항상 변수를 확인하세요.
  • sudo를 빠뜨려서 Permission denied 에러가 발생하면 app 때문이라고 착각하기 쉽습니다 — 명령어 실행 전에 ls -la로 확인하세요.
  • kill -9를 습관처럼 사용하면 Spring Boot 앱이 connection pool을 종료할 기회가 없어 database에서 누수가 발생합니다.
  • tail -f 후 grep을 pipe할 때 --line-buffered를 빼먹으면 로그가 없다고 생각하지만 실제로는 buffer되어 있습니다.
  • “권한 문제를 빨리 넘기기 위해” chmod 777을 사용하면 — 특히 .env나 SSH key 파일의 경우 보안 구멍이 됩니다.

12. 마치며

이 글의 명령어들 — find, grep, awk/sed, ps/kill, ss/curl, tar, tail -f, journalctl — 은 DevOps 팀의 도움을 받지 않고 직접 디버깅하기 위한 최소한의 도구입니다. 이들을 능숙하게 다룬다는 것이 모든 flag를 외워야 한다는 뜻은 아니며, 각 명령어가 어떤 문제를 해결하는지 알고 필요할 때 man으로 세부사항을 찾으면 됩니다. 주변에 Grafana, Kibana, APM tool이 아무리 많아도, 터미널은 여전히 가장 빠른 디버깅 도구입니다.

linuxdevopsbashproductivityjava