백그라운드에서 실행 중인 프로세스에서 stderr을 읽는 방법은 무엇입니까?

백그라운드에서 실행 중인 프로세스에서 stderr을 읽는 방법은 무엇입니까?

데몬을 백그라운드로 보내고 데몬이 다음 행 중 특정 행을 stderr에 출력할 때만 스크립트 실행을 계속하고 싶습니다.

# Fictional daemon
{
    for x in {1..9}; do
        sleep 1
        if [ "$x" != "5" ]; then
            echo $x 1>&2
        else
            echo now 1>&2
        fi
    done
} &

# Daemon PID
PID="$!"

# Wait until the particular output... 
until { read -r line && grep -q "now" <<<"$line"; } do 
    sleep 1
done </proc/$PID/fd/2

#
# Do more stuff...
#

fg

답변1

다음과 mydaemon같이 동작합니다.

#! /bin/sh -
i=1 stage=init
while true; do
  if [ "$i" -eq 5 ]; then
    echo >&2 ready
    stage=working
  fi
  echo >&2 "$stage $i"
  sleep 1
  i=$((i + 1))
done

즉, 초기화하는 데 4초가 걸리며, ready준비가 되면 출력되고 이후 작업은 모두 동일한 프로세스에서 계속됩니다. 다음 start-mydaemon과 같은 스크립트를 작성할 수 있습니다.

#! /bin/sh -
DAEMON=./mydaemon
LOGFILE=mydaemon.log
umask 027

: >> "$LOGFILE" # ensures it exists
pid=$(
  sh -c 'echo "$$"; exec "$0" "$@"' tail -fn0 -- "$LOGFILE" | {
    IFS= read -r tail_pid
    export LOGFILE DAEMON
    setsid -f sh -c '
      echo "$$"
      exec "$DAEMON" < /dev/null >> "$LOGFILE" 2>&1'
    grep -q ready
    kill -s PIPE "$tail_pid"
  }
)
printf '%s\n' "$DAEMON started in process $pid and now ready"
$ time ./start-mydaemon
./mydaemon started in process 230254 and now ready
./start-mydaemon  0.01s user 0.01s system 0% cpu 4.029 total
$ ps -fjC mydaemon
UID          PID    PPID    PGID     SID  C STIME TTY          TIME CMD
chazelas  230254    6175  230254  230254  0 10:28 ?        00:00:00 /bin/sh - ./mydaemon

start-mydaemonmydaemon가 준비되었음을 표시할 때까지 반환되지 않습니다 . 여기서 mydaemon의 stdout 및 stderr은 로그 파일로 이동하고, stdin은 에서 리디렉션됩니다 /dev/null. setsid -f(표준 명령은 아니지만 대부분의 Linux 배포판에 있음)은 데몬이 터미널에서 분리되도록 보장해야 합니다(터미널에서 시작된 경우).

그러나 mydaemon초기화에 실패하고 를 쓰지 않은 채 종료 되면 해당 스크립트는 절대 오지 않을(또는 다음에 성공적으로 시작될 때 올) ready를 영원히 기다립니다 .readymydaemon

또한 sh -c ...tail및 데몬이 동시에 시작된다는 점에 유의하세요. mydaemon이미 초기화되어 ready시간이 시작되고 인쇄된 경우 tail로그 파일의 끝을 검색하면 메시지가 tail누락됩니다 ready.

다음과 같은 방법으로 이러한 문제를 해결할 수 있습니다.

#! /bin/sh -
DAEMON=./mydaemon
LOGFILE=mydaemon.log
export DAEMON LOGFILE
umask 027

died=false ready=false canary_pid= tail_pid= daemon_pid=
: >> "$LOGFILE" # ensures it exists
{
  exec 3>&1
  {
    tail -c1 <&5 5<&- > /dev/null # skip to the end synchronously
    (
      sh -c 'echo "tail_pid=$$" >&3; exec tail -fn+1' |
        { grep -q ready && echo ready=true; }
    ) <&5 5<&- &
  } 5< "$LOGFILE"
  setsid -f sh -c '
    echo "daemon_pid=$$" >&3
    exec "$DAEMON" < /dev/null 3>&- 4>&1 >> "$LOGFILE" 2>&1' 4>&1 |
      (read anything; echo died=true) &
  echo "canary_pid=$!"
} | {
  while
    IFS= read -r line &&
      eval "$line" &&
      ! "$died" &&
      ! { [ -n "$daemon_pid" ] && "$ready" ; }
  do
    continue
  done
  if "$ready"; then
    printf '%s\n' "$DAEMON started in process $daemon_pid and now ready"
  else
    printf >&2 '%s\n' "$DAEMON failed to start"
  fi
  kill -s PIPE "$tail_pid" "$canary_pid" 2> /dev/null
  "$ready"
}

꽤 복잡해지기 시작했지만. 또한 tail -fLinux에서는 이제 stdin에서 작동하므로 파일에서 새 데이터를 사용할 수 있는 시기를 감지하기 위해 inotify를 사용하지 않고 일반적인 방법을 사용합니다.매초마다 확인ready이는 로그 파일에서 감지하는 데 최대 1초가 더 걸릴 수 있음을 의미합니다 .

