포스트

프로세스 찾고 종료하기: pgrep, pkill, pidof, lsof, kill

ps aux | grep의 한계를 이해하고 이름·포트·열린 파일로 대상을 확인한 뒤 안전하게 종료하는 흐름

프로세스 찾고 종료하기: pgrep, pkill, pidof, lsof, kill

서버를 다시 띄우려는데 포트가 이미 사용 중이거나, 오래전에 실행한 작업이 남아 있는 일은 흔합니다. 이때 중요한 것은 명령어를 많이 아는 게 아니라 무엇을 종료할지 먼저 확인하고, 정상 종료를 요청한 뒤, 결과를 검증하는 순서입니다.

이 글은 그 판단과 작업 흐름에 집중합니다. 명령·옵션·시그널의 전체 목록이 필요하다면 devkit Linux Process Cheatsheet를 참고하세요.


ps aux | grep은 부족한가

1
2
3
$ ps aux | grep jekyll
deploy  36785  ... bundle exec jekyll s
deploy  41210  ... grep jekyll          ← grep 자신이 잡힘
  • grep 프로세스 자신이 결과에 섞임
  • 사용자명, 인자, 환경별 출력처럼 의도하지 않은 문자열까지 함께 매칭될 수 있음
  • PID를 사람이 골라 다시 입력하므로 확인과 종료가 분리됨

ps는 프로세스 상태를 넓게 살펴보는 도구이고, grep은 그 출력 문자열을 거릅니다. 즉 “프로세스 이름으로 찾는다”기보다 우연히 그 문자열이 들어간 행을 찾는 방식입니다. 검색 결과가 하나 보였다는 사실만으로 종료 대상을 확정하면 위험한 이유입니다.

전통적인 grep [j]ekyll 트릭도 있습니다. [j]가 정규식 문자 클래스라 grep 명령 자체에는 검색 문자열이 그대로 나타나지 않는 점을 이용합니다. 역사적으로 유용한 요령이지만, 자기 자신만 숨길 뿐 매칭 범위와 종료 대상이 모호한 문제는 해결하지 못합니다.

먼저 질문을 바꾸는 편이 낫습니다.

  • 실행 이름이나 커맨드라인을 알고 있는가? → pgrep
  • 충돌한 포트를 알고 있는가? → lsof
  • 삭제·마운트 해제를 막는 파일을 알고 있는가? → lsof 또는 fuser

pgrep — 이름·커맨드라인으로 PID 찾기

이름을 알고 있다면 pgrep으로 프로세스 목록 자체를 검색합니다. 단순한 이름과 전체 커맨드라인은 구분해야 합니다.

1
2
pgrep -x nginx                 # 실행 이름이 정확히 nginx인 PID
pgrep -fl "jekyll s"           # 전체 커맨드라인에서 찾아 PID와 이름 확인

-f는 실행 이름이 아니라 전체 커맨드라인을 매칭합니다. sudo ./tools/run.sh처럼 찾으려는 문자열이 인자에 들어 있다면 필요하지만, 그만큼 다른 프로세스까지 잡을 가능성도 커집니다. -f 패턴은 짧게 만들기보다 서비스를 구별할 만큼 구체적으로 만들고, 종료 전에 같은 패턴의 조회 결과를 눈으로 확인하는 편이 안전합니다.


pkill — 같은 매칭 문법으로 종료

pkillpgrep과 같은 기준으로 대상을 골라 시그널을 보냅니다. 그래서 편리한 만큼 패턴이 넓으면 여러 프로세스를 한꺼번에 종료할 수 있습니다.

1
2
pgrep -fl "jekyll s"           # 먼저 같은 패턴의 대상 확인
pkill -TERM -f "jekyll s"      # 확인한 대상에 정상 종료 요청

pkill -f bash처럼 광범위한 패턴은 본인의 셸이나 관계없는 작업까지 종료할 수 있습니다. 운영 환경에서는 조회 결과에서 PID와 커맨드라인을 확인한 뒤, 대상이 하나라면 그 PID에 kill을 보내는 방식이 의도를 더 분명하게 남깁니다.


pidof — 정확한 실행파일 이름으로

pidof nginx는 정확한 실행파일 이름으로 PID를 찾는 간결한 리눅스 명령입니다. 다만 macOS에는 기본으로 없고, 결과를 곧바로 kill $(pidof nginx)에 넘기면 여러 PID를 검토하지 않은 채 종료하게 됩니다. 대화형 작업에서는 pgrep -x로 확인 단계를 드러내는 편이 낫습니다.


killall — 이름으로 일괄 종료 (주의)

