Devin.KR
로그인

리눅스 patch 명령어 사용법 - 패치 적용·되돌리기와 -p 옵션 이해하기

개발자 조회 1

이 명령어를 언제 쓰는가

diff 가 만든 변경 내역을 실제 파일에 적용할 때 쓴다. git 이 있는 환경에서는 git apply 가 대신하지만, git 이 없는 운영 서버에서는 여전히 patch 가 유일한 수단이다.

실무에서 마주치는 상황은 대체로 이렇다 — 한 서버에서 설정을 고쳐 검증한 뒤 같은 수정을 다른 서버 열 대에 적용해야 할 때, 오픈소스 프로젝트가 배포한 보안 패치를 소스에 적용해야 할 때, 그리고 적용한 변경을 안전하게 원복해야 할 때다. 마지막 것이 patch 의 진짜 가치다. -R 한 번이면 정확히 되돌아간다.

기본 형식

patch [옵션] [대상파일] < 패치파일
  • 패치 파일은 표준 입력으로 넣는 것이 관례다. -i 패치파일 로 지정할 수도 있다.
  • 대상 파일을 생략하면 패치 안의 ---/+++ 줄에 적힌 경로를 보고 스스로 찾는다.
  • 패치는 현재 디렉터리를 기준으로 경로를 해석한다. 반드시 대상 소스 트리 최상단에서 실행한다.

자주 쓰는 옵션

옵션의미예시
-pN패치 안 경로에서 앞의 N개 디렉터리를 떼고 찾는다patch -p1 < fix.patch
--dry-run / -C실제로 쓰지 않고 적용 가능한지만 확인한다patch -p1 --dry-run < fix.patch
-R패치를 거꾸로 적용한다. 원복용patch -R -p1 < fix.patch
-b원본을 .orig 로 백업한 뒤 적용한다patch -b app.conf < fix.patch
-N이미 적용된 패치는 건너뛴다. 재실행 안전장치patch -N -p1 < fix.patch
-i 파일패치 파일을 인자로 지정한다patch -p1 -i fix.patch
-t질문하지 않고 기본 답을 가정한다. 단독으로 쓰면 이미 적용된 패치를 거꾸로 적용하므로 -N 과 함께 쓴다patch -t -N -p1 < fix.patch
-l공백 차이를 느슨하게 본다. 최후의 수단patch -l -p1 < fix.patch

-p 옵션 이해하기

patch 를 처음 쓰는 사람이 가장 많이 막히는 지점이다. 패치 파일 안에 이런 헤더가 있다고 하자.

--- a/src/config/nginx.conf
+++ b/src/config/nginx.conf
  • -p0 — 경로 전체를 그대로 쓴다. a/src/config/nginx.conf 를 찾는다.
  • -p1 — 앞 1단계를 뗀다. src/config/nginx.conf 를 찾는다.
  • -p2config/nginx.conf 를 찾는다.

git 이 만든 패치는 항상 a/·b/ 접두사가 붙으므로 -p1 이 정답이다. 반대로 같은 디렉터리에서 diff -u old new 로 직접 만든 패치는 경로에 디렉터리가 없으니 -p0 이다. 실패하면 can't find file to patch 가 뜨고 대화형으로 파일 이름을 묻는다.

실전 예제

1) 적용 전에 반드시 확인한다. 운영 서버에서 이 단계를 건너뛰면 안 된다.

patch -p1 --dry-run < security-fix.patch
patching file src/handler.c

여기서 Hunk #2 FAILED 같은 줄이 나오면 실제 적용은 하지 않는다. 성공했을 때만 --dry-run 을 빼고 다시 실행한다.

2) 백업을 남기며 적용한다. 되돌릴 길을 두 개 확보하는 것이다.

patch -b -p1 < security-fix.patch
ls *.orig

3) 적용한 패치를 되돌린다. 배포 후 문제가 생겼을 때 가장 빠른 원복 수단이다.

patch -R -p1 < security-fix.patch

패치 파일만 보관하고 있으면 원본 백업이 없어도 정확히 이전 상태로 돌아간다.

