Bash 5.x · GNU make

빌드가 실패했는데 다음 단계가 실행되는 이유

종료 상태를 보존하고, 자식 스크립트의 실패를 부모까지 전달합니다. 셸과 Makefile의 달러 기호도 구분합니다.

$?는 직전 명령의 종료 상태입니다

$?는 직전에 끝난 명령 또는 파이프라인의 종료 상태입니다. 성공은 0이며, 0이 아닌 값의 세부 의미는 프로그램마다 다릅니다. 다른 명령을 실행하면 값이 바뀌므로 필요할 때 바로 저장합니다.

bash -c 'exit 7'
status=$?
printf 'status=%s\n' "$status"
exit "$status"
실행 단계상태의 의미
자식의 exit 7자식이 7을 반환합니다. 이 명령만으로 부모가 종료되지는 않습니다.
status=$?자식의 7을 변수에 저장합니다.
printf로그를 출력합니다. 이 명령 뒤의 $?는 printf의 결과입니다.
exit "$status"저장해 둔 7을 부모의 호출자에게 반환합니다.

반면 $$는 셸의 PID를 나타냅니다. 일반 셸 스크립트의 $$?는 PID 뒤에 물음표가 붙은 문자열이며, 종료 상태를 읽는 문법이 아닙니다.

실패 처리는 호출하는 쪽에도 있어야 합니다

#!/bin/sh
./build-library.sh || exit "$?"
./build-application.sh || exit "$?"
printf '%s\n' 'build completed' 

|| 오른쪽은 왼쪽 명령이 실패한 경우에 실행됩니다. 자식 스크립트도 내부의 make 실패를 0이 아닌 값으로 돌려줘야 합니다. 자식이 실패한 뒤 성공하는 echo를 마지막에 실행하고 끝나면, 자식 전체가 성공으로 보일 수 있습니다.

실패가 전달되는 방향
  1. 컴파일러가 실패make가 실패를 감지합니다.
  2. make가 실패 상태로 종료자식 스크립트가 종료 상태를 검사합니다.
  3. 자식이 실패 상태를 반환부모도 검사하여 다음 빌드를 중단합니다.

위에서 아래로 실패 결과가 호출자에게 전달됩니다. 각 호출 단계에서 반환값을 검사해야 합니다. make의 종료값이 컴파일러의 원래 숫자와 같다는 뜻은 아닙니다.

Makefile은 셸보다 먼저 $를 해석합니다

.PHONY: build
build:
	./build-library.sh || exit "$$?"
	./build-application.sh

레시피 줄의 처음은 탭입니다. GNU make는 $$$ 하나로 바꿔 셸에 전달하므로, 위 셸이 받는 문자열은 exit "$?"입니다. 일반 .sh 파일에 같은 이유로 달러를 두 번 적으면 안 됩니다.

이 예제처럼 각 레시피 줄이 스크립트 호출 하나로 끝난다면 make가 그 실패를 직접 감지하므로 || exit …는 생략해도 됩니다. 기본적으로 레시피 줄마다 별도 셸이 실행됩니다. 앞 줄에서 저장한 셸 변수는 다음 줄로 자동 전달되지 않습니다.

파이프라인은 별도로 확인합니다

#!/usr/bin/env bash
set -o pipefail
./build-library.sh 2>&1 | tee build.log
status=$?
if [ "$status" -ne 0 ]; then
    exit "$status"
fi

기본 설정에서는 tee가 성공하면 앞의 빌드가 실패했어도 파이프라인 상태가 0일 수 있습니다. Bash의 pipefail을 켜면 실패한 명령 중 가장 오른쪽 명령의 종료 상태를 돌려줍니다. 이 예제는 로그 기록 실패도 실패로 처리합니다.

set -e에는 if 조건, || 목록 등 종료하지 않는 경우가 있습니다. 무조건 모든 오류에서 종료한다고 가정하지 말고, 중단해야 하는 단계는 반환값 처리까지 명시합니다.

확인한 문서

Bash 5.x · GNU make