답변2

...
done </proc/$PID/fd/2

그것은 당신이 생각하는 대로 작동하지 않습니다.

표준 오류는 $PID다음 중 하나입니다.

  • 제어 tty, 이 경우 사용자가 입력한 문자열을 읽으려고 시도할 것입니다.표준입력~의$PID
  • 파이프 - 이미 파이프를 읽고 있는 사람과 경쟁하게 되어 완전히 엉망이 됩니다.
  • /dev/null-- EOF!
  • 다른 것 ;-) ?

실행 중인 프로세스에서 파일 설명자를 다른 곳으로 리디렉션하는 해킹 방법만 있으므로 가장 좋은 방법은 입력 대기 코드를 cat >/dev/null백그라운드에서 실행되도록 다운그레이드하는 것입니다.

예를 들어, 데몬이 출력할 때까지 "대기"합니다 4.

% cat /tmp/daemon
#! /bin/sh
while sleep 1; do echo $((i=i+1)) >&2; done

% (/tmp/daemon 2>&1 &) | (sed /4/q; cat >/dev/null &)
1
2
3
4
%

그 후에는 쉘 제어 외부 /tmp/daemon에 계속해서 쓰기를 수행합니다 .cat >/dev/null &

또 다른 해결책은 데몬의 stderr을 일반 파일로 리디렉션하는 것입니다 tail -f. 하지만 그러면 데몬은 계속해서 디스크를 쓰레기로 채울 것입니다. rm파일을 사용하더라도 파일이 차지하는 공간은 데몬이 실행될 때까지 해제되지 않습니다. 닫습니다) 이는 리소스가 부족한 사람이 cat배회하는 것보다 훨씬 더 나쁩니다.

물론 가장 좋은 방법은 초기화 후 자체적으로 배경을 설정하고 표준 파일 설명자를 닫고 오류 인쇄 등에 /tmp/daemon사용하는 실제 데몬으로 작성하는 것입니다.syslog(3)

답변3

다른 답변에는 파일이나 명명된 파이프 사용이 포함됩니다. 당신도 필요하지 않습니다. 간단한 파이프로 충분합니다.

#!/bin/bash

run_daemon() {
    # Fictional daemon
    {
        for x in {1..9}; do
            sleep 1
            if [ "$x" != "5" ]; then
                echo $x 1>&2
            else
                echo now 1>&2
            fi
        done
    }
}

run_daemon 2>&1 | (

    exec 9<&0 0</dev/null

    while read -u 9 line && test "$line" != now
    do
        echo "$line"    ## or ## :
    done
    echo "$line"        ## or ##
    cat /dev/fd/9 &     ## or ## cat /dev/fd/9 >/dev/null &

    #fictional stuff
    for a in 1 2 3
    do
        echo do_stuff $a
        sleep 1
    done
    echo done

    wait
)

표시된 섹션은 ## or ##데몬의 출력이 혼합되어 표시되지 않는 경우 대안입니다. 고양이는 출력 시 데몬이 SIGPIPE를 얻는 것을 방지하는 유일한 것이므로 생략할 수 없습니다.

입력을 다른 파일 설명자( exec)로 리디렉션하지 않으려고 시도했지만 그렇지 않으면 입력이 닫히고 데몬이 종료됩니다.

또한 내가 명시적으로 배경을 지정하지 않은 데몬을 만들었다는 점에 주목하실 수도 있습니다. 파이프를 사용하면 필요하지 않습니다. wait악마가 아니라 고양이를 기다립니다 . 데몬이 백그라운드인 경우에도 여전히 작동합니다.

wait와 유사 fg하지만 콘솔을 제어하지는 않습니다. (또한 훨씬 더 오래되었습니다.)

답변4

mkfifo필요한 것을 할 수 있습니다. 이 답변을 참조하세요: https://stackoverflow.com/questions/43949166/reading-value-of-stdout-and-stderr

대신 FIFO를 사용하세요

기술적으로 /dev/stdout 및 /dev/stderr은 실제로 FIFO나 명명된 파이프가 아닌 파일 설명자입니다. 내 시스템에서는 실제로 /dev/fd/1 및 /dev/fd/2에 대한 심볼릭 링크일 뿐입니다. 해당 설명자는 일반적으로 TTY 또는 PTY에 연결됩니다. 따라서 여러분이 하려는 방식으로는 실제로 그 내용을 읽을 수 없습니다.

이 코드는 귀하가 찾고 있는 작업을 수행합니다(처리 단계를 제거했습니다). 읽는 루프에서는 수면이 필요하지 않습니다. fifo에서 코드 읽기가 중지되면 데몬은 코드가 가득 찰 때까지 코드 쓰기를 계속 시도하고 fifo에서 무언가를 읽을 때까지 데몬은 차단됩니다.

fifo="daemon_errors"
{
    mkfifo $fifo
    for x in {1..9}; do
        sleep 1
        if [ "$x" != "5" ]; then
            echo $x 1>&2
        else
            echo now 1>&2
        fi
    done 2>$fifo
} &

sleep 1 # need to wait until the file is created

# Read all output 
while read -r line; do
    echo "just read: $line"
done < $fifo

관련 정보