개발자를 위한 리눅스·맥 명령어 실전 가이드 | 네트워크·파일·프로세스·디버깅

이 글의 핵심

같은 명령이라도 리눅스(GNU)와 macOS(BSD)에서 옵션이 조금씩 달라 복사한 명령이 그대로 안 도는 경우가 많습니다. 디렉터리 탐색·파일 내용 검색·네트워크 디버깅·프로세스 관리·디스크 점검을 상황별로 묶어 두었으니, 문제가 생긴 순간 필요한 명령 조합을 찾아 쓰는 참고용으로 활용할 수 있습니다.

들어가며

“서버가 응답이 없어요”, “로그 어디서 보죠?”, “포트가 이미 사용 중이래요” — 개발하다 보면 터미널 명령어로 문제를 빠르게 해결해야 할 때가 많습니다. 이 글은 개발 현장에서 실제로 자주 쓰는 리눅스·맥 명령어를 실전 예제로 정리한 치트시트입니다. 복사해서 바로 쓸 수 있도록 구성했습니다.

대상 독자:

  • 백엔드·DevOps 개발자
  • 서버 배포·디버깅하는 프론트엔드 개발자
  • 리눅스 터미널 입문자

먼저 알아 둘 것: GNU와 BSD의 차이

리눅스 배포판의 기본 명령어는 대부분 GNU coreutils 계열이고, macOS의 기본 명령어는 BSD 계열입니다. 이름은 같아도 옵션과 동작이 조금씩 달라서, 서버에서 쓰던 명령을 맥에서 그대로 실행하면 에러가 나거나 더 나쁘게는 다른 동작을 조용히 하기도 합니다. 자주 부딪히는 차이는 다음과 같습니다.

  • sed -i: GNU는 sed -i 's/a/b/' f, BSD는 백업 확장자 인자가 필수라 sed -i '' 's/a/b/' f입니다. 맥에서 GNU 문법을 쓰면 sed: 1: "f": invalid command code f 같은 알쏭달쏭한 에러가 나고, 리눅스에서 BSD 문법을 쓰면 ''를 스크립트로, 스크립트를 파일명으로 해석해 can't read s/a/b/: No such file or directory가 납니다. 두 환경에서 모두 도는 스크립트가 필요하면 sed -i.bak 's/a/b/' f && rm f.bak처럼 확장자를 붙여 쓰면 됩니다.
  • find: BSD find는 시작 경로를 생략할 수 없어 find -name '*.log'가 illegal option -- n 에러를 냅니다. 항상 find . -name ...처럼 경로를 적는 습관이 안전합니다.
  • date: 상대 날짜 계산이 GNU는 date -d '1 day ago', BSD는 date -v-1d로 전혀 다릅니다.
  • grep -P(Perl 정규식)와 readlink -f, stat -c는 GNU 전용이거나 BSD에서 문법이 다릅니다(stat -f).
  • xargs: 입력이 비어 있을 때 GNU xargs는 명령을 한 번 실행하고, BSD는 실행하지 않습니다. lsof -ti:8080 | xargs kill이 리눅스에서 포트가 비어 있을 때 kill: usage 에러를 내는 이유이며, GNU에서는 xargs -r로 막을 수 있습니다.

맥에서 리눅스와 똑같이 쓰고 싶다면 brew install coreutils findutils gnu-sed grep으로 GNU 버전을 설치할 수 있습니다. 기본으로는 gsed, gfind, gdate처럼 g 접두사가 붙은 이름으로 설치되므로, 스크립트에서 어느 쪽을 부르는지 분명히 해야 합니다. 아래 예제는 공통으로 동작하는 형태를 우선하고, 다른 경우에는 주석으로 표시했습니다.


디렉토리 탐색과 파일 관리

기본 탐색

# 현재 디렉토리 확인
pwd
# 디렉토리 이동
cd /var/log
cd ~              # 홈 디렉토리
cd -              # 이전 디렉토리
cd ..             # 상위 디렉토리
# 디렉토리 내용 보기
ls                # 기본
ls -la            # 숨김 파일 포함, 상세 정보
ls -lh            # 사람이 읽기 쉬운 크기 (KB, MB)
ls -lt            # 최신 파일 먼저
ls -lS            # 큰 파일 먼저
ls -R             # 하위 디렉토리까지 재귀
# 트리 구조로 보기
tree              # 전체 트리
tree -L 2         # 2단계까지만
tree -d           # 디렉토리만
tree -I 'node_modules|.git'  # 제외

디렉토리 생성 및 삭제

# 디렉토리 생성
mkdir mydir
mkdir -p parent/child/grandchild  # 중간 디렉토리 자동 생성
# 디렉토리 삭제
rmdir mydir       # 빈 디렉토리만
rm -r mydir       # 내용 포함 삭제
rm -rf mydir      # 강제 삭제 (주의!)
# 안전한 삭제 (확인 후 삭제)
rm -ri mydir      # 각 파일마다 확인

파일 복사 및 이동

