실패한 솔루션 1

실패한 솔루션 1

Linux에서는 파이프라인을 실행할 수 있습니까?

cmd1 | cmd2

이런 식으로:

  1. cmd2cmd1완전히 완료될 때까지 실행을 시작하지 않습니다.

  2. cmd1오류가 있는 경우 cmd2전혀 실행되지 않으며 파이프라인의 종료 상태는 입니다 cmd1.

예를 들어 이 파이프라인을 만드는 방법은 다음과 같습니다.

false | echo ok

아무것도 인쇄하지 않고 0이 아닌 상태를 반환하시겠습니까?


실패한 솔루션 1

set -o pipefail

파이프라인의 종료 상태는 0이 아니지만 cmd2실패하더라도 계속 실행됩니다.cmd1

해결 방법 2 실패

cmd1 && cmd2

이것은 파이프라인이 아닙니다. I/O 리디렉션이 없습니다.

해결 방법 3 실패

mkfifo /tmp/fifo
cmd1 > /tmp/fifo && cmd2 < /tmp/fifo

차단됩니다.

최적이 아닌 솔루션

touch /tmp/file
cmd1 > /tmp/file && cmd2 < /tmp/file

이것은 작동하는 것 같습니다. 그러나 여기에는 몇 가지 단점이 있습니다.

  1. I/O 속도가 느린 디스크에 데이터를 씁니다. (물론 당신은 사용할 수 있습니다임시 파일 시스템그러나 이는 추가적인 시스템 요구 사항입니다.)

  2. 임시 파일 이름을 신중하게 선택해야 합니다. 그렇지 않으면 실수로 기존 파일을 덮어쓸 수 있습니다.임시 테이블도움이 될 수 있지만 이름이 지정되지 않은 파이프는 이름 지정 노력을 완전히 줄여줍니다.

  3. 임시 파일이 있는 파일 시스템은 전체 데이터를 저장할 만큼 크지 않을 수 있습니다.

  4. 임시 파일은 자동으로 정리되지 않습니다.

답변1

출력 크기는 모르지만 cmd1파이프는 알고 있습니다.버퍼 크기가 제한되어 있습니다.. 특정 양의 데이터가 파이프에 기록되면 누군가가 파이프를 읽을 때까지 모든 후속 쓰기가 차단됩니다(실패한 솔루션 3과 유사).

차단하지 않는 것이 보장되는 메커니즘을 사용해야 합니다. 매우 큰 데이터의 경우 임시 파일을 사용하십시오. 그렇지 않고 데이터를 메모리에 보관할 수 있는 능력이 있다면(결국 파이프의 아이디어임) 다음을 사용하세요.

result=$(cmd1) && cmd2 < <(printf '%s' "$result")
unset result

여기의 결과는 cmd1변수에 저장됩니다 result. cmd1성공 하면 cmd2에서 데이터를 실행하고 제공합니다 result. 마지막으로 result설정을 해제하여 관련 메모리를 확보하세요.

참고: 이전에는 여기에 문자열( <<< "$result")을 사용하여 데이터를 제공 했지만 cmd2Stéphane Chazelas는 이로 인해 bash원하지 않는 임시 파일이 생성되는 것을 관찰했습니다.

댓글의 질문에 답해 주세요.

  • 예, 명령을 연결할 수 있습니다무작위의:

    result=$(cmd1) \
    && result=$(cmd2 < <(printf '%s' "$result")) \
    && result=$(cmd3 < <(printf '%s' "$result")) \
    ...
    && cmdN < <(printf '%s' "$result")
    unset result
    
  • 아니요, 위 솔루션은 다음과 같은 이유로 이진 데이터에 적합하지 않습니다.

    1. 명령 대체는 $(...)후행 줄 바꿈을 먹습니다.
    2. \0명령 대체 결과에서 NUL 문자( )의 동작은 지정되지 않습니다(예: Bash는 이를 삭제합니다).
  • 예, 바이너리 데이터와 관련된 이러한 모든 문제를 방지하려면 다음과 같은 인코더 base64(또는 uuencodeNUL 문자와 후행 줄 바꿈만 처리하는 집에서 만든 인코더)를 사용할 수 있습니다.

    result=$(cmd1 > >(base64)) && cmd2 < <(printf '%s' "$result" | base64 -d)
    unset result
    

    여기서는 종료 값을 변경하지 않고 >(...)유지하기 위해 프로세스 대체( )를 사용해야 합니다 .cmd1

