아래 코드는 Linux 시스템에서 실행 중인 간단한 bash 스크립트이며 각 출력 사이의 시간 간격이 왜 다음과 같은지 알고 싶습니다.4개 두번째바꾸다여덟?
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:27 AM +07
Sun 11 Apr 2021 12:42:31 AM +07
Sun 11 Apr 2021 12:42:35 AM +07
후자의 시간 값을 약간 더 늘리더라도 각 출력 사이의 시간은 여전히 4초입니다.
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:44 AM +07
Sun 11 Apr 2021 12:42:48 AM +07
Sun 11 Apr 2021 12:42:52 AM +07
이것은 매우 혼란스러운데 누구든지 이것을 설명할 수 있다면 매우 감사하겠습니다.
더욱 혼란스러운 점은 명령을 앞에 놓으면 date
절전 명령이 실행되지 않는 것처럼 보인다는 것입니다.
$ for test in test1 test2 test3; do (date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
답변1
이를 명확히 하기 위해 "hop2"를 에코하기 전과 후에(파이프 우회) stderr에 디버그 출력을 추가하겠습니다.
$ for test in test1 test2 test3; do
(echo ${test}; sleep 4s; echo before hop2 >&2;
echo hop2; echo after hop2 >&2; sleep 4s; echo hop3) | date;
done
Sat Apr 10 11:29:46 PDT 2021
before hop2
Sat Apr 10 11:29:50 PDT 2021
before hop2
Sat Apr 10 11:29:54 PDT 2021
before hop2
알아채다echo after hop2 >&2
절대 실행하지 마세요, 그리고 그 뒤에 오는 명령: 두 번째 sleep
및 echo hop3
.
내가 이해한 바로는 이런 일이 일어났습니다. 루프 내에서는 두 개의 개별 프로세스가 병렬로 실행되며 첫 번째 프로세스의 출력이 두 번째 프로세스의 입력으로 연결됩니다. 두 개의 프로세스가 실행됩니다.
echo ${test}
sleep 4s
echo before hop2 >&2
echo hop2
echo after hop2 >&2
sleep 4s
echo hop3
그리고
date
대략적인 실행 순서는 다음과 같습니다(처음 3단계의 정확한 순서와 4단계의 시작은 다소 무작위입니다).
- 프로세스 1이 실행됩니다
echo ${test}
. 이는 "test1"(및 개행 문자)을 파이프에 기록하고 나중에 읽을 수 있도록 버퍼링됩니다. - 프로세스 2를 실행
date
하고 현재 날짜를 터미널에 인쇄합니다. - 프로세스 2가 종료되고 파이프 끝이 닫힙니다.
- 프로세스 1이 실행됩니다
sleep 4s
. - 4초 후에 프로세스 1이 실행되어
echo before hop2 >&2
"before hop2"를 터미널에 인쇄합니다. - 공정 1노력하다실행
echo hop2
하지만 파이프의 유일한 판독기가 파이프를 닫았기 때문에 SIGPIPE 오류가 발생합니다. 이로 인해 분명히 전체 하위 쉘 프로세스(echo
명령뿐만 아니라)가 종료됩니다.
이는 echo
쉘이 내장되어 있기 때문에 발생합니다. /bin/echo hop2
(쉘의 echo
내장 명령이 아닌 외부 명령) 을 사용하면 sleep
예상대로 두 번째 명령이 실행됩니다.
그건 그렇고, 이것은 다른 쉘에서 상대적으로 일관됩니다. bash, zsh, dash 및 ksh(대화형)에서 동일한 결과를 얻었습니다. 스크립트의 ksh는 계속하기 전에 프로세스 1이 종료될 때까지 기다리지 않는다는 점에서 약간 다릅니다. 따라서 모든 date
s가 즉시 실행되고 그 뒤에 일련의 "hop2 이전" 줄(4초 후)이 이어집니다.
답변2
(echo ${test}; ...) | date
여기서 무엇을 하고 싶나요? 데이터를 표준 입력으로 전송 중이지만 date
입력 date
을 읽지 않습니다. 날짜만 인쇄하고 종료됩니다.
을 종료한 후 date
파이프가 닫히고 나중에 인쇄된 모든 데이터는 더 이상 갈 곳이 없으며 파이프(서브쉘 (echo; sleep; echo; sleep)
)에 쓰는 프로세스는 SIGPIPE 신호를 수신하고 종료됩니다.
이것이 파이프라인이 일반적으로 작동하는 방식입니다. 만약 왼쪽이 많은 양의 출력을 생성할 수 있다면, 신호는 오른쪽이 흥미를 잃은 후에 멈추라고 지시하는 것입니다.
예를 들어 비슷한 상황에서는 cat /dev/zero | head -c128 > /dev/null
신호가 결국 종료되어 cat
무기한으로 유지되지 않습니다. 쉘이나 쉘 모두 cat
오류 메시지를 인쇄하지 않습니다. 이런 방식으로 작동하는 파이프는 정상적인 작동의 일부일 뿐입니다. 지금의 상황도 마찬가지다. (어떤 경우에는 오류 메시지가 표시되지만 항상 오류 메시지가 표시될 것이라고 기대하지는 마세요.)
(외부 루프는 결과에 영향을 주지 않으므로 생략했습니다. 이를 이용하여 time ( ... ) | ...
파이프라인에 걸리는 시간을 측정할 수 있습니다.)
Bash에서는 배열의 파이프라인에 있는 명령의 종료 상태를 확인할 수 있습니다 PIPESTATUS
.
$ ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
date; declare -p PIPESTATUS
Sat Apr 10 21:34:56 EEST 2021
declare -a PIPESTATUS=([0]="141" [1]="0")
SIGPIPE는 적어도 Linux에서는 13번이므로 표시된 종료 상태 141 = 128 + 신호 번호와 일치합니다. (프로세스는 상태 >= 128로 정상적으로 종료될 수도 있지만 여기서는 그렇지 않습니다.)
또는 strace
어떤 일이 일어나는지 확인할 수 있습니다.
$ strace -f bash -c '( echo ${test}; sleep 4s; echo hop2;
sleep 50s; echo hop3; ) | date'
...
[pid 31647] write(1, "hop2\n", 5) = -1 EPIPE (Broken pipe)
[pid 31647] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=31647, si_uid=1000} ---
[pid 31647] +++ killed by SIGPIPE +++
...
반면에 파이프 오른쪽에 있는 입력을 읽는 경우 SIGPIPE가 없으며 전체 파이프가 총 54초 동안 절전 모드로 유지됩니다.
$ time ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
cat > /dev/null
real 0m54.005s
user 0m0.000s
sys 0m0.000s
또는 쓰기 측에서 SIGPIPE를 무시하는 경우:
$ time ( trap '' PIPE; echo foo; sleep 4s; echo hop2; sleep 10s;
echo hop3; ) | date
Sat Apr 10 21:57:07 EEST 2021
bash: echo: write error: Broken pipe
bash: echo: write error: Broken pipe
real 0m14.006s
user 0m0.004s
sys 0m0.000s
SIGPIPE가 무시되면 쉘은 닫힌 파이프에 쓸 때 주기적으로 오류를 반환하고 이 목적으로 오류가 인쇄됩니다.
왼쪽 파이프에 쓰는 것과 오른쪽 파이프를 닫는 것 사이에는 타이밍 문제도 있습니다.
내가 다음과 같이 하면:
( echo foo; echo bar; sleep 4s; ) | date;
그것은 sleep
달아났다. 그러나 둘 다 교체하면 LHS가 도달하기 전에 종료 echo
됩니다 . (여기서의 경쟁은 시스템에 따라 달라집니다.)for i in {1..100}; do echo foo; done;
sleep
그리고 date
LHS에서 다른 경우를 먼저 고려하십시오.
(date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date
이 역시 타이밍 문제 때문이다. date
외부 명령이기 때문에 이를 실행하는 셸은 내부적으로 처리하는 것보다 느릴 수 있습니다 echo
(결국 모든 셸 옆에 내장된 명령입니다). 이렇게 하면 date
오른쪽에 있는 것이 경주에서 승리하고 첫 번째 쓰기 전에 파이프를 닫을 가능성이 높아 집니다 echo
.
일반적으로 말해서, 파이프에 쓰는 것은 일부 데이터를 상대방에게 전달하는 것이 목적이 아니고 다른 할 일이 있는 경우 가능한 SIGPIPE를 처리해야 하는 경우 그다지 유용하지 않습니다.
답변3
date
표준 입력으로 데이터를 수신하지 않았기 때문에 보낼 것이 없습니다 .
따라서 a를 사용하는 |
것은 완전히 잘못된 것입니다. 나는 당신이 사용해야한다고 생각합니다 &&
.
그러나 문제는 다음과 같습니다.
각 출력 사이의 시간 간격은 무엇입니까?4초바꾸다여덟?
여전히 작동하지만 해석하기가 혼란스럽습니다. 즉, 후속 명령 echo hop2
은 실행되지 않습니다. 두 번째 수면은 사용되지 않았습니다.
$ (echo test; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; date
Sun 11 Apr 2021 05:30:28 AM UTC
Sun 11 Apr 2021 05:30:32 AM UTC
stderr
대신 다음 stdout
을 사용하여 모든 명령을 실행하는 경우 :
$ (echo test >&2; sleep 4s; echo hop2 >&2; sleep 4s; echo hop3 >&2) | date; date
test
Sun 11 Apr 2021 05:32:36 AM UTC
hop2
hop3
Sun 11 Apr 2021 05:32:44 AM UTC
그 이유는 date
stdin에서 입력을 받지 못하고 여기에 쓰려고 하면 SIGPIPE 응답을 받게 되고 전체 (bash) 셸(왼쪽에 있는 것 |
)이 닫히기 때문입니다. 목록에 있는 다른 모든 명령을 삭제합니다.
예제에서 첫 번째 항목이 성공한 이유는 echo
시간에 따라 다릅니다.
- 처음에는 실행 중인 쉘이 파이프를 설정합니다
|
. date
이를 수행하기 위해 launch 명령의 하위 명령을 엽니다.- 아무 것도 기다리지 않고 쉘은 파이프의 다른 쪽 끝으로 돌아가서 (다른 하위 프로세스에서) 실행을 요청합니다
echo ${test}
. - 파이프가 아직 열려 있기 때문에(
date
닫히지 않았음) echo 출력이 버퍼링되고 명령이sleep 4s
실행됩니다. - 이
date
명령에는 파이프를 완료하고 닫는 데 충분한 시간이 있습니다. - 이제 파이프가 닫혀 있으면 다음 파이프
echo
에 쓸 수 없습니다. SIGPIPE가 발생합니다. - SIGPIPE를 받은 후 왼쪽의 하위 쉘이
|
깔끔하게 종료됩니다(에코가 내장되어 있으며 내장 오류로 인해 전체 쉘이 실패하게 됩니다). - 더 이상 파이프에서 명령이 실행되지 않습니다.
그렇기 때문에:
$ (sleep 1; echo test; sleep 4s; echo hop2 >&2; ) | date ; date
Sun 11 Apr 2021 06:10:04 AM UTC
Sun 11 Apr 2021 06:10:05 AM UTC
첫 번째 에코도 인쇄하지 않습니다. 시간은 0.1초 정도로 작을 수 있습니다. 그것은 폐쇄를 허용하기에 충분했습니다 date
.
echo
지연하기에 충분한 외부 명령에 대한 호출일 수도 있습니다 .
(/usr/bin/true; echo test; sleep 4s; echo confirm >&2 ) | date; date
Sun 11 Apr 2021 06:27:01 AM UTC
Sun 11 Apr 2021 06:27:01 AM UTC
루프 for
는 위의 내용을 세 번 반복합니다. 따라서 각 루프 사이에는 단 4초만 있습니다.