# 파일 복사
cp file.txt backup.txt
cp -r src/ dest/              # 디렉토리 복사
cp -p file.txt backup.txt     # 권한·타임스탬프 유지
cp -u src/* dest/             # 최신 파일만 복사
# 파일 이동 (이름 변경)
mv old.txt new.txt
mv file.txt /path/to/dest/
mv *.log logs/                # 여러 파일 이동
# 심볼릭 링크
ln -s /path/to/original link_name
ls -l link_name               # 링크 확인

파일 찾기 - find의 강력한 기능

find 명령어 내부 동작:

find의 파일 시스템 탐색 메커니즘:

1. 기본 동작:
   find /var/log -name "*.log"

   내부 처리:
   ↓
   opendir("/var/log") → 디렉토리 핸들 얻기
   ↓
   readdir() 반복:
   - d_name (파일명)
   - d_type (파일 타입: DT_REG, DT_DIR, DT_LNK)
   ↓
   각 파일마다:
   - 이름 패턴 매칭 ("*.log"와 비교)
   - 조건 평가 (-type, -size, -mtime 등)
   - 매칭 시 출력 또는 -exec 실행
   ↓
   디렉토리면 재귀:
   - opendir("/var/log/subdir")
   - 같은 과정 반복
   ↓
   closedir()

2. 메타데이터 접근:
   stat() 시스템 콜로 파일 정보 조회:

   find . -size +100M
   ↓
   각 파일마다 stat(filename, &statbuf)
   ↓
   statbuf.st_size 확인 (바이트 단위)
   ↓
   100MB = 100 * 1024 * 1024 = 104857600 bytes
   if (st_size > 104857600) → 매칭

   find . -mtime -7
   ↓
   stat()로 st_mtime (수정 시간) 조회
   ↓
   현재 시간 - st_mtime < 7일?

   find . -perm 777
   ↓
   st_mode & 0777 == 0777?

3. -exec 내부 동작:
   find . -name "*.log" -exec rm {} \;

   각 매칭 파일마다:
   ↓
   fork() → 자식 프로세스 생성
   ↓
   execve("/bin/rm", ["rm", "file.log"], environ)
   ↓
   wait() → 자식 종료 대기
   ↓
   다음 파일 처리

   성능 차이:
   -exec ... \;  : 파일마다 fork/exec (느림)
   -exec ... +   : 한 번에 여러 파일 (빠름)

   예:
   -exec rm {} \;  : rm file1; rm file2; rm file3;
   -exec rm {} +   : rm file1 file2 file3

4. 최적화:
   find는 파일 시스템 순회가 비용 높음

   대안:
   - locate: DB 기반 (빠르지만 최신 아님)
     updatedb로 DB 갱신 필요

   - fd (modern find):
     더 빠른 알고리즘, 병렬 처리
     .gitignore 자동 인식

   - 파일 개수 많을 때:
     find + xargs 조합 (인자를 묶어서 실행)
     find . -name "*.log" -print0 | xargs -0 rm
     (-print0/-0 없이 쓰면 공백이 든 파일명이 쪼개짐)

파일 시스템 API 흐름:
User: find . -name "*.log"
  ↓
Kernel: sys_openat("/path") → inode 읽기
  ↓
Kernel: sys_getdents64() → directory entries
  ↓
find: 각 entry 필터링 (이름, 타입, 크기)
  ↓
find: 재귀 시 sys_openat(subdir)
  ↓
Output: 매칭된 경로 출력
# 이름으로 찾기
find . -name "*.log"          # 현재 디렉토리부터 재귀 검색
find /var -name "error.log"   # 특정 경로에서 검색
find . -iname "*.TXT"         # 대소문자 무시 (-iname)

# 타입으로 찾기
find . -type f                # 파일만 (regular file)
find . -type d                # 디렉토리만 (directory)
find . -type l                # 심볼릭 링크만 (link)
find . -type f -empty         # 빈 파일만

# 크기로 찾기
find . -size +100M            # 100MB 이상
find . -size -1k              # 주의: 사실상 빈 파일만 (크기를 1k 단위로 올림)
find /tmp -size +1G -delete   # 1GB 이상 파일 삭제
# 크기 단위: c(byte), k(KB), M(MB), G(GB)

# 시간으로 찾기 (디버깅에 유용)
find . -mtime -7              # 최근 7일 이내 수정 (Modified)
find . -mtime +30             # 30일 이상 수정 안 됨
find . -atime -1              # 최근 1일 이내 접근 (Accessed)
find . -ctime -1              # 최근 1일 이내 메타데이터 변경 (Changed)
find . -mmin -60              # 최근 60분 이내 (분 단위)

# 권한으로 찾기 (보안)
find . -perm 777              # 정확히 777 권한
find . -perm -644             # 최소 644 권한 이상
find / -perm /u+s,g+s         # SUID/SGID 비트가 설정된 파일 (보안 위험)

# 찾아서 실행 (자동화)
find . -name "*.tmp" -delete
# -exec: 각 파일마다 명령어 실행
find . -name "*.js" -exec grep "TODO" {} \;
# {}: 찾은 파일명으로 대체
# \;: -exec의 끝 (세미콜론 이스케이프)

find . -type f -exec chmod 644 {} \;
# 모든 파일을 644로 변경

# -exec vs -exec ... +
find . -name "*.log" -exec rm {} \;       # 파일마다 rm 실행 (느림)
find . -name "*.log" -exec rm {} +        # 여러 파일을 한 번에 (빠름)
# rm file1 file2 file3 ... (한 번의 rm 호출)

# 복잡한 조건 (AND, OR, NOT)
find . \( -name "*.log" -or -name "*.txt" \) -size +1M
# .log 또는 .txt이면서 1MB 이상

find . -name "*.js" -not -path "*/node_modules/*"
# .js 파일 중 node_modules 제외

# 실전 예제: 오래된 로그 파일 압축 및 삭제
find /var/log -name "*.log" -mtime +30 -exec gzip {} \;
find /var/log -name "*.gz" -mtime +90 -delete

find에서 가장 자주 헷갈리는 부분은 단위 올림입니다. -size -1k는 “1KB 미만”처럼 보이지만, find는 파일 크기를 지정 단위로 올림한 뒤 비교하기 때문에 1바이트짜리 파일도 1k로 계산되어 매칭되지 않고 사실상 0바이트 파일만 걸립니다. 정확한 기준이 필요하면 -size -1024c처럼 바이트(c) 단위를 쓰세요. -mtime도 같은 방식으로 24시간 단위로 버림한 값을 비교하므로 -mtime +1은 “1일 초과”가 아니라 “만 2일 이상 지난” 파일입니다. 또 -delete는 조건 뒤에 와야 합니다. find . -delete -name "*.tmp"처럼 순서를 바꾸면 이름 조건을 보기 전에 모든 파일을 지웁니다. 삭제 명령을 쓰기 전에는 -delete 없이 한 번 실행해 목록을 확인하는 것이 안전합니다.


파일 내용 보기와 grep·awk·sed

파일 내용 출력

# 전체 내용
cat file.txt
cat file1.txt file2.txt       # 여러 파일 연결
# 줄 번호 포함
cat -n file.txt
nl file.txt
# 처음 N줄
head file.txt                 # 기본 10줄
head -n 20 file.txt           # 20줄
head -c 100 file.txt          # 처음 100바이트
# 마지막 N줄
tail file.txt                 # 기본 10줄
tail -n 50 file.txt           # 50줄
tail -f /var/log/app.log      # 실시간 모니터링 (로그)
tail -f -n 100 app.log        # 최근 100줄부터 실시간
# 페이지 단위로 보기
less file.txt                 # 스크롤 가능 (q로 종료)
more file.txt                 # 한 페이지씩

grep: 텍스트 검색

grep 내부 패턴 매칭 메커니즘:

grep의 문자열 검색 알고리즘:

1. 기본 동작:
   grep "error" app.log

   처리 흐름:
   ↓
   open("app.log", O_RDONLY)
   ↓
   read() 반복 (버퍼 단위, 보통 4KB~8KB):
   - 버퍼에 데이터 읽기
   - 줄 단위로 분리 (\n 기준)
   ↓
   각 줄마다:
   - 패턴 매칭 (Boyer-Moore 또는 KMP 알고리즘)
   - 매칭 시: write(STDOUT, line, len)
   ↓
   close()

2. 패턴 매칭 알고리즘:

   Boyer-Moore (대부분의 grep 구현):

   패턴: "error"
   텍스트: "This is an error message"

   단계:
   1. 패턴 끝부터 비교
      "error" vs "This "
      's' != 'r' → 패턴에 's' 없음
      → 5칸 건너뛰기 (패턴 길이)

   2. 다음 위치에서 비교
      "error" vs "s an "
      ' ' != 'r' → 계속

   3. 매칭 발견
      "error" vs "error"
      → 전체 매칭!

   시간 복잡도:
   - 최선: O(n/m) (n: 텍스트 길이, m: 패턴 길이)
   - 평균: O(n)
   - 최악: O(nm) (드물음)

   vs 단순 검색: O(nm) 항상

3. 정규표현식 (regex):
   grep -E "error|warning" app.log

   NFA (Non-deterministic Finite Automaton):

   패턴: error|warning
   ↓
   정규식 파싱 → NFA 생성
   ↓
   상태 머신:

   State 0 (시작)
     ↓
     e → State 1 (error 경로)
     w → State 6 (warning 경로)

   State 1
     ↓ r
   State 2
     ↓ r
   State 3
     ↓ o
   State 4
     ↓ r
   State 5 (accept)

   State 6
     ↓ a
   State 7
     ↓ r
   State 8
     ↓ n
   ...

   매칭 과정:
   - GNU grep은 가능한 경우 NFA를 DFA로 바꿔
     각 문자를 한 번씩만 보고 상태를 전이 (백트래킹 없음)
   - 역참조(\1) 같은 기능이 필요할 때만 느린 엔진으로 전환

   성능:
   - 일반 패턴: 입력 길이에 선형
   - (a+)+b 같은 패턴의 지수 시간 폭발(Catastrophic Backtracking)은
     백트래킹 엔진(grep -P의 PCRE, JS·Python·Java 정규식)의 문제

   대안:
   - ripgrep (rg): 더 빠른 구현
   - egrep: grep -E와 동일

4. 최적화 기법:

   grep -F "a.b[1]" app.log
   (-F: Fixed string, 정규식 아님)
   ↓
   . [ ] 같은 메타문자를 글자 그대로 검색
   (정규식 특수문자가 없는 패턴은 GNU grep이 이미
    자동으로 고정 문자열 검색을 쓰므로 속도 차이는 작음)

   grep -r "pattern" /
   (재귀 검색)
   ↓
   opendir() + readdir() 반복
   각 파일마다 grep
   병렬화 없음 (순차)

   대안: ripgrep
   - 병렬 파일 처리
   - .gitignore 인식
   - UTF-8 최적화

5. 메모리 효율:

   대용량 파일 (10GB):
   ↓
   mmap() 또는 버퍼 읽기
   - 전체 파일 로드 안 함
   - 4KB~8KB 버퍼 재사용
   - 줄 단위 처리

   grep -c "error" huge.log
   ↓
   매칭 카운트만 유지
   매칭 줄 출력 안 함
   → 메모리 절약

내부 시스템 콜 흐름:
User: grep "error" app.log
  ↓
Kernel: sys_open("app.log") → fd
  ↓
Loop:
  Kernel: sys_read(fd, buffer, 4096)
  grep: Boyer-Moore 검색
  grep: 매칭 시 write(1, line, len)
  ↓
Kernel: sys_close(fd)
# 기본 검색
grep "error" app.log
grep -i "error" app.log       # 대소문자 무시
grep -v "debug" app.log       # 제외 (debug 없는 줄)
grep -n "error" app.log       # 줄 번호 포함
grep -c "error" app.log       # 매칭 줄 개수
# 여러 파일 검색
grep "TODO" *.js
grep -r "import" src/         # 재귀 검색
grep -l "error" *.log         # 파일명만 출력
# 정규표현식
grep "^Error" app.log         # Error로 시작
grep "failed$" app.log        # failed로 끝남
grep "error\|warning" app.log # OR 검색
grep -E "error|warning" app.log  # 확장 정규식
# 컨텍스트 포함
grep -A 5 "error" app.log     # 매칭 후 5줄
grep -B 3 "error" app.log     # 매칭 전 3줄
grep -C 2 "error" app.log     # 전후 2줄
# 실전 예제
grep -i "exception" *.log | grep -v "test"  # 파이프 조합

grep "error\|warning"처럼 기본 정규식(BRE)에서 \|로 OR를 쓰는 것은 GNU 확장이라 맥의 BSD grep에서는 동작이 보장되지 않습니다. 두 환경에서 같은 결과를 원하면 grep -E "error|warning"을 쓰세요. 또 grep -r로 프로젝트 전체를 검색하면 node_modules와 빌드 산출물까지 뒤지느라 느려지는데, --exclude-dir=node_modules를 붙이거나 .gitignore를 자동으로 존중하는 rg(ripgrep)를 쓰는 편이 낫습니다. 바이너리 파일에 매칭되면 내용 대신 Binary file app.db matches만 출력되는 것도 알아 두면 당황하지 않습니다.

awk: 텍스트 처리

# 특정 컬럼 출력
awk '{print $1}' file.txt     # 첫 번째 컬럼
awk '{print $1, $3}' file.txt # 1, 3번째 컬럼
ps aux | awk '{print $2, $11}' # PID와 명령어
# 조건 필터
awk '$3 > 100' file.txt       # 3번째 컬럼이 100 초과
awk '/error/ {print $0}' app.log  # error 포함 줄
# 합계 계산
awk '{sum += $1} END {print sum}' numbers.txt
du -sk */ | awk '{sum += $1} END {print sum " KB"}'  # -h 출력(4.0K, 1.2G)은 더할 수 없으므로 -k 사용
# 구분자 변경
awk -F: '{print $1}' /etc/passwd  # : 구분자
awk -F',' '{print $2}' data.csv   # CSV
# 실전 예제: 메모리 사용량 상위 5개
ps aux | awk '{print $4, $11}' | sort -rn | head -5
# 로그에서 응답 시간 평균
awk '/response_time/ {sum += $5; count++} END {print sum/count}' app.log

