파이프라인 실패에 대한 대시와 동등한 기능이 있습니까?

파이프라인 실패에 대한 대시와 동등한 기능이 있습니까?

쉘 명령을 실행하기 위해 호출 을 실행하는 Perl 스크립트가 있는데 system()여러 명령을 실행하고 명령 간에 데이터를 전송하려고 합니다. (Perl에서) 다음과 같습니다:

system("command1 | command2");

Perl의 system()목적은 /bin/shUbuntu Server 시스템에서 실행되기 때문에 /bin/sh다른 dash많은 쉘과 마찬가지로 대시의 파이프 종료 값은 가장 오른쪽 명령의 종료 값입니다. 이는 다음과 같은 내용이 0(성공적으로) 반환될 것임을 의미합니다.

system("false | true")

/bin/sh내가 사용하고 있던 시스템이 다음과 같은 옵션을 추가하여 쉽게 수정할 bash수 있었습니다 .pipefail

system("set -o pipefail; false | true");

실제로 이것은 내 로컬 Arch 시스템에서 예상대로 작동하며 /bin/sh그 요점은 다음과 같습니다 bash.

$ perl -le '$status = system("false | true"); print $status>>8'
0
$ perl -le '$status = system("set -o pipefail; false | true"); print $status>>8'
1

(예, 이것이 이상해 보인다는 것을 압니다. 그러나 Perl을 모른다면 제 말을 믿으십시오. 실제 종료 상태를 얻으려면 8을 이동해야 합니다.)

그러나 실행될 것으로 예상되는 머신에는 다음이 있으므로 dash동일한 작업이 실패합니다 .

$ perl -le '$status = system("set -o pipefail; false | true"); print $status>>8'
sh: 1: set: Illegal option -o pipefail
2

set -e또한 솔루션이 다음을 사용하고 있지 않습니다.

$ perl -le '$status = system("set -e; false | true"); print $status>>8'
0

파이프를 사용하지 않거나 모듈을 사용하는 등 몇 가지 Perl 해결 방법을 생각할 수 있지만 IPC::System::Simple대시에서 직접 이 작업을 수행하는 더 쉬운 방법이 있을 수 있기를 바랍니다.

dash그렇다면 파이프라인 명령 세트의 종료 상태가 0(성공) 이어야 한다는 것을 알 수 있는 몇 가지 트릭, 트릭 또는 옵션이 있습니까?모두명령은 유효하며 ?의 방식으로 실패한 경우 set -o pipefail0이 아니어야 합니다 .bash

답변1

Dash에서 이 작업을 수행하는 방법은 모르겠지만 /bin/sh완전히 우회할 수는 있습니다.

Perl은 system()프로그래머가 원하는 것이 무엇인지 추측하려고 시도하며 sh -c인수가 하나만 있고 해당 인수에 셸 메타 문자가 포함된 경우에만 이를 사용합니다. 그렇지 않으면 주어진 명령을 직접 실행하고 정확한 인수를 지정할 수 있습니다("간접 개체"를 통해 전달된 셸 argv[0]또는 $0셸 내의 명령 이름 포함).

문서에 따르면:

매개변수 처리는 매개변수 수에 따라 달라집니다. LIST에 여러 인수가 있거나 LIST가 여러 값을 가진 배열인 경우 목록의 첫 번째 요소가 제공하는 프로그램은 목록의 나머지 요소가 제공하는 인수를 사용하여 시작됩니다.

스칼라 인수가 하나만 있는 경우 인수에 셸 메타 문자가 있는지 확인하고, 그렇다면 구문 분석을 위해 전체 인수가 시스템의 명령 셸에 전달됩니다(이는 /bin/sh -cUnix 플랫폼에 있지만 다른 플랫폼에서는 다릅니다).

이 두 가지는 동일한 execve()호출을 수행합니다.

system{"/bin/sh"} ("sh", "-c", 'false |true; echo $?')
system('false |true; echo $?')

즉, 아래와 같이 이 것입니다 strace.

execve("/bin/sh", ["sh", "-c", "false |true; echo $?"], [/* 39 vars */]) = 0

( 비슷하지만 for 를 system("sh", "-c", '...')찾고 , 단지 대신 실행된 셸 에서 전달된다는 점에서 약간 다릅니다 . 큰 차이는 없습니다.)$PATHshsystem("/bin/sh", "-c", '...')/bin/shargv[0]sh

따라서 Perl에서 실행할 수 있습니다:

system("bash", "-o", "pipefail", "-c", 'false |true; echo $?');
# or a bit shorter
system(qw/bash -o pipefail -c/, 'false |true; echo $?');

왕복은 피하고 sh,그리고Bash 명령줄에 대한 연관 큰따옴표입니다.

답변2

매우 추악하지만 실용적인 솔루션은 다음과 같습니다.

system(q{
  exec 3>&1; \
  status1="$(((false 2>&1 1>&3 3>&- 4>&-; echo $? >&4) \
    | true 1>&2 3>&- 4>&-) 4>&1)"; \
  status2=$? \
  [ $status2 != 0 ] && exit $status2; \
  exit $status1
});

백슬래시, 줄 바꿈 및 들여쓰기를 모두 한 줄에 표시하려면 제거할 수 있습니다. 이것은 다음에서 적응되었습니다.유해한 것으로 간주되는 Csh 프로그래밍Paper, § 1d "더 미세한 조합"[Posix 쉘은 할 수 있지만 Csh는 할 수 없는 것].

이는 파일 설명자 3을 열어 stdout을 임시로 저장한 다음 첫 번째 명령을 실행하고 출력 위치를 다시 지정합니다. FD4 및 FD3은 닫히고 FD1(표준 출력)은 FD3으로 이동하며 FD2(표준 오류)는 FD1로 이동합니다. 그런 다음 두 번째 명령을 실행하기 전에 종료 코드를 FD4에 넣습니다. 두 번째 명령은 FD4와 FD3을 닫은 다음 stderr을 FD2에 넣습니다(stderr가 실제로 인쇄한다고 생각합니다. 여기에는 stdout이 포함될 수 있지만 마술을 올바르게 읽지 못할 수도 있습니다). 출력을 인쇄한 후 FD4를 stdout으로 반환한 다음 에 저장하는 것이 안전합니다 $status1. 두 번째 명령의 상태가 채워지기 때문에 할당이 $status2더 간단해집니다 $?. 이제 우리는 그것을 구현하기만 하면 됩니다 pipefail. $status2깔끔한 종료가 아닌 경우 0으로 종료합니다. 그렇지 않으면 $status1저장된 내용에 관계없이 종료됩니다.

$status더 많은 파이프에 대한 새 파일 설명자(5, 6 등)와 변수를 추가했습니다 .

이는 Posix 규정 준수 및 이식성을 제공하지만 Perl에서 더 기본적으로 수행하거나 다음을 사용하는 것이 더 나을 것입니다.아니크의 조언bash를 호출하면(옵션이라고 가정) 비교해 보면 다음과 같이 우아해 보입니다.

system("bash", "-o", "pipefail", "-c", 'false |true; echo $?')

(Perl의 보다 안전한 구현에 대한 주장은 다음을 참조하십시오.일카츄의 답변통화에 대한 세부정보입니다. )

관련 정보