killall은 이름으로 여러 프로세스를 종료하는 명령이지만, 오래된 운영체제 차이를 알고 있어야 합니다. 리눅스·BSD·macOS에서는 이름이 일치하는 프로세스를 종료하지만, Solaris의 killall은 시스템 종료 과정에서 모든 프로세스에 시그널을 보내는 명령입니다. 이 역사적 차이 때문에 낯선 시스템에서는 피하고, 조회와 종료의 매칭 기준이 이어지는 pgrep·pkill을 쓰는 편이 명확합니다.


lsof — 포트·파일로 역추적

“어떤 프로세스가 4000 포트를 잡고 있지?”라는 상황에서는 이름을 추측하지 말고 충돌한 자원에서 역추적해야 합니다. 재실행된 서비스나 이름이 비슷한 프로세스가 여럿 있어도 실제 포트를 연 주체를 바로 좁힐 수 있습니다.

1
2
lsof -iTCP:4000 -sTCP:LISTEN     # TCP 4000 포트를 연 서버 프로세스
lsof /var/log/syslog             # 이 파일을 열고 있는 프로세스

포트 충돌을 찾을 때 -sTCP:LISTEN을 빼면 클라이언트의 연결까지 결과에 섞일 수 있습니다. PID를 얻은 뒤에는 ps -p <PID> -o pid=,ppid=,user=,etime=,command=처럼 소유자, 실행 시간, 전체 명령을 다시 확인합니다. 포트를 열었다는 사실은 강한 단서지만, 곧바로 종료해도 된다는 허가는 아니기 때문입니다.


fuser — 더 가벼운 포트 점유자 찾기 (리눅스)

1
2
fuser 4000/tcp                   # 4000 포트를 사용하는 PID
fuser /var/log/syslog            # 파일을 사용하는 PID

fuser는 리눅스에서 빠르게 PID만 확인할 때 유용하지만 lsof보다 문맥이 적고 macOS에는 기본으로 없습니다. fuser -k로 조회와 종료를 합칠 수도 있지만, 이 글의 흐름에서는 일부러 분리합니다. 무엇을 찾았는지 확인할 기회를 없애면서 아끼는 한 줄은 안전상의 이득이 없습니다.


kill — 시그널 선택의 의미

1
2
kill -TERM <PID>                 # 정리할 기회를 주는 정상 종료 요청
kill -KILL <PID>                 # 응답하지 않을 때만 강제 종료

기본 kill <PID>TERM을 보냅니다. 프로세스는 이 시그널을 처리하면서 요청을 마무리하고 임시 파일이나 DB 연결을 정리할 수 있습니다. 반면 KILL은 프로세스가 처리하거나 무시할 수 없으므로 그런 정리 기회를 주지 않습니다. 처음부터 -9를 쓰지 않는 이유입니다.


실전 흐름

안전한 흐름은 식별(identify) → TERM → 검증(verify) → KILL입니다. 서버를 다시 띄우려는데 4000 포트가 잡혀 있고, 조회 결과의 PID가 36785라면 다음처럼 진행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1) 자원에서 종료 후보를 식별
lsof -iTCP:4000 -sTCP:LISTEN

# 2) PID의 소유자와 실제 커맨드라인 확인
ps -p 36785 -o pid=,ppid=,user=,etime=,command=

# 3) 정상 종료 시도
kill -TERM 36785

# 4) 기다린 뒤 PID와 포트가 사라졌는지 모두 검증
sleep 5
ps -p 36785 -o pid=,ppid=,user=,etime=,command=
lsof -iTCP:4000 -sTCP:LISTEN

# 5) 같은 프로세스가 여전히 남아 있을 때만 강제 종료
kill -KILL 36785

검증은 단순히 “PID가 아직 있는가”만 보는 단계가 아닙니다. 프로세스가 재시작되었거나 드물게 PID가 재사용될 수도 있으므로, KILL 전에는 커맨드라인과 소유자가 처음 확인한 대상과 같은지 다시 봅니다. 서비스 관리자(systemd 등)가 프로세스를 되살린 상황이라면 개별 PID를 반복해서 죽이기보다 해당 서비스 관리자에서 중지해야 합니다.


대중성·대안

psprocs 같은 도구는 전체 상태를 둘러보거나 트리와 사용량을 탐색할 때 여전히 유용합니다. 다만 종료할 대상을 고르는 질문에는 이름을 아는지, 포트를 아는지, 열린 파일을 아는지에 따라 pgrep·lsof·fuser로 범위를 먼저 좁히는 편이 낫습니다.

결론은 특정 명령 네 개를 외우는 것이 아닙니다. 근거가 있는 기준으로 대상을 식별하고, TERM으로 정리할 시간을 주고, 같은 대상이 남았는지 검증한 뒤에만 KILL을 사용한다. 이 순서를 지키면 명령 한 줄을 줄이는 것보다 훨씬 큰 실수를 막을 수 있습니다. 세부 옵션과 시그널이 필요할 때는 앞서 링크한 devkit Cheatsheet에서 확인하면 됩니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.