sed: 텍스트 치환

# 기본 치환
sed 's/old/new/' file.txt     # 첫 번째만
sed 's/old/new/g' file.txt    # 모두
sed -i 's/old/new/g' file.txt # 파일 직접 수정 (리눅스)
sed -i '' 's/old/new/g' file.txt  # 맥 (BSD sed는 백업 확장자 인자가 필수 — 빈 문자열로 지정)
# 특정 줄만
sed '3s/old/new/' file.txt    # 3번째 줄만
sed '1,5s/old/new/' file.txt  # 1~5번째 줄
# 줄 삭제
sed '3d' file.txt             # 3번째 줄 삭제
sed '/pattern/d' file.txt     # 패턴 매칭 줄 삭제
# 실전 예제: 설정 파일 수정
sed -i 's/DEBUG=true/DEBUG=false/g' .env
sed -i 's/localhost/0.0.0.0/g' config.yaml

awk는 기본적으로 연속된 공백을 하나의 구분자로 보기 때문에 ps나 ls -l 출력처럼 칸이 맞춰진 텍스트에 잘 맞지만, -F','로 CSV를 자르면 따옴표 안의 쉼표("Seoul, Korea")까지 구분자로 잘라 버립니다. 제대로 된 CSV는 csvkit이나 mlr(Miller) 같은 도구를 쓰는 편이 안전합니다. sed -i로 설정 파일을 고칠 때는 localhost처럼 짧은 패턴이 의도하지 않은 곳(예: localhost_backup)까지 바꾸는 일이 흔하므로, 먼저 -i 없이 실행해 결과를 확인하거나 s/^host: localhost$/host: 0.0.0.0/처럼 줄 전체를 고정해서 쓰세요.


IP, 포트, curl로 네트워크 확인하기

IP 및 네트워크 인터페이스 확인

# IP 주소 확인
ifconfig                      # 맥, 구형 리눅스
ip addr show                  # 최신 리눅스
ip a                          # 축약형
# 특정 인터페이스만
ifconfig en0                  # 맥
ip addr show eth0             # 리눅스
# 외부 IP 확인
curl ifconfig.me
curl ipinfo.io/ip
dig +short myip.opendns.com @resolver1.opendns.com
# 라우팅 테이블
route -n                      # 리눅스
netstat -rn                   # 맥
ip route show                 # 리눅스

포트 및 연결 확인 - 실전 디버깅

# 열린 포트 확인
netstat -tuln                 # 리눅스 (TCP/UDP, listening, numeric)
# -t: TCP만
# -u: UDP만
# -l: listening (LISTEN 상태) 만
# -n: 숫자로 표시 (DNS 조회 안 함, 빠름)

netstat -an | grep LISTEN     # 맥 (BSD 버전)
# -a: all (모든 소켓)
# -n: 숫자로 표시

lsof -i -P -n                 # 모든 네트워크 연결
# -i: internet 소켓만
# -P: 포트를 숫자로 (80 대신 http 표시 방지)
# -n: IP를 숫자로 (DNS 조회 안 함)

lsof -i :8080                 # 특정 포트
# 출력:
# COMMAND  PID  USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
# node    1234  user   12u  IPv4  0x123  0t0     TCP *:8080 (LISTEN)

# 프로세스별 포트 확인
lsof -i -P -n | grep LISTEN
sudo lsof -iTCP -sTCP:LISTEN -n -P
# -s: 상태 필터 (LISTEN, ESTABLISHED, CLOSE_WAIT 등)

# 특정 프로세스의 포트
lsof -p 1234                  # PID 1234의 모든 파일 디스크립터
lsof -c node                  # node로 시작하는 모든 프로세스
lsof -c node -a -i            # node 프로세스의 네트워크 연결만
# -a: AND 연산

# 포트 사용 중인 프로세스 죽이기 (자주 쓰는 패턴)
lsof -ti:8080 | xargs kill
# -t: PID만 출력
# xargs: 파이프로 받은 PID를 kill 인자로 전달

lsof -ti:8080 | xargs kill -9  # 강제 종료 (SIGKILL)
# kill -9: 프로세스가 즉시 종료 (정리 작업 불가)
# 주의: 데이터 손실 가능

# 한 줄로 해결 (더 간단)
kill $(lsof -ti:8080)          # 서브셸 사용