즉, 데이터가 디스크에 기록되지 않도록 하는 것은 상당히 번거로운 것 같습니다. 중간 임시 파일이 더 나은 솔루션입니다. 바라보다스티븐의 대답이는 이에 대한 대부분의 우려를 해결합니다.

답변2

파이핑 명령의 요점은 명령을 동시에 실행하고 다른 명령의 출력을 읽는 것입니다. 순차적으로 실행하고 파이프 비유를 유지하는 경우 첫 번째 명령의 출력을 버킷으로 파이프(저장)한 다음 해당 버킷을 다른 명령으로 비워야 합니다.

그러나 이를 수행하기 위해 파이프를 사용한다는 것은 첫 번째 명령에 대해 두 개의 프로세스(명령과 파이프의 다른 쪽 끝에서 출력을 읽어 버킷에 저장하는 또 다른 프로세스)가 있고 두 번째 명령에 대해 두 개의 프로세스(하나는 파이프를 한쪽 끝으로 비우는 명령)은 다른 쪽 끝에서 읽습니다.

버킷의 경우 메모리나 파일 시스템이 필요합니다. 메모리 확장이 잘 되지 않으므로 파이프가 필요합니다. 파일 시스템이 더 의미가 있습니다. 그것이 /tmp목적입니다. 훨씬 나중(임시 파일이 삭제된 후)까지 데이터가 플러시되지 않을 수 있고, 플러시하더라도 여전히 메모리(캐시)에 남아 있을 수 있으므로 디스크에서는 데이터를 볼 수 없습니다. 그렇지 않다면 애초에 데이터가 너무 커서 메모리에 들어갈 수 없을 것입니다.

임시 파일은 항상 셸에서 사용됩니다. 대부분의 쉘에서 여기의 문서와 여기의 문자열은 임시 파일을 사용하여 구현됩니다.

존재하다:

cat << EOF
foo
EOF

대부분의 쉘은 임시 파일을 생성하고, 쓰기 및 읽기를 위해 열고, 삭제하고, stdin으로 채운 foo다음, cat열린 fd에서 복사한 stdin으로 읽기 위해 실행합니다. 파일은 가득 차기도 전에 삭제됩니다(이는 파일에 기록된 내용이 정전 후에도 유지될 필요가 없다는 단서를 시스템에 제공합니다).

여기서도 같은 작업을 수행할 수 있습니다.

tmp=$(mktemp) && {
  rm -f -- "$tmp" &&
    cmd1 >&3 3>&- 4<&- &&
    cmd2 <&4 4<&- 3>&-
} 3> "$tmp" 4< "$tmp"

그러면 파일이 처음부터 삭제되기 때문에 정리에 대해 걱정할 필요가 없습니다. 버킷 안팎으로 데이터를 가져오는 데 추가 프로세스가 필요하지 않으며 cmd1자체적 cmd2으로 수행할 수 있습니다.

출력을 메모리에 저장하려면 셸을 사용하는 것은 좋은 생각이 아닙니다. 단 zsh, 첫 번째 셸은 변수에 임의의 데이터를 저장할 수 없습니다. 어떤 형태의 인코딩을 사용해야 합니다. 그런 다음 해당 데이터를 전달하기 위해 여기 문서나 여기 문자열을 사용할 때 디스크에 쓰지 않으면 결국 메모리에 여러 번 복사하게 됩니다.

perl예를 들어 다음을 사용할 수 있습니다.

 perl -MPOSIX -e '
   sub status() {return WIFEXITED($?) ? WEXITSTATUS($?) : WTERMSIG($?) | 128}
   $/ = undef;
   open A, "-|", "cmd1" or die "open A: $!\n";
   $out = <A>;
   close A;
   $status = status;
   exit $status if $status != 0;

   open B, "|-", "cmd2" or die "open B: $!\n";
   print B $out;
   close B;
   exit status'

답변3

이것은 서로 다른 도구를 함께 결합하는 솔직히 끔찍한 버전입니다.moreutils:

chronic sh -c '! { echo 123 ; false ; }' | mispipe 'ifne -n false' 'ifne echo ok'

