빌드노트
grep 종료 코드 1은 미발견이다: 파일 오류 2와 나누기
GNU grep의 종료 1은 검색 미발견이고 파일 오류는 보통 2다. 합성 파일의 표준 출력·표준 오류·종료 상태를 비교하고, Bash에서 세 경로를 나누는 처리와 -q가 파일 오류를 0으로 덮을 수 있는 조건을 확인한다.
grep으로 특정 문자열을 찾는 배치가 검색 결과가 없을 때 멈춘다면, 0이 아닌 종료 코드를 모두 실행 오류로 처리하고 있는지 확인한다. GNU grep은 보통 일치한 줄이 있으면 0, 없으면 1, 오류가 발생하면 2를 반환한다. 1을 정상적인 미발견으로 받아들이는 작업이라면 세 경로를 나눠야 한다. GNU grep 종료 상태 문서 (새 탭에서 열림)
반대로 멈추지 않게 하려고 grep ... || true를 붙이면 실제 파일 오류도 성공으로 바뀐다. 빈 출력만으로는 “문자열이 없다”와 “파일을 읽지 못했다”를 구분할 수 없다.
같은 빈 출력이어도 종료 상태는 다르다
Linux나 WSL에서 GNU grep과 Bash가 필요하다. bash --noprofile --norc로 새 Bash를 열고 아래를 실행한다. 새 임시 폴더에 두 줄짜리 파일 하나만 만든다. set +e는 비교 중 미발견 직후에도 종료 상태를 출력할 수 있게 자동 종료를 끈다. 영어 오류 문구를 비교하기 위해 LC_ALL=C로 고정했다.
set +e
export LC_ALL=C
work=$(mktemp -d)
cd "$work" || exit
printf 'INFO ready\nERROR timeout\n' > sample.txt
grep -F -- 'ERROR' sample.txt
printf 'exit=%s\n' "$?"
grep -F -- 'FATAL' sample.txt
printf 'exit=%s\n' "$?"
grep -F -- 'ERROR' missing.txt
printf 'exit=%s\n' "$?"
Ubuntu 24.04.4의 GNU grep 3.11, Bash 5.2.21에서 각각 표준 출력과 표준 오류를 따로 수집해 확인했다. 실제 배치나 시스템 로그를 검색한 결과는 아니다.
ERROR검색: grep의 표준 출력은ERROR timeout한 줄, 표준 오류는 비어 있었고 종료 코드는 0이었다.FATAL검색: 표준 출력과 표준 오류가 모두 비었고 종료 코드는 1이었다. 파일은 읽었지만 찾는 문자열이 없었다.- 없는
missing.txt검색: 표준 출력은 비었지만 표준 오류에grep: missing.txt: No such file or directory가 있었고 종료 코드는 2였다.
코드의 exit=... 줄은 grep의 출력이 아니라 바로 뒤 printf가 출력한 값이다. 실행 환경에 따라 오류 문구가 달라도 파일 오류를 미발견으로 바꾸면 안 된다. 2가 나오면 먼저 경로와 접근 권한 등 표준 오류가 가리키는 원인을 확인한다. 일치한 줄이 일부 출력됐더라도 다른 입력 파일에서 오류가 나면 2일 수 있다.
배치에서는 1만 미발견 경로로 보낸다
아래는 Bash 스크립트에 넣을 처리부다. pattern과 file을 실제 검색 문자열과 파일 경로로 바꾼다. 앞에서 만든 파일에 그대로 실행하면 FATAL이 없으므로 NOT_FOUND를 출력한다. -F는 정규식 대신 고정 문자열을 찾는 선택 (새 탭에서 열림)이다. 결과 줄이 필요 없어서 표준 출력만 버리고 표준 오류는 남겼다.
pattern='FATAL'
file='sample.txt'
if grep -F -- "$pattern" "$file" >/dev/null; then
printf '%s\n' 'FOUND'
else
rc=$?
case "$rc" in
1) printf '%s\n' 'NOT_FOUND' ;;
*) printf 'grep failed: %s\n' "$rc" >&2; exit "$rc" ;;
esac
fi
else에 들어왔다는 사실만으로 미발견이라고 판단하지 않는다. 첫 명령으로 $?를 저장한 뒤 1만 따로 처리한다. 이 예제의 미발견 경로는 메시지를 출력하고 다음 작업으로 진행하며, 나머지 오류는 저장한 상태로 스크립트를 종료한다. 2 외의 비정상 상태까지 미발견으로 삼지 않도록 마지막 분기를 *로 뒀다. 이 처리부를 터미널에 직접 붙이면 파일 오류 시 그 Bash도 종료되므로, 확인용 새 Bash나 배치 스크립트에서 실행한다.
같은 처리부에 입력을 바꿔 실행했을 때, ERROR와 sample.txt는 FOUND·스크립트 상태 0, FATAL과 sample.txt는 NOT_FOUND·상태 0이었다. ERROR와 missing.txt는 원래 파일 오류와 grep failed: 2를 표준 오류에 남기고 상태 2로 끝났다. set -e를 켠 별도 실행에서도 세 결과는 같았다.
Bash는 if의 조건 명령을 0과 그 외로 나누며, 조건 위치의 명령에는 -e의 자동 종료 예외가 적용된다. 그래서 if 안에서 원래 상태를 받아 분류할 수 있다. 비교용으로 set -e 뒤에 미발견 grep을 단독으로 놓았을 때는 바로 상태 1로 끝나 다음 명령이 실행되지 않았다. Bash 조건문 (새 탭에서 열림), set의 -e 규칙 (새 탭에서 열림)
grep ... 2>/dev/null || true로 바꾼 대조군은 없는 파일에서도 출력 없이 상태 0이 됐다. 미발견만 허용하려는 목적에는 이 처리가 너무 넓다.
-q의 0은 모든 파일이 정상이라는 뜻이 아니다
여러 파일을 검색하면서 오류도 놓치면 안 된다면 -q를 별도로 살펴야 한다. GNU grep의 -q 설명 (새 탭에서 열림)은 일치를 찾으면 즉시 0으로 끝나며, 이미 오류를 발견했어도 0이 될 수 있다고 명시한다. 앞의 임시 폴더에서 파일 순서를 바꿔 확인할 수 있다.
grep -q -F -- 'ERROR' missing.txt sample.txt
printf 'exit=%s\n' "$?"
grep -q -F -- 'ERROR' sample.txt missing.txt
printf 'exit=%s\n' "$?"
grep -F -- 'ERROR' missing.txt sample.txt >/dev/null
printf 'exit=%s\n' "$?"
첫 명령은 없는 파일 오류를 표준 오류에 출력했는데도 종료 코드가 0이었다. 그 뒤 sample.txt에서 일치를 찾았기 때문이다. 두 번째도 0이었지만 표준 오류까지 비었다. 첫 파일에서 일치를 찾고 끝나 뒤의 없는 경로를 검사하지 않은 결과다. 두 경우 모두 grep의 표준 출력은 비었다.
세 번째는 -q를 빼고 표준 출력만 버렸다. 같은 파일 오류를 표준 오류에 남겼고 종료 코드는 2였다. 정상 파일을 먼저 놓아도 결과는 같았다. 일치 여부뿐 아니라 이 일반 텍스트 입력들의 파일 오류도 배치에 전달해야 한다면, 앞 처리부처럼 일반 검색의 출력만 리다이렉션하고 상태를 분류한다. 하나라도 일치하는지만 확인하려고 -q를 쓸 때에는 그 0을 전체 입력의 정상 검사로 해석하지 않는다.
이번 오류 재현은 존재하지 않는 경로를 여는 실패다. 읽는 도중 발생하는 디스크 I/O 오류나 권한 거부를 주입한 실험은 아니다. 온라인 근거는 GNU grep 3.12 문서이며 직접 실행한 버전은 3.11이다. 다른 grep 구현, 바이너리 입력, 실제 배치의 동작은 검증하지 않았다. 여기서 바꿀 판단은 단일 검색 명령의 0·1·오류 분류이며, 파이프라인 전체의 상태를 고르는 pipefail과는 별개다.