# ss (최신 리눅스, netstat 대체) - 더 빠르고 강력
ss -tuln                      # 열린 포트
# /proc/net/tcp를 파싱하는 netstat과 달리 netlink로 커널에서 직접 받아와 연결이 많을수록 빠름

ss -tunp                      # 프로세스 포함 (root 필요)
# 출력:
# tcp LISTEN 0 128 *:8080 *:* users:(("node",pid=1234,fd=12))

ss -s                         # 통계
# 전체 TCP/UDP 연결 수, 상태별 통계

# 실전 예제: ESTABLISHED 연결 수 확인
ss -tan state established | wc -l
# -t: TCP
# -a: all
# -n: 숫자
# state established: ESTABLISHED 상태만

# 특정 포트의 모든 연결
ss -tan 'sport = :8080'       # 소스 포트 8080
ss -tan 'dport = :80'         # 목적지 포트 80

# TIME_WAIT 상태 확인 (포트 고갈 진단)
ss -tan state time-wait | wc -l
# 수천 개면 문제 (Keep-Alive 사용 권장)

실전 시나리오:

# 시나리오 1: "포트가 이미 사용 중입니다" 에러
# → 어떤 프로세스가 포트를 쓰고 있는지 확인
lsof -i :3000
# 출력:
# COMMAND  PID  USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
# node    5678  user   10u  IPv4  0x123  0t0     TCP *:3000 (LISTEN)
# → PID 5678 (node)가 사용 중
# 종료:
kill 5678

# 시나리오 2: 서버가 응답 없음
# → 포트가 LISTEN 상태인지 확인
netstat -an | grep 8080
# tcp4  0  0  *.8080  *.*  LISTEN  ← 정상
# (아무 출력 없음)  ← 서버가 시작 안 됨

# 시나리오 3: 너무 많은 연결로 서버 부하
# → ESTABLISHED 연결 수 확인
ss -s
# TCP: 5000 (estab 4500, closed 100, orphaned 0, timewait 400)
# → 4500개 활성 연결 (너무 많음)
# 연결 제한 또는 스케일 아웃 필요

연결 테스트

# 핑 테스트
ping google.com
ping -c 4 8.8.8.8             # 4번만
# 포트 연결 테스트
telnet example.com 80
nc -zv example.com 80         # netcat
curl -I https://example.com   # HTTP 헤더만
# 경로 추적
traceroute google.com
traceroute -n google.com      # IP만 (DNS 생략)
mtr google.com                # 실시간 traceroute
# DNS 조회
nslookup example.com
dig example.com
dig example.com +short        # IP만
host example.com

curl: HTTP 요청 - API 테스트의 핵심 도구

# GET 요청 (기본)
curl https://api.example.com/users
# 출력: JSON 또는 HTML 응답 본문

curl -i https://example.com   # 헤더 포함 (include headers)
# HTTP/1.1 200 OK
# Content-Type: application/json
# ...
# {"data": "..."}

curl -v https://example.com   # 상세 정보 (verbose)
# > GET / HTTP/1.1  ← 요청 헤더
# < HTTP/1.1 200 OK  ← 응답 헤더
# SSL 핸드셰이크, 리다이렉트 등 모든 과정 출력

curl -I https://example.com   # HEAD 요청 (헤더만)
# 응답 본문 없이 헤더만 받음 (빠름)

# POST 요청 (API 호출)
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John","email":"[email protected]"}'
# -X: HTTP 메서드 지정 (POST, PUT, DELETE 등)
# -H: 헤더 추가
# -d: 데이터 (자동으로 POST로 전환)

# 파일에서 데이터 읽기
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d @data.json
# @: 파일에서 읽기

# 파일 업로드 (multipart/form-data)
curl -F "[email protected]" https://api.example.com/upload
# -F: form data
# @: 파일 경로

curl -F "[email protected]" -F "title=MyPhoto" https://api.example.com/upload
# 여러 필드 전송

# 인증 (Authentication)
curl -u user:pass https://api.example.com
# Basic 인증 (Base64 인코딩됨)

curl -H "Authorization: Bearer TOKEN" https://api.example.com
# Bearer 토큰 (JWT 등)

curl -H "Authorization: Bearer $(cat token.txt)" https://api.example.com
# 파일에서 토큰 읽기

# 쿠키 사용
curl -b "session_id=abc123" https://example.com
# -b: 쿠키 전송

curl -c cookies.txt https://example.com
# -c: 응답 쿠키 저장

curl -b cookies.txt -c cookies.txt https://example.com
# 쿠키 읽고 저장 (세션 유지)

# 응답 저장
curl -o output.html https://example.com
# -o: 지정한 파일명으로 저장

curl -O https://example.com/file.zip  # 원본 파일명으로 저장
# -O: 대문자 O, URL의 마지막 부분을 파일명으로

# 타임아웃 설정 (프로덕션 필수)
curl --connect-timeout 5 https://example.com
# 연결 타임아웃 5초

curl --max-time 10 https://example.com
# 전체 작업 타임아웃 10초

curl --connect-timeout 5 --max-time 30 https://example.com
# 연결 5초, 전체 30초

# 리다이렉트 따라가기
curl -L https://example.com
# -L: 301, 302 리다이렉트 자동 따라감

# SSL 인증서 무시 (테스트 환경만!)
curl -k https://self-signed.example.com
# -k: insecure, 인증서 검증 안 함 (위험!)

# 프록시 사용
curl -x http://proxy.example.com:8080 https://api.example.com
# -x: 프록시 서버

# 속도 측정 (-w: write-out)
curl -X POST http://localhost:3000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret"}' \
  -w "\nStatus: %{http_code}\nTime: %{time_total}s\n"
# %{http_code}: HTTP 상태 코드
# %{time_total}: 전체 소요 시간
# %{time_connect}: 연결 시간
# %{size_download}: 다운로드 크기

# 더 상세한 측정
curl -w @curl-format.txt -o /dev/null -s https://example.com
# curl-format.txt:
#     time_namelookup:  %{time_namelookup}s\n
#     time_connect:     %{time_connect}s\n
#     time_appconnect:  %{time_appconnect}s\n
#     time_pretransfer: %{time_pretransfer}s\n
#     time_redirect:    %{time_redirect}s\n
#     time_starttransfer: %{time_starttransfer}s\n
#     time_total:       %{time_total}s\n

# 병렬 요청 (부하 테스트 간단 버전)
for i in {1..100}; do
  curl -s https://api.example.com/health &
done
wait
# 100개 요청 동시 전송

# JSON 응답 파싱 (jq 필요)
curl -s https://api.example.com/users | jq '.[] | .name'
# -s: silent (진행률 숨김)
# jq: JSON 파서

실전 시나리오:

# 시나리오 1: API가 느림, 어느 단계가 문제인지 확인
curl -w "DNS: %{time_namelookup}s, Connect: %{time_connect}s, Total: %{time_total}s\n" \
  -o /dev/null -s https://slow-api.com
# 출력:
# DNS: 0.05s, Connect: 0.5s, Total: 5.2s
# → 네트워크 연결이 느림 (0.5s)