여전히 원하는 것이 아닙니다. 실패하면 1을 반환하고 그렇지 않으면 0을 반환합니다. 그러나 첫 번째 명령이 성공하지 않으면 두 번째 명령을 시작하지 않고 첫 번째 명령이 유효한지 여부에 따라 실패 또는 성공 코드를 반환하며 파일을 사용하지 않습니다.

보다 일반적인 버전은 다음과 같습니다.

chronic sh -c '! '"$CMD1" | mispipe 'ifne -n false' "ifne $CMD2"

이는 세 가지 추가 유틸리티 도구를 함께 제공합니다.

  • chronic실패하지 않는 한 명령을 자동으로 실행합니다. 이 경우 성공/실패 결과를 되돌릴 수 있도록 첫 번째 명령을 실행하는 셸을 실행하고 있습니다. 그러면 명령이 자동으로 실행됩니다.만약에실패, 성공하면 마지막에 출력을 인쇄합니다.
  • mispipe두 명령을 함께 파이프하여 첫 번째 명령의 종료 상태를 반환합니다. 이는 와 비슷한 효과를 갖습니다 set -o pipefail. 이러한 명령은 구별할 수 있도록 문자열로 제공됩니다.
  • ifne표준 입력이 비어 있지 않거나 표준 입력이 비어 있으면 프로그램을 실행합니다 -n. 우리는 그것을 두 번 사용합니다:

    • 첫 번째는 입니다 ifne -n false. 이는 false입력이 다음과 같은 경우에만 실행되어 종료 코드로 사용됩니다.비었다( chronic먹는다는 뜻, cmd1실패한다는 뜻)

      입력이 비어 있지 않으면 실행되지 않고 falselike 를 통해 입력을 전달하고 cat0으로 종료됩니다. 출력은 다음 명령으로 파이프됩니다 mispipe.

    • 두 번째는 입니다 ifne cmd2. 이는 cmd2입력이 다음과 같은 경우에만 실행됩니다.비어 있지 않습니다. 이 입력은 의 출력이며 ifne -n false, 의 출력이 chronicnull이 아닌 경우(명령이 성공할 때 발생) 출력도 null이 아닙니다.

      입력이 비어 있으면 cmd2실행되지 않고 ifne0으로 종료됩니다. mispipe어쨌든 종료 값은 삭제됩니다.


이 접근 방식에는 (적어도) 두 가지 단점이 있습니다.

  1. 앞에서 언급했듯이 실제 종료 코드가 손실되어 cmd1부울 true/false로 축소됩니다. 종료 코드가 의미가 있으면 손실됩니다. 명령 내에서 코드를 파일에 저장하고 sh필요한 경우 나중에 다시 로드할 수 있습니다.ifne -n sh -c 'read code <FILENAME ; rm -f FILENAME; exit $code'
  2. 성공 했지만 cmd1출력이 생성되지 않으면 어쨌든 모든 것이 충돌합니다.

게다가, 그것은 분명하지 않은 의미를 가지고 조심스럽게 인용된 여러 개의 다소 드문 명령이 함께 엮여 있는 것입니다.

답변4

cmd1 | cmd2다음과 같이 파이프라인을 실행합니다.

cmd2cmd1완전히 완료될 때까지 실행을 시작하지 마세요.

이는 일반적으로 불가능합니다. 읽다파이프(7)이게 생각나네파이프라인 용량이 제한되어 있습니다.(보통 4Kbytes 또는 64Kbytes) 그들은 일부를 사용합니다.핵심버퍼의 메모리.

따라서 출력이 cmd1파이프로 들어갑니다. 가득 차면 어떤쓰기(2)cmd1to에 의한 완료는 ( 매우 특이한 표준 출력에 대한 비차단 I/O를 처리하기 위해 특별히 코딩되지 STDOUT_FILENO않은 경우) 차단됩니다 .cmd1cmd2독서(2)그 파이프의 다른 쪽 끝에서. 시작하지 않으면 cmd2결코 일어나지 않을 것입니다.

이런 책을 꼭 읽어보시길 권합니다고급 Linux 프로그래밍이것은 자세히 설명되어 있습니다(모두 설명하려면 책 한 권이 필요할 것입니다).

관련 정보