Bash 명령 그룹화와 파이프라인 상태를 결합하는 방법

Bash 명령 그룹화와 파이프라인 상태를 결합하는 방법

Bash 명령 그룹화와 파이프라인 상태를 결합하는 방법은 무엇입니까?

다음은 명령 그룹의 예입니다.

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; } 3>&1 | gzip --rsyncable > my_file.tar.gz


다음은 위와 일치하는 파이프라인 상태 읽기의 예입니다.

[[ ${PIPESTATUS[*]} =~ [1-9] ]] && rm my_file.tar.gz


이 예에서 명령 그룹은 cron을 통해 전달된 "tar에서 선행 / 제거"에 대한 경고에서 메일 풀을 제외합니다. 이는 stderr에 속하기 때문입니다(Unices에는 stdwarn이 부족하고 tar에는 Quiet 옵션이 부족함). 실제 오류는 그대로 둡니다.

파이프 상태 판독은 손상된 백업 파일이 즉시 삭제되도록 보장하여 나중에 표준 FIFO 알고리즘을 사용하여 정리하는 동안 유효한 오래된 파일이 삭제되는 것을 방지합니다.

하지만 이 예는 작동하지 않습니다. 위의 파이프라인 상태에는 grep 및 gzip의 종료 코드인 [1 0]이 포함되어 있지만 tar는 포함되어 있지 않습니다.

내가 시도한 한 가지 시도는 다음과 같습니다.

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; GROUPSTATUS=${PIPESTATUS[*]}; } 3>&1 | gzip --rsyncable > my_file.tar.gz
[[ $GROUPSTATUS =~ [1-9] ]] && rm my_file.tar.gz

하지만 그룹을 탈퇴한 후에는 GROUPSTATUS가 비어 있습니다.

(GROUPSTATUS를 일부 리터럴과 같은 파이프라인 상태 이외의 것으로 설정하여 이 변수가 실제로 일반적인 상황에서 명령 그룹화 범위를 이스케이프하는지 확인할 수 있습니다.)

return또한 첫 번째 파이프라인 구성 요소의 종료 코드를 그룹 내에서 외부로 전달할 수 있는지 시도했지만 명령 그룹 내로 반환하면 bash에서 오류 메시지가 생성됩니다.

답변1

파이프라인을 실행하면 파이프로 구분된 각 요소가 자체 프로세스에서 실행됩니다. 변수 할당은 자체 프로세스 내에서만 적용됩니다. ksh 및 zsh에서 파이프의 마지막 요소는 원래 셸에서 실행됩니다. 다른 셸(예: bash)에서는 각 파이프 요소가 자체 하위 셸에서 실행되고 원래 셸은 모두 완료될 때까지 기다립니다.

$ bash -c 'GROUPSTATUS=foo; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is foo
$ bash -c 'GROUPSTATUS=foo | :; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is 

귀하의 경우 성공적인 모든 명령에만 관심이 있으므로 상태 코드가 위쪽으로 흐르도록 할 수 있습니다.

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2;
  ! ((PIPESTATUS[0])); } 3>&1 |
gzip --rsyncable > my_file.tar.gz;
if ((PIPESTATUS[0] || PIPESTATUS[1])); then rm my_file.tar.gz; fi

파이프 왼쪽에서 8비트 이상의 정보를 원할 경우 다른 파일 설명자에 쓸 수 있습니다. 다음은 원리 증명의 예입니다.

{ { tar …; echo $? >&4; } | …; } | { gzip …; echo $? >&4; } \
  4>&1 | ! grep -vxc '0'

stdout에 데이터가 있으면 명령 대체를 사용하여 이를 쉘 변수에 넣을 수 있습니다. 즉, $(…)명령 대체는 명령의 stdout에서 읽으므로 스크립트의 stdout에도 내용을 인쇄하려는 경우 임시로 전달해야 합니다. 파일 설명자. 다음 코드 조각은 fd 3을 사용하여 스크립트의 stdout에서 끝나는 내용을 나타내고 fd 4를 사용하여 캡처된 내용을 나타냅니다 $statuses.

statuses=$({ { tar -v … >&3; echo tar $? >&4; } | …; } |
           { gzip …; echo gzip $? >&4; } 4>&1) 3>&1

다른 명령의 출력을 다른 변수로 캡처해야 하는 경우 bash, ksh 또는 zsh와 같은 "고급" 셸에도 직접적인 방법이 없다고 생각합니다. 다음은 몇 가지 해결 방법입니다.

  • 임시 파일을 사용하십시오.
  • 단일 출력 스트림을 사용하십시오. 예를 들어 각 줄에는 원본을 나타내는 접두사가 있고 최상위 수준에서 필터링됩니다.
  • Perl이나 Python과 같은 고급 언어를 사용하세요.

답변2

이 문제를 해결하는 쉬운 방법은 my_folder를 쉘 변수에 넣고 앞의 슬래시를 제거하는 것입니다.

tar -cf - ${my_folder##/} | gzip --rsyncable > my_file.tar.gz

그렇지 않으면 블록 내부의 ${PIPESTATUS[0]}를 확인하고 0이 아닌 경우 false를 사용하여 외부 프로세스에 오류를 알릴 수 있습니다.

{ tar -cf - my_folder 2>&1 1>&3 | 
    grep -v "Removing leading" 1>&2; 
    [ ${PIPESTATUS[0]} -eq 0 ] && true || false; } 3>&1 | 
    gzip --rsyncable > my_file.tar.gz

관련 정보