# 시나리오 2: API 응답 검증 (CI/CD)
response=$(curl -s -w "%{http_code}" https://api.example.com/health -o response.txt)
if [ "$response" == "200" ]; then
  echo "✅ API is healthy"
else
  echo "❌ API failed: $response"
  exit 1
fi

# 시나리오 3: API 부하 테스트 (간단 버전)
for i in {1..1000}; do
  (curl -s -w "%{time_total}\n" https://api.example.com -o /dev/null &)
done | awk '{sum += $1; count++} END {print "Avg:", sum/count, "s"}'
# 1000개 요청의 평균 응답 시간 측정

wget: 파일 다운로드

# 파일 다운로드
wget https://example.com/file.zip
wget -O custom_name.zip https://example.com/file.zip
# 재시도 및 백그라운드
wget -c https://example.com/large.iso  # 이어받기
wget -b https://example.com/file.zip   # 백그라운드
# 여러 파일
wget -i urls.txt              # 파일에서 URL 읽기
# 웹사이트 전체 다운로드
wget -r -np -k https://example.com/docs/

ps, kill, 백그라운드 실행

프로세스 확인

# 모든 프로세스
ps aux                        # BSD 스타일
ps -ef                        # System V 스타일
# 특정 프로세스 검색
ps aux | grep node
pgrep node                    # PID만
pgrep -l node                 # PID와 이름
# 프로세스 트리
ps auxf                       # 리눅스
pstree                        # 트리 구조
pstree -p                     # PID 포함
# 실시간 모니터링
top                           # 기본
top -o cpu                    # CPU 순 정렬 (맥)
htop                          # 개선된 top (설치 필요)
# 특정 프로세스 모니터링
top -p 1234                   # PID 1234만 (리눅스)

프로세스 종료

# 프로세스 종료
kill 1234                     # PID로 종료 (SIGTERM)
kill -9 1234                  # 강제 종료 (SIGKILL)
killall node                  # 이름으로 모두 종료
pkill node                    # 패턴으로 종료
# 포트로 프로세스 찾아 종료
lsof -ti:8080 | xargs kill
kill $(lsof -ti:8080)
# 실전 예제: 좀비 프로세스의 부모 찾기
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'
# 좀비는 이미 죽은 프로세스라 kill -9로도 없어지지 않음
# → 출력된 PPID(부모)가 wait()를 하도록 고치거나 부모를 재시작

kill은 기본으로 SIGTERM을 보내 프로세스가 정리 작업(열린 파일 닫기, 처리 중인 요청 마무리)을 할 기회를 줍니다. kill -9(SIGKILL)는 커널이 즉시 끝내 버리므로 임시 파일이나 락 파일이 남고, DB라면 복구 과정이 필요해질 수 있습니다. 먼저 kill을 보내고 몇 초 기다린 뒤에도 살아 있을 때만 -9를 쓰는 순서를 지키세요. 좀비 프로세스(STAT이 Z, <defunct>)는 이미 종료되어 부모가 종료 상태를 거둬 가기만 기다리는 상태라 어떤 시그널로도 지울 수 없습니다. 좀비가 계속 늘어난다면 자식을 wait하지 않는 부모 프로그램의 버그이고, 부모를 종료하면 좀비는 init(PID 1)에 입양되어 정리됩니다. 컨테이너 안에서는 PID 1이 이 역할을 못 하는 경우가 있어 docker run --init이나 tini를 쓰는 이유가 여기에 있습니다.

백그라운드 실행

# 백그라운드 실행
npm start &
nohup npm start &             # 터미널 종료 후에도 실행
nohup npm start > app.log 2>&1 &  # 로그 파일로
# 백그라운드 작업 확인
jobs
jobs -l                       # PID 포함
# 포그라운드로 가져오기
fg %1                         # 작업 번호 1
# 실행 중인 프로세스 일시정지
Ctrl+Z
bg                            # 백그라운드로 재개

tail·sort·uniq로 로그 분석하기

실시간 로그 모니터링

# 실시간 로그
tail -f /var/log/app.log
tail -f -n 100 app.log        # 최근 100줄부터
# 여러 파일 동시 모니터링
tail -f app.log error.log
multitail app.log error.log   # 분할 화면 (설치 필요)
# 패턴 필터링하며 실시간
tail -f app.log | grep "ERROR"
tail -f app.log | grep --line-buffered "ERROR"  # 즉시 출력

로그 분석

# 에러 개수
grep -c "ERROR" app.log
# 시간대별 에러
grep "2026-04-01 14:" app.log | grep "ERROR"
# 상위 에러 유형
grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c | sort -rn
# IP별 요청 수
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 응답 시간 분석
awk '{sum += $10; count++} END {print "Avg:", sum/count, "ms"}' app.log
# 특정 시간대 추출
sed -n '/2026-04-01 14:00/,/2026-04-01 15:00/p' app.log

텍스트 정렬 및 중복 제거

# 정렬
sort file.txt
sort -r file.txt              # 역순
sort -n file.txt              # 숫자 정렬
sort -k 2 file.txt            # 2번째 컬럼 기준
# 중복 제거
sort file.txt | uniq
sort file.txt | uniq -c       # 개수 포함
sort file.txt | uniq -d       # 중복만
# 실전 예제: 접속 IP 상위 10개
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

wc: 단어·줄 개수

# 줄 개수
wc -l file.txt
cat file.txt | wc -l
# 단어 개수
wc -w file.txt
# 바이트 크기
wc -c file.txt
# 실전 예제
find . -name "*.js" | wc -l   # JS 파일 개수
grep "ERROR" app.log | wc -l  # 에러 개수

tcpdump, 속도 테스트, 방화벽

패킷 캡처 및 분석

# tcpdump (root 권한 필요)
sudo tcpdump -i eth0          # eth0 인터페이스
sudo tcpdump -i any port 80   # 모든 인터페이스, 포트 80
sudo tcpdump -i eth0 -w capture.pcap  # 파일로 저장
sudo tcpdump -r capture.pcap  # 파일 읽기
# 특정 호스트
sudo tcpdump host 192.168.1.100
sudo tcpdump src 192.168.1.100
sudo tcpdump dst 192.168.1.100
# HTTP 요청 보기
sudo tcpdump -i any -A port 80 | grep "GET\|POST"

네트워크 속도 테스트

# 다운로드 속도
curl -o /dev/null -w "Speed: %{speed_download} bytes/sec\n" https://example.com/file.zip
# 대역폭 측정
iperf3 -c server_ip           # 클라이언트
iperf3 -s                     # 서버
# ping 통계
ping -c 100 google.com | tail -3

방화벽 확인

# iptables (리눅스)
sudo iptables -L
sudo iptables -L -n -v        # 상세
# ufw (Ubuntu)
sudo ufw status
sudo ufw allow 22/tcp
sudo ufw deny 80/tcp
# firewalld (CentOS/RHEL)
sudo firewall-cmd --list-all
sudo firewall-cmd --add-port=8080/tcp --permanent

디스크 사용량과 시스템 정보

디스크 사용량

# 전체 디스크
df -h                         # 사람이 읽기 쉽게
df -h /                       # 루트 파티션만
df -i                         # inode 사용량
# 디렉토리별 크기
du -sh *                      # 현재 디렉토리 하위
du -sh /var/log/*             # 로그 디렉토리
du -h --max-depth=1 /var      # 1단계까지
# 큰 디렉토리 찾기
du -sh * | sort -rh | head -10
du -ah / | sort -rh | head -20  # 파일 포함
# 실전 예제: 디스크 풀 때
df -h                         # 어느 파티션?
du -sh /var/log/* | sort -rh  # 로그 확인
find /var/log -name "*.log" -mtime +30 -size +100M  # 오래된 큰 로그

시스템 정보

# OS 정보
uname -a                      # 전체 정보
uname -r                      # 커널 버전
cat /etc/os-release           # 리눅스 배포판
sw_vers                       # 맥 버전
# CPU 정보
lscpu                         # 리눅스
sysctl -n machdep.cpu.brand_string  # 맥
cat /proc/cpuinfo             # 리눅스
# 메모리 정보
free -h                       # 리눅스
vm_stat                       # 맥
cat /proc/meminfo             # 리눅스
# 가동 시간
uptime
uptime -p                     # 사람이 읽기 쉽게

시스템 리소스 모니터링

# 실시간 리소스
top
htop                          # 개선된 버전
glances                       # 종합 모니터링
# I/O 모니터링
iostat                        # CPU·디스크 I/O
iostat -x 2                   # 2초마다 상세
iotop                         # 프로세스별 I/O (root)
# 네트워크 I/O
iftop                         # 실시간 네트워크 (root)
nethogs                       # 프로세스별 네트워크

chmod·chown으로 권한과 소유자 관리

권한 변경

# chmod: 권한 변경
chmod 755 script.sh           # rwxr-xr-x
chmod +x script.sh            # 실행 권한 추가
chmod -R 644 files/           # 재귀적으로
# 숫자 의미
# 4 = 읽기(r), 2 = 쓰기(w), 1 = 실행(x)
# 755 = rwxr-xr-x (소유자 7, 그룹 5, 기타 5)
# 644 = rw-r--r-- (소유자 6, 그룹 4, 기타 4)
# 실전 예제
chmod 600 ~/.ssh/id_rsa       # SSH 키 권한
chmod 644 ~/.ssh/id_rsa.pub
chmod 700 ~/.ssh

소유자 변경

# chown: 소유자 변경
sudo chown user file.txt
sudo chown user:group file.txt
sudo chown -R user:group dir/
# 실전 예제
sudo chown -R www-data:www-data /var/www/html
sudo chown -R $USER:$USER ~/project

권한 확인

# 상세 권한
ls -l file.txt
stat file.txt                 # 더 상세
# 실행 가능 파일 찾기
find . -type f -perm -111
find . -type f -executable

tar, zip, gzip 압축

tar: 압축·해제

# 압축
tar -czf archive.tar.gz dir/  # gzip
tar -cjf archive.tar.bz2 dir/ # bzip2
tar -cJf archive.tar.xz dir/  # xz (더 작음)
# 해제
tar -xzf archive.tar.gz
tar -xzf archive.tar.gz -C /path/to/dest  # 특정 위치
# 내용 확인 (해제 안 함)
tar -tzf archive.tar.gz
tar -tzf archive.tar.gz | grep "file"
# 실전 예제: 백업
tar -czf backup-$(date +%Y%m%d).tar.gz ~/project
tar -czf - ~/project | ssh user@remote "cat > backup.tar.gz"

zip: 압축·해제

# 압축
zip archive.zip file1.txt file2.txt
zip -r archive.zip dir/       # 디렉토리
# 해제
unzip archive.zip
unzip archive.zip -d dest/    # 특정 위치
unzip -l archive.zip          # 내용 확인
# 암호화
zip -e secure.zip file.txt

gzip, bzip2

# gzip
gzip file.txt                 # file.txt.gz 생성 (원본 삭제)
gzip -k file.txt              # 원본 유지
gunzip file.txt.gz            # 해제
# bzip2
bzip2 file.txt
bunzip2 file.txt.bz2
# 압축률 비교
gzip file.txt    # 빠름, 보통 압축
bzip2 file.txt   # 느림, 높은 압축
xz file.txt      # 가장 느림, 최고 압축

환경 변수, SSH, Git, Docker

환경 변수

# 환경 변수 확인
env                           # 모든 환경 변수
echo $PATH
echo $HOME
printenv PATH
# 환경 변수 설정
export API_KEY="secret"
export PATH="$PATH:/new/path"
# 영구 설정 (bash)
echo 'export API_KEY="secret"' >> ~/.bashrc
source ~/.bashrc
# 영구 설정 (zsh)
echo 'export API_KEY="secret"' >> ~/.zshrc
source ~/.zshrc

SSH

# SSH 접속
ssh user@hostname
ssh -p 2222 user@hostname     # 포트 지정
ssh -i ~/.ssh/key.pem user@hostname  # 키 파일
# SSH 키 생성
ssh-keygen -t ed25519 -C "[email protected]"
ssh-keygen -t rsa -b 4096
# 공개키 복사
ssh-copy-id user@hostname
cat ~/.ssh/id_rsa.pub | ssh user@hostname "cat >> ~/.ssh/authorized_keys"
# SSH 터널링
ssh -L 8080:localhost:80 user@remote  # 로컬 포트 포워딩
ssh -R 8080:localhost:3000 user@remote  # 리모트 포트 포워딩
# SCP: 파일 복사
scp file.txt user@remote:/path/
scp -r dir/ user@remote:/path/
scp user@remote:/path/file.txt ./

Git 관련 명령어

# 브랜치 정리
git branch -d old-branch
git branch -D force-delete
git remote prune origin       # 삭제된 리모트 브랜치 정리
# 로그 한 줄로
git log --oneline -10
git log --graph --oneline --all
# 변경 사항 확인
git diff
git diff --staged
git diff HEAD~1 HEAD
# 실전 예제: 특정 파일 히스토리
git log -p -- path/to/file.txt
git log --follow -- file.txt  # 이름 변경 추적

Docker 관련

# 컨테이너 관리
docker ps                     # 실행 중
docker ps -a                  # 모든 컨테이너
docker logs -f container_id   # 로그 실시간
docker exec -it container_id bash  # 컨테이너 접속
# 이미지 관리
docker images
docker rmi image_id
docker system prune -a        # 미사용 정리
# 실전 예제: 디버깅
docker logs --tail 100 -f container_id
docker exec -it container_id sh -c "ps aux"
docker stats                  # 리소스 사용량

포트 충돌·느린 서버·디스크 풀 대응 명령 조합

시나리오 1: “포트 8080이 이미 사용 중”

# 1. 포트 사용 프로세스 확인
lsof -i :8080
# 또는
netstat -tuln | grep 8080
# 2. PID 확인 후 종료
kill $(lsof -ti:8080)
# 3. 재확인
lsof -i :8080

시나리오 2: “서버가 느려요”

# 1. CPU·메모리 확인
top
htop
# 2. 디스크 I/O 확인
iostat -x 2
# 3. 네트워크 확인
iftop
nethogs
# 4. 로그 확인
tail -f /var/log/app.log | grep "ERROR\|WARN"
# 5. 프로세스별 리소스
ps aux --sort=-%cpu | head -10
ps aux --sort=-%mem | head -10

시나리오 3: “디스크가 꽉 찼어요”

# 1. 디스크 사용량 확인
df -h
# 2. 큰 디렉토리 찾기
du -sh /* | sort -rh | head -10
du -sh /var/* | sort -rh
# 3. 큰 파일 찾기
find / -type f -size +1G 2>/dev/null
find /var/log -name "*.log" -size +100M
# 4. 오래된 로그 삭제
find /var/log -name "*.log" -mtime +30 -delete
find /tmp -mtime +7 -delete
# 5. Docker 정리 (개발 환경)
docker system prune -a
docker volume prune

시나리오 4: “API가 응답 안 해요”

# 1. 서비스 실행 확인
ps aux | grep node
systemctl status myapp        # systemd
# 2. 포트 리스닝 확인
lsof -i :3000
netstat -tuln | grep 3000
# 3. 로컬 연결 테스트
curl http://localhost:3000/health
curl -v http://localhost:3000
# 4. 외부 연결 테스트
curl http://server-ip:3000
telnet server-ip 3000
# 5. 방화벽 확인
sudo iptables -L
sudo ufw status
# 6. 로그 확인
tail -f /var/log/app.log
journalctl -u myapp -f        # systemd

시나리오 5: “특정 에러 로그만 추출”

# 1. 시간대별 에러
grep "2026-04-01 14:" app.log | grep "ERROR" > errors.txt
# 2. 에러 유형별 분류
grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c | sort -rn
# 3. 특정 에러만
grep "NullPointerException" app.log > null_errors.txt
# 4. 컨텍스트 포함
grep -A 10 "ERROR" app.log > errors_with_context.txt
# 5. 여러 파일에서
grep -r "ERROR" /var/log/app/*.log | grep "database"

시나리오 6: “배포 스크립트 자동화”

#!/bin/bash
# deploy.sh
set -e  # 에러 시 중단
echo "🚀 배포 시작..."
# 1. Git 최신 코드
git pull origin main
# 2. 의존성 설치
npm ci
# 3. 빌드
npm run build
# 4. 기존 프로세스 종료
pkill -f "node.*server.js" || true
# 5. 백업
tar -czf backup-$(date +%Y%m%d-%H%M%S).tar.gz dist/
# 6. 새 프로세스 시작
nohup npm start > app.log 2>&1 &
# 7. 헬스 체크
sleep 5
curl -f http://localhost:3000/health || exit 1
echo "✅ 배포 완료"

시나리오 7: “로그 로테이션 수동”

# 1. 현재 로그 백업
cp app.log app.log.$(date +%Y%m%d)
# 2. 로그 비우기
> app.log
# 또는
truncate -s 0 app.log
# 3. 오래된 백업 삭제
find . -name "app.log.*" -mtime +30 -delete
# 4. 압축
gzip app.log.20260301

파이프, alias, 히스토리 활용

파이프 조합

# 프로세스 메모리 사용량 상위 10개
ps aux | awk 'NR>1 {print $4, $11}' | sort -rn | head -10
# 접속 IP별 요청 수
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn
# 에러율 계산
total=$(wc -l < app.log)
errors=$(grep -c "ERROR" app.log)
echo "scale=2; $errors / $total * 100" | bc
# 디렉토리별 파일 개수
find . -type f | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn

유용한 별칭 (alias)

# ~/.bashrc 또는 ~/.zshrc에 추가
alias ll='ls -lah'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias grep='grep --color=auto'
alias ports='lsof -i -P -n | grep LISTEN'
alias myip='curl ifconfig.me'
alias serve='python3 -m http.server 8000'
# Git 별칭
alias gs='git status'
alias ga='git add'
alias gc='git commit -m'
alias gp='git push'
alias gl='git log --oneline -10'
# Docker 별칭
alias dps='docker ps'
alias dpa='docker ps -a'
alias di='docker images'
alias dlog='docker logs -f'

히스토리 활용

# 명령어 히스토리
history
history | grep "docker"
# 히스토리 재실행
!123                          # 123번 명령어
!!                            # 직전 명령어
!$                            # 직전 명령어의 마지막 인자
!*                            # 직전 명령어의 모든 인자
# 실전 예제
sudo !!                       # 직전 명령어를 sudo로
ls /var/log
cd !$                         # cd /var/log

파일 비교

# diff: 파일 비교
diff file1.txt file2.txt
diff -u file1.txt file2.txt   # unified 형식
diff -y file1.txt file2.txt   # 나란히
# 디렉토리 비교
diff -r dir1/ dir2/
# 바이너리 비교
cmp file1.bin file2.bin

파일 검색 고급

# locate (DB 기반, 빠름)
locate file.txt
sudo updatedb                 # DB 업데이트
# which: 실행 파일 경로
which python
which -a python               # 모든 경로
# whereis: 바이너리·소스·매뉴얼
whereis python

터미널 단축키

터미널 단축키

Ctrl+C          현재 명령어 중단
Ctrl+Z          현재 프로세스 일시정지
Ctrl+D          EOF (로그아웃)
Ctrl+L          화면 지우기 (clear)
Ctrl+A          줄 맨 앞으로
Ctrl+E          줄 맨 뒤로
Ctrl+U          커서 앞 전부 삭제
Ctrl+K          커서 뒤 전부 삭제
Ctrl+W          단어 하나 삭제
Ctrl+R          명령어 히스토리 검색
Ctrl+S          출력 일시정지
Ctrl+Q          출력 재개

커서 이동

Alt+B           단어 하나 뒤로
Alt+F           단어 하나 앞으로
Ctrl+XX         줄 맨 앞과 현재 위치 토글

systemd 서비스 관리

서비스 관리

# 서비스 상태
systemctl status nginx
systemctl status myapp
# 서비스 시작·정지
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx   # 설정만 리로드
# 부팅 시 자동 시작
sudo systemctl enable nginx
sudo systemctl disable nginx
# 로그 확인
journalctl -u nginx
journalctl -u nginx -f        # 실시간
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-04-01 14:00"

서비스 파일 작성

# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js App
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
# 서비스 등록
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

strace, ldd로 성능 분석

strace: 시스템 콜 추적

# 프로세스 추적
strace ls
strace -p 1234                # 실행 중인 프로세스
# 파일 I/O만
strace -e open,read,write ls
# 시간 측정
strace -c ls                  # 통계
strace -T ls                  # 각 호출 시간
# 실전 예제: 설정 파일 찾기
strace -e open myapp 2>&1 | grep "config"

ldd: 라이브러리 의존성

# 실행 파일 의존성
ldd /usr/bin/python3
ldd myapp
# 실전 예제: 누락된 라이브러리 찾기
ldd myapp | grep "not found"

네트워크 디버깅 고급

# 연결 상태별 개수
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c
# TIME_WAIT 많을 때
netstat -an | grep TIME_WAIT | wc -l
# 특정 IP 연결 수
netstat -an | grep 192.168.1.100 | wc -l
# 실전 예제: DDoS 의심 시
netstat -an | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

자주 쓰는 원라이너

파일 처리

# 빈 줄 제거
grep -v '^$' file.txt
# 중복 줄 제거
sort file.txt | uniq
# 파일 합치기
cat file1.txt file2.txt > merged.txt
# CSV 특정 컬럼
awk -F',' '{print $2}' data.csv
# JSON 파싱 (jq 필요)
curl https://api.example.com/users | jq '.[] | .name'
cat data.json | jq '.users[0].email'

시스템 모니터링

# CPU 사용률 상위 10개
ps aux | sort -rnk 3 | head -10   # -n 없으면 문자열 정렬이라 9.0이 10.0보다 위에 옴
# 메모리 사용률 상위 10개
ps aux | sort -rnk 4 | head -10
# 디스크 사용률 80% 이상
df -h | awk '$5+0 > 80 {print $0}'
# 좀비 프로세스 찾기
ps aux | awk '$8=="Z" {print $0}'

네트워크

# 외부 IP
curl -s ifconfig.me
# 포트 스캔
nc -zv example.com 80-443
# DNS 전파 확인
dig @8.8.8.8 example.com
dig @1.1.1.1 example.com
# 응답 시간 측정
time curl https://example.com > /dev/null

로그 분석

# 시간대별 요청 수
awk '{print $4}' access.log | cut -d: -f2 | sort | uniq -c
# 상태 코드별 개수
awk '{print $9}' access.log | sort | uniq -c
# 느린 요청 찾기
awk '$10 > 1000 {print $0}' app.log  # 1000ms 이상
# 에러 로그만 추출
grep -E "ERROR|FATAL" app.log > errors.txt

macOS 전용 명령어

맥 시스템 관리

# 앱 목록
ls /Applications
# Homebrew
brew install package
brew update
brew upgrade
brew list
brew search keyword
# 서비스 관리 (brew services)
brew services list
brew services start postgresql
brew services stop postgresql
# 맥 정보
sw_vers
system_profiler SPHardwareDataType

맥 네트워크

# 네트워크 인터페이스
networksetup -listallhardwareports
networksetup -getinfo Wi-Fi
# DNS 캐시 클리어
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
# 열린 파일·포트
lsof -i -P -n | grep LISTEN

리눅스 전용: 패키지 관리와 systemd

패키지 관리

# Ubuntu/Debian (apt)
sudo apt update
sudo apt upgrade
sudo apt install package
sudo apt remove package
sudo apt autoremove
# CentOS/RHEL (yum/dnf)
sudo yum update
sudo yum install package
sudo dnf install package
# 패키지 검색
apt search keyword
yum search keyword

systemd 고급

# 실패한 서비스
systemctl --failed
# 부팅 시간 분석
systemd-analyze
systemd-analyze blame         # 서비스별 시간
# 로그 크기 제한
journalctl --disk-usage
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-size=500M

ripgrep, fd 등 추가로 설치할 도구

설치 권장 도구

# 리눅스
sudo apt install -y \
  curl wget git vim \
  htop tree ncdu \
  net-tools dnsutils \
  jq ripgrep fd-find \
  tmux
# 맥
brew install \
  curl wget git vim \
  htop tree ncdu \
  jq ripgrep fd \
  tmux fzf bat

도구별 용도

도구용도예제
ripgrep빠른 검색rg "error" src/
fd빠른 파일 찾기fd "\.js$"
jqJSON 파싱cat data.json | jq '.name'
batcat 개선bat file.txt
ncdu디스크 사용량 UIncdu /var
tmux터미널 멀티플렉서tmux new -s dev
fzf퍼지 파인더history | fzf

ripgrep (rg) 예제

# grep보다 빠름
rg "error" src/
rg -i "error"                 # 대소문자 무시
rg -l "TODO"                  # 파일명만
rg -t js "import"             # JS 파일만
rg "error" -g "*.log"         # 로그 파일만
rg -A 5 "error"               # 후 5줄

fd 예제

# find보다 빠르고 간단
fd "\.js$"
fd -e js                      # 확장자
fd -t f                       # 파일만
fd -t d                       # 디렉토리만
fd -H "\.env"                 # 숨김 파일 포함
fd -E node_modules            # 제외

SSH 보안, 파일 무결성, 포트 스캔

SSH 보안

# SSH 로그인 시도 확인
sudo grep "Failed password" /var/log/auth.log
sudo grep "Accepted" /var/log/auth.log
# IP별 실패 횟수
sudo grep "Failed password" /var/log/auth.log | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
# 현재 SSH 세션
who
w
last                          # 로그인 히스토리

파일 무결성

# 체크섬
md5sum file.txt               # 리눅스
md5 file.txt                  # 맥
shasum -a 256 file.txt        # SHA-256
# 파일 비교
md5sum file1.txt file2.txt

열린 포트 스캔

# nmap (설치 필요)
nmap localhost
nmap -p 1-65535 localhost     # 모든 포트
nmap -sV localhost            # 서비스 버전

배포 전과 장애 때 확인할 명령

서버 배포 전 체크

# 1. 디스크 공간
df -h
# 2. 메모리
free -h
# 3. 포트 충돌
lsof -i :8080
# 4. 방화벽
sudo ufw status
# 5. 로그 로테이션 설정
ls -lh /var/log/*.log
# 6. 환경 변수
env | grep API_KEY
# 7. 권한
ls -l /var/www/html

장애 대응 체크

# 1. 서비스 상태
systemctl status myapp
# 2. 최근 로그
journalctl -u myapp -n 100
# 3. 리소스
top
df -h
# 4. 네트워크
netstat -tuln | grep LISTEN
curl http://localhost:3000/health
# 5. 프로세스
ps aux | grep myapp

카테고리별 핵심 명령어

카테고리명령어용도
디렉토리cd, pwd, ls, tree탐색
파일cat, less, head, tail내용 보기
검색grep, find, rg, fd파일·텍스트 찾기
텍스트awk, sed, cut, sort처리·변환
네트워크curl, netstat, lsof, ss연결·포트
프로세스ps, top, kill, htop관리·모니터링
디스크df, du, ncdu사용량
압축tar, gzip, zip아카이브
권한chmod, chown, sudo접근 제어
시스템systemctl, journalctl서비스 관리

자주 쓰는 조합 패턴

# 로그 실시간 에러 모니터링
tail -f app.log | grep --line-buffered "ERROR"
# 프로세스 메모리 사용량
ps aux | awk '{print $4, $11}' | sort -rn | head -10
# 포트 사용 프로세스 종료
kill $(lsof -ti:8080)
# 디스크 큰 파일 찾기
du -ah / 2>/dev/null | sort -rh | head -20
# 접속 IP 상위 10개
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

명령어를 익히는 방법

  1. man 페이지 활용: man ls, man grep
  2. tldr 설치: 간단한 예제 보기 (npm install -g tldr)
  3. 히스토리 검색: Ctrl+R로 이전 명령어 빠르게 찾기
  4. 별칭 설정: 자주 쓰는 명령어는 ~/.bashrc에 별칭 추가
  5. 스크립트 작성: 반복 작업은 쉘 스크립트로 자동화

앱 미기동, 디스크 풀, 느린 API, 메모리 누수 추적 순서

Node.js 앱이 시작되지 않을 때

# 1. 포트 충돌 확인
lsof -i :3000
# 출력: node 1234 user ... *:3000 (LISTEN)
# 2. 프로세스 종료
kill 1234
# 3. 재시작
npm start
# 4. 로그 확인
tail -f logs/app.log

디스크가 가득 찼을 때

# 1. 사용량 확인
df -h
# 출력: /dev/sda1 98% 사용
# 2. 큰 디렉토리 찾기
du -sh /* | sort -rh | head -5
# 출력: /var/log 15GB
# 3. 로그 확인
du -sh /var/log/* | sort -rh
# 출력: app.log 10GB
# 4. 오래된 로그 삭제
find /var/log -name "*.log" -mtime +7 -size +100M -delete
# 5. 재확인
df -h

API 응답이 느릴 때

# 1. 프로세스 리소스 확인
top -p $(pgrep -d',' node)
# 2. 네트워크 연결 수
netstat -an | grep :3000 | grep ESTABLISHED | wc -l
# 3. 응답 시간 측정
time curl http://localhost:3000/api/users
# 4. 로그에서 느린 요청
awk '$10 > 1000 {print $0}' app.log
# 5. 데이터베이스 연결 확인
lsof -i :5432                 # PostgreSQL

메모리 누수가 의심될 때

# 1. 메모리 사용량 추이
while true; do
  ps aux | grep myapp | awk '{print $4, $6}'
  sleep 60
done
# 2. 상세 메모리 정보
pmap 1234                     # PID 1234
cat /proc/1234/status | grep VmRSS
# 3. 힙 프로파일링 (Node.js)
node --inspect server.js
# Chrome DevTools로 연결
# 4. 재시작 후 비교
systemctl restart myapp

매일 쓰는 명령어와 다음 학습

매일 쓰는 10개:

  1. ls -lah — 파일 목록
  2. cd — 디렉토리 이동
  3. grep — 텍스트 검색
  4. tail -f — 로그 실시간
  5. ps aux | grep — 프로세스 찾기
  6. lsof -i — 포트 확인
  7. curl — HTTP 요청
  8. df -h — 디스크 사용량
  9. top — 리소스 모니터링
  10. systemctl status — 서비스 상태 문제 해결 흐름:
  11. 로그 확인: tail -f, journalctl
  12. 리소스 확인: top, df, free
  13. 네트워크 확인: lsof, netstat, curl
  14. 프로세스 확인: ps, pgrep
  15. 재시작: systemctl restart

학습 자료

온라인 연습:

  • explainshell.com — 명령어 분해 설명

  • tldr.sh — 간단한 예제

  • commandlinefu.com — 커뮤니티 원라이너 책:

  • “The Linux Command Line” by William Shotts

  • “Unix and Linux System Administration Handbook”

다음 단계

명령어에 익숙해지면:

실전 적용:

  1. 자주 쓰는 명령어 10개 선택
  2. ~/.bashrc에 별칭 추가
  3. 배포 스크립트 작성
  4. 모니터링 대시보드 구축

자주 묻는 질문 (FAQ)

Q. 명령어를 외워야 하나요?

A. 자주 쓰는 10~20개만 외우며, 나머지는 필요할 때 찾아 쓰세요. man 명령어나 tldr로 빠르게 확인할 수 있습니다.

Q. 리눅스와 맥 명령어 차이는?

A. 대부분 동일하지만, GNU(리눅스)와 BSD(맥) 버전 차이가 있습니다. 예: sed -i 's/a/b/' (리눅스) vs sed -i '' 's/a/b/' (맥, 백업 확장자 인자 필수). 이 글에서는 공통 명령어를 우선 소개했습니다.

Q. Windows에서는 못 쓰나요?

A. WSL(Windows Subsystem for Linux), Git Bash, PowerShell 일부 명령어로 사용 가능합니다. 또는 Docker 컨테이너에서 리눅스 환경을 실행하세요.


같이 보면 좋은 글