백그라운드에서 실행 중인 프로세스에서 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

즉, 초기화하고, ready준비되면 출력하고, 작업을 계속하는 데 4초가 소요됩니다. 모두 동일한 프로세스에서 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준비가 되었음을 표시할 때까지 반환되지 않습니다. 여기서 mydaemonstdout 및 stderr은 로그 파일로 이동하고, stdin은 /dev/null. setsid -f(표준 명령은 아니지만 대부분의 Linux 배포판에서 사용 가능)에서 데몬이 터미널에서 분리되었는지 확인해야 합니다(터미널에서 시작된 경우).

mydaemon그러나 초기화가 실패하고 를 쓰지 않고 종료 되면 ready스크립트는 ready결코 오지 않을(또는 mydaemon다음에 성공적으로 시작할 때 올) 무언가를 영원히 기다릴 것이라는 점에 유의하십시오 .

또한 sh -c ...tail및 데몬이 동시에 시작된다는 점에 유의하십시오. 초기화하고 처음부터 인쇄하여 mydaemon로그 파일의 끝을 찾으면 메시지가 누락됩니다.readytailtailready

다음을 통해 이러한 문제를 해결할 수 있습니다.

#! /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 -f이제 Linux의 stdin에서 실행되므로 파일에 새 데이터가 있는지 감지하기 위해 inotify를 사용하지 않고 일반적인 방법을 사용합니다.매초마다 확인하세요ready이는 로그 파일에서 감지하는 데 추가 시간이 걸릴 수 있음을 의미합니다 .

답변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 ##혼합 데몬의 출력을 표시하지 않으려면 표시된 섹션이 대안입니다. cat은 데몬이 출력 시 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

관련 정보