4) 설정 변경을 여러 서버에 배포한다. 한 서버에서 만든 diff 를 그대로 다른 서버들에 적용하는 전형적인 흐름이다.

cd /etc/nginx && diff -u nginx.conf.orig nginx.conf > /tmp/ssl.patch
for h in web02 web03 web04; do
  scp /tmp/ssl.patch "$h":/tmp/
  ssh "$h" 'cd /etc/nginx && sudo patch -p0 --dry-run -i /tmp/ssl.patch'
done

전 서버에서 dry-run 이 통과한 것을 확인한 뒤에 --dry-run 을 뺀 명령을 다시 돌린다.

5) 스크립트에서 자동으로 적용한다. 대화형 질문이 뜨면 배포가 멈춘 채로 매달려 있게 된다.

if patch -t -N -p1 -i fix.patch; then
  echo "패치 적용 완료"
else
  echo "패치 실패, 롤백 필요" >&2
  exit 1
fi

함정과 주의점

이미 적용된 패치를 다시 적용하면 대화형 프롬프트에서 멈춘다. patch 는 이렇게 묻는다.

Reversed (or previously applied) patch detected!  Assume -R? [y]

여기서 무심코 엔터를 치면 y 가 기본값이라 패치가 거꾸로 적용된다. 방금 적용한 수정이 도로 풀린다는 뜻이다. 스크립트라면 표준 입력이 패치 파일이라 예측 불가능하게 동작한다. -N(이미 적용된 것은 건너뜀)과 -t(질문 금지)를 같이 주는 것이 안전하다. 다만 이 조합으로 재실행하면 패치를 거꾸로 적용하지는 않는 대신 종료 코드 1과 .rej 파일을 남긴다.

$ patch -t -N -p0 -i fix.patch app.conf
patching file app.conf
Ignoring previously applied (or reversed) patch.
1 out of 1 hunks ignored--saving rejects to app.conf.rej

실패한 조각은 .rej 파일로 남는다. 패치의 일부만 적용되고 나머지는 파일명.rej 에 떨어진다. 이때 파일은 반쯤 수정된 상태다. 그대로 두면 문법 오류가 난 설정 파일로 서비스가 재기동되는 사고가 난다. .rej 를 발견하면 즉시 판단해야 한다 — 내용을 보고 수동으로 반영하거나, .orig 백업으로 되돌린다.

find . -name '*.rej' -o -name '*.orig' | head

적용이 끝난 뒤 이 명령으로 잔여물이 없는지 확인하는 것을 습관으로 삼는다.

디렉터리 위치를 틀리면 엉뚱한 파일을 고친다. patch 는 상대 경로로 파일을 찾으므로, 잘못된 디렉터리에서 -p 값을 낮춰 가며 시도하다 보면 동명의 다른 파일에 적용될 수 있다. --dry-run 출력의 patching file ... 경로가 의도한 파일인지 매번 확인한다.

줄 끝 문자와 공백 차이로도 실패한다. CR 이 섞인 파일에 CR 없는 패치를 적용하면 전부 실패한다. tr -d '\r' 로 양쪽 형식을 맞추는 것이 정석이고, -l 로 공백을 무시하는 것은 마지막 수단이다. -l 은 의도치 않은 위치에 붙을 위험이 있다.

구현마다 옵션이 다르다. macOS 에 기본 탑재된 patch 는 GNU patch 2.0 계열 포크(patch 2.0-12u11-Apple)라서 최신 GNU patch 2.7 의 일부 옵션이 없다. Linux 배포판은 대부분 GNU patch 2.7 이다. 스크립트를 옮길 때 patch --version 부터 확인한다.

함께 보면 좋은 명령어

  • diff — 패치 파일을 만드는 쪽. diff -u 로 만들고 patch 로 적용하는 것이 한 세트다.
  • cp — 패치 전에 원본을 복사해 두는 것이 -b 보다 확실할 때가 많다.
  • find — 적용 후 남은 .rej·.orig 잔여물을 찾아내는 데 쓴다.