종료되지 않은 이전 명령이 특정 문자열을 인쇄한 후에 명령을 실행하는 방법은 무엇입니까?

종료되지 않은 이전 명령이 특정 문자열을 인쇄한 후에 명령을 실행하는 방법은 무엇입니까?

commandA다음 인쇄를 수행하고 싶습니다 .

line1
line2
line3
started successfully
line4
...
...

해당 줄이 인쇄 되면 started successfully다른 명령을 시작하고 싶습니다(그리고 출력을 다중화하여 둘 다 터미널에 인쇄되도록 할 수도 있습니다).

Bash에서 어떻게 이를 달성할 수 있나요?

답변1

다음 방법 중 하나로 이 작업을 수행할 수 있습니다.

$ outputfile=output.log
$ commandA | tee "$outputfile" & tail --pid=$! -n+1 -f "$outputfile" |grep -q "started successfully" && commandB; wait

설명하다:

  1. tee명령을 사용하여 STDOUT 및 $outputfile.
  2. 백그라운드에서 명령을 실행하면 이제 출력을 추적할 수 있습니다.
  3. tail --pid=$! -n+1 -f "$outputfile"tail- 명령을 실행 $outputfile하고 첫 번째 줄부터 출력을 추적합니다.
    • 이 플래그는 명령이 완료되면 종료되도록 --pid=$!하는 데 필요합니다 .tailcommandA
  4. grep -q "started successfully"- 필요한 문자열이 처음 나타날 때 성공적으로(조용히) 종료됩니다.
  5. && commandB- grep문자열이 발견되면 실행됩니다 commandB.
    • 실행을 보장하는 &&대신 사용해야 합니다.;commandB오직 의 출력에서 ​​문자열이 발견되면 commandA. 이 문자열을 쓰지 않고 완료되는 경우 commandA(예: 실패하는 경우) 두 번째 명령을 실행하고 싶지 않습니다.
  6. wait- 이전에 완료된 경우 commandB셸로 돌아가기 전에 commandA실행이 계속될 때까지 기다립니다 .commandA
    • 또는 이전에 완료된 경우 포그라운드에서 계속 실행 하는 fg대신 실행할 수 있습니다.waitcommandAcommandB

고쳐 쓰다

tail@PaulPazderski의 답변에서 영감을 받아 로그 파일과 명령이 없는 또 다른 방법이 있습니다 .

$ commandA | tee >(grep -q "started successfully" && commandB; cat >/dev/null)

유일한 단점은 commandA완료 되면앞으로 commandB, 쉘은 완료되자마자 프롬프트로 돌아가며 commandA, commandB백그라운드에서 계속 실행되고 터미널에 출력을 쓸 수도 있습니다.

이 문제를 해결하려면 최종 출력을 다른 명령으로 파이프하면 됩니다. 예를 들어 catstdout에 아무 것도 기록되지 않은 경우에만 쉘이 프롬프트로 돌아가도록 합니다(이는 전체 명령이 완료되었음을 의미함 commandB).

$ commandA | tee >(grep -q "started successfully" && commandB; cat >/dev/null) | cat

답변2

다음과 같은 "트랙 출력" 스크립트를 사용해 보겠습니다.

#!/bin/bash

while IFS= read -r line; do
  echo "$line"
  if [[ "$line" == "started successfully" ]]; then
    do_something
    # optional exit but that could disrupt commandA with a SIGPIPE
  fi
done

그리고 전화해

commandA | track-output

또는 tee대체를 사용하고 처리하며 스크립트에 에코 라인이 없는 경우

commandA | tee >(track-output)

답변3

가능한:

{
  commandA 3<&- | perl -pe '
    exec "commandB <&3 3<&-" if !$pid && /started successfully/ && ($pid = fork) == 0;
    END {if ($pid) {wait; exit($? & 127 ? $? + 128 : $? >> 8)}}'
} 3<&0

bash 및 GNU 대신 zsh를 사용하는 경우 tee비슷한 작업을 수행할 수 있지만 commandB종료 상태가 손실된다는 경고가 표시됩니다.

{
  commandA 3<&- |
    tee -p >(grep -q 'started successfully' && commandB <&3 3<&-)
} 3<&0

이는 대기 시간이 없다는 점을 제외하면 bash에서도 작동합니다(항상 포크를 최적화하지 않는 exec앞에 commandBas를 추가 해야 하지만 ).bashcommandB

두 가지 방법 모두 및 의 표준 입력이 방해받지 않도록 commandA주의 합니다 . stdout은 원본에서 파이프로 변경됩니다. 이는 파이프를 살펴봐야 하는 경우 필요하지만 해당 출력은 또는 에 의해 전달 됩니다 . 표준 출력은 중단되지 않습니다.commandBcommandAstarted successfullyperl -pteecommandB

일부 명령에 주의하세요완충기stdout이 터미널 장치가 아닐 때의 출력입니다. 따라서 commandA여기의 출력은 다음과 같습니다.관로started successfully, 큰 청크의 일부로만 인쇄되고 commandB가능한 한 빨리 시작되지 않을 수 있습니다.

다음을 사용하여 해당 출력을 의사 터미널에 연결하기 시작 zsh하면 이 문제를 해결할 수 있습니다 .zptycommandA

zmodload zsh/zpty

# start commandA with only stdout going to the pseudo-terminal
# stdin and stderr restored.
zpty A 'command A <&3 3<&- 2>&4 4>&-' 3<&0 4>&2

pid=
while zpty -r A line; do
  print -rn - $line
  if (( ! pid )) && [[ $line = *"started successfully" ]]; then
    { commandB <&3 3<&- & } 3<&0
    pid=$!
  fi
done
(( ! pid )) || wait $pid

답변4

아래 코드는 "Some text"를 인쇄한 command2 후 실행됩니다 command1(일치하는 grep이 종료되므로). 그 후 출력 command1command2표준 출력으로 인쇄됩니다.

command1 | tee >(grep -o -m1 "Some text"; command2 )

관련 정보