bash_07 파이프 처리로 각 명령어 종료 상태값 조사하기

 
  • 출처 : 유닉스 리눅스 쉘스크립트 예제사전_한빛미디어

명령어: echo
키워드: 파이프, 종료 스테이터스
사용처: 파이프 처리 중에 어떤 명령어 종료 스테이터스를 확인해서 명령어의 성공/실패를 판단하고 싶을 때


실행예제

$ ./pipestatus.sh
[ERROR] sort-data.sh에 실패했습니다.

스크립트

#!/bin/bash

# 다음과 같은 처리를 하는 경우를 가정
#  script.sh : 데이터 출력
#  sort-data.sh : 데이터 정렬
#  calc.sh : 출력 데이터 계산
./script.sh | ./sort-data.sh | ./calc.sh > output.txt  #------- 1

# 다른 명령어를 실행하면 PIPESTATUS값이 사라지므로
# 결과를 복사해둠
pipe_status=("$PIPESTATUS[@]")  #------------------------------ 2

# 파이프 처리 중에 명령어 성공, 실패 확인
# sort-data.sh 종료 스테이터스가 0이 아닌지 확인
if [ "${pipe_status[1]}" -ne 0 ]; then  #---------------------- 3(if문)
  echo "[ERROR] sort-data.sh에 실패했습니다." >&2
fi

   

해설

이 스크립트는 파이프 처리 중에 에러가 발생했는지 확인해서 에러가 발생하면 에러 메시지를 출력합니다.

여기서는 script.sh -> sort-data.sh -> calc.sh라는 세 스크립트를 순서대로 실행해서 어떤 데이터를 처리한 뒤에 그 결과를 output.txt라는 파일에 출력한다고 가정합니다.

여기서 sort-data.sh는 실패하기도 하는데 그 다음 calc.sh에서 하는 계산은 정렬이 필수가 아니므로 에러 발생 여부와 관계없이 실행 가능하다고 가정합니다. 다만 정렬에 실패하면 종료 스테이터스가 0이 아니므로 이걸 검출해서 에러 메시지만 출력하고 싶다고 합시다.

명령어 종료 스테이터스 $?를 이용하는 예제는 앞에서 많이 다루었지만 파이프 처리는 도중에 명령어 종료 스테이터스를 $?로 취득할 수 없습니다. 변수 $?는 파이프 라인 마지막에 실행된 명령어 종료 스테이터스만 대입되기 때문에 1에서 마지막 calc.sh 종료 스테이터스만 확인할 수 있습니다.

그러나 bash 내장 변수인 PIPESTATUS를 이용하면 직접 파이프라인 처리의 모든 종료 스테이터스를 취득할 수 있습니다. PIPESTATUS는 배열 변수라서 0번에는 파이프 첫번째 종료 스테이터스가, 1번에는 파이프 두 번째 종료 스테이터스가 들어 있습니다.

  • 예제에서 다루는 특수 변수 PIPESTATUS값

    서술 예 설명
    ${PIPESTATUS[0]} 파이프 처리 첫 번째(script.sh) 종료 스테이터스
    ${PIPESTATUS[1]} 파이프 처리 두 번째(sort-data.sh) 종료 스테이터스
    ${PIPESTATUS[2]} 파이프 처리 세 번째(calc.sh) 종료 스테이터스

2의 ${PIPESTATUS[@]}로 사용하는 배열 첨자 @은 배열 전체를 의미합니다. 전체를 ()로 싸서 배열 전체를 셸 변수에 복사합니다.

3은 sort-data.sh 종료 스테이터스를 ${PIPESTATUS[1]}로 취득해서 TEST 명령어의 다른 값인지 확인하는 -ne 연산자로 0과 비교합니다. 종료 스테이터스가 0이 아니면 에러 메시지를 표시합니다.

이렇게 해서 파이프 처리 중에 명령어 종료 스테이터스를 취득할 수 있습니다. 세세한 에러 처리를 하고 싶을 때 사용하면 되겠습니다.

## PIPESTATUS 이용 시 주의점

변수 PIPESTATES를 이용할 때 주의해야 하는 것은 어떤 명령어를 실행할 때마다 변수 PIPESTATUS가 늘 변경된다는 점입니다. 예를 들어 다음은 파이프 처리하는 세 명령어 종료 스테이터스를 echo 명령어로 확인하는(하려고 했던) 스크립트 입니다. 하지만 이 스크립트는 잘못 작성되었습니다.

  • 파일1 잘못된 PIPESTATUS 사용 예(list2.sh)

    #!/bin/bash
    
    ./script.sh | ./sort-data.sh | ./calc.sh > output.txt
    
    echo ${PIPESTATUS[0]}
    echo ${PIPESTATUS[1]}  #---------A
    echo ${PIPESTATUS[2]}  #---------B
    

이걸 실행하면 AB의 echo 명령어에 아무것도 표시되지 않습니다.

  • 실행 결과

    $ ./list2.sh
    0
    

이것은 파이프를 사용하지 않는 명령어 처리에서도 변수 PIPESTATUS는 0개의 파이프 처리로 보고 늘 변경되기 때문입니다. 따라서

A에서는 직전의 echo 명령어의 두 번째 파이프 처리 종료 스테이터스를 얻으려고 하는데 그런 처리가 존재하지 않으므로 공백문자열을 출력합니다.

B도 마찬가지입니다. 이렇듯 어떤 명령어를 실행할 때마다 PIPESTATUS는 덮어쓰기로 변경됩니다. 따라서 예제에서는 PIPESTATUS를 일단 셸 변수 pipe_status에 그대로 복사합니다.

   

주의사항

  • 셸 변수 PIPESTATUS는 sh에는 없는 bash 고유 셸 변수 입니다. 그 외에도 몇 가지 bash 고유 셸 변수가 있는데 그 중 몇가지를 소개합니다.

  • bash 고유의 편리한 셸 변수

    셸 변수명 의미
    BASH 현재 실행하고 있는 bash를 실행할 때의 전체 경로
    DIRSTACK 디렉터리 스택 내용(배열 변수)
    SHEELOPTS 유효한 셸 옵션(쉼표로 구분)
    SECONDS 셸이 실행된 초
    HOSTNAME 호스트명
    UID 셸을 실행한 사용자 ID
    GROUPS 현재 사용자가 속한 그룹 목록(배열 변수)

    그 외에도 다양한 bash 셸 변수가 있으므로 man bash로 확인해보기 바랍니다.