bash 스크립트에서 stdout을 한 줄씩 캡처하는 방법


26

bash 스크립트에서는 긴 명령의 표준 출력을 한 줄씩 캡처하여 초기 명령이 계속 실행되는 동안 분석하고보고 할 수 있습니다. 이것이 내가 상상할 수있는 복잡한 방법입니다.

# Start long command in a separated process and redirect stdout to temp file
longcommand > /tmp/tmp$$.out &

#loop until process completes
ps cax | grep longcommand > /dev/null
while [ $? -eq 0 ]
do
    #capture the last lines in temp file and determine if there is new content to analyse
    tail /tmp/tmp$$.out

    # ...

    sleep 1 s  # sleep in order not to clog cpu

    ps cax | grep longcommand > /dev/null
done

더 간단한 방법이 있는지 알고 싶습니다.

편집하다:

내 질문을 명확히하기 위해 이것을 추가 할 것입니다. longcommand초당 한 번 라인으로 표시 상태 라인. longcommand완료 되기 전에 출력을 잡고 싶습니다 .

이렇게하면 longcommand예상 한 결과를 제공하지 않으면 잠재적으로를 죽일 수 있습니다 .

나는 시도했다 :

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

그러나 whatever(예 echo:)는 longcommand완료 후에 만 실행 됩니다.


답변:


32

명령을 while루프 로 파이프하십시오 . 이것에는 많은 뉘앙스가 있지만 기본적으로 ( bashPOSIX 셸에서)

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

이것에 대한 다른 주요 IFS문제점은 (아래 내용 이외의 ) 루프가 끝나면 루프 내부에서 변수를 사용하려고 할 때입니다. 이는 루프가 실제로는 서브 쉘 (단지 다른 쉘 프로세스)에서 실행되기 때문에 변수에 액세스 할 수 없습니다 (또한 루프가 완료되면 변수가 완전히 사라지는 시점에서 완료 됨). 넌 할 수있어:

longcommand | {
  while IFS= read -r line
  do
    whatever "$line"
    lastline="$line"
  done

  # This won't work without the braces.
  echo "The last line was: $lastline"
}

설정 Hauke의 예 lastpipe에서는 bash다른 솔루션입니다.

최신 정보

'있는 그대로'명령의 출력을 처리하기 위해 stdbuf프로세스 stdout를 라인 버퍼링 하도록 설정할 수 있습니다 .

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

이렇게하면 내부적으로 출력을 블록으로 버퍼링하는 대신 파이프에 한 번에 한 줄씩 쓰도록 프로세스가 구성됩니다. 프로그램은이 설정 자체를 내부적으로 변경할 수 있습니다. unbuffer(의 일부 expect) 또는로 유사한 효과를 얻을 수 있습니다 script.

stdbufGNU 및 FreeBSD 시스템에서 사용할 수 있으며 stdio버퍼링 에만 영향을 미치고 동적으로 연결된 비 setuid, 비 setgid 응용 프로그램 (LD_PRELOAD 트릭을 사용하므로)에서만 작동합니다.


@Stephane에 IFS=필요하지 않습니다 bash. 지난 번에 확인했습니다.
Graeme

2
네 맞습니다. 생략하면 필요하지 않습니다 line(이 경우 $REPLY선행 및 후행 공백을 제거하지 않고 결과가 입력 됨 ). 시도 : echo ' x ' | bash -c 'read line; echo "[$line]"'와 비교 echo ' x ' | bash -c 'IFS= read line; echo "[$line]"'또는echo ' x ' | bash -c 'read; echo "[$REPLY]"'
스테판 Chazelas가에게

@Stephane은 그 변수와 명명 된 변수 사이에 차이가 있다는 것을 결코 깨닫지 못했습니다. 감사.
Graeme

@Graeme 내 질문에 명확하지 않았지만 longcommand가 완료되기 전에 긴 명령이 오류 메시지를 표시하면 신속하게 대응하기 위해 출력을 한 줄씩 처리하고 싶습니다. 더 명확하게하기 위해 질문을 편집하겠습니다.
gfrigon

@gfrigon이 업데이트되었습니다.
Graeme

2
#! /bin/bash
set +m # disable job control in order to allow lastpipe
shopt -s lastpipe
longcommand |
  while IFS= read -r line; do lines[i]="$line"; ((i++)); done
echo "${lines[1]}" # second line
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.