web-dev-qa-db-ja.com

プロセスの実行中にstdoutをリダイレクトする-そのプロセスは/ dev / nullに何を送信するか

コマンドを実行し、その出力を> /dev/null経由でリダイレクトしました
予想よりも大幅に長く実行されているので、何が行われているのかを確認したいと思います。

すべての新しいコンテンツがstdoutに出力されるように、出力をリダイレクトする方法はありますか?以前の内容がすべてなくなっていることに気づきました。

9
Mikhail

straceを使用して実行できます。

straceを使用すると、stdoutファイル記述子であるファイル記述子1に書き込まれているものをスパイできます。次に例を示します。

_strace  -p $pid_of_process_you_want_to_see_stdout_of 2>&1 | \
    sed -re 's%^write\(1,[[:blank:]](.*),[[:blank:]]*[0-9]+\)[[:blank:]]*=[[:blank:]]*[0-9]+%\1%g' 
_

フィルタを改善したいと思うかもしれませんが、それは別の質問になります。出力はありますが、今はそれを整理する必要があります。

:警告:このソリューションにはいくつかの制限があります。以下のコメントを参照してください。常に機能するとは限りません。マイレージは異なる場合があります。

テスト:

このプログラム(下記)をファイルhelloに入れ、_chmod +x hello_

_#!/bin/bash

while true
do
    echo -en  "hello\nworld\n"
done
_

これは_hello1_と_chmod +x hello1_にあります

_#!/bin/bash
dir=$(dirname $0)
$dir/hello >/dev/null
_

これは_hello2_と_chmod +x hello2_にあります

_#!/bin/bash
dir=$(dirname $0)
$dir/hello1 >/dev/null
_

次に_./hello2 >/dev/null_を使用して実行し、プロセスhelloのpidを見つけて_pid_of_process_you_want_to_see_stdout_of=xyz_と入力します。ここで、xyzはhelloのpidであり、最上部にある行を実行します。

使い方。 helloが実行されると、bashはフォークし、fd 1を_/dev/null_にリダイレクトしてから、helloを実行します。 Helloは、システムコール_write(1, …_を使用して出力をfd1に送信します。カーネルはシステムコール_write(1, …_を受信し、fd 1が_/dev/null_に接続されていることを確認して…

次にstrace(システムコールトレース)をhelloで実行し、それがwrite(1, "hello\nworld\n")を呼び出していることを確認します。上の行がトレースの適切な行を選択しているだけの場合は残りの部分です。

9
ctrl-alt-delor

私はこの質問に対する答えをかなり長い間探していました。主に2つの解決策があります。

  1. ここで述べたように、straceオプション。
  2. Gdbを使用して出力を取得します。

私の場合、最初に出力を切り捨てたため(そして、それ以上設定できなかったため)、どれも満足のいくものではありませんでした。私のプラットフォームにはgdbがインストールされていないため、2つ目は問題外です。これは、組み込みデバイスです。

インターネット上でいくつかの部分的な情報を収集し(作成せず、断片化するだけで)、名前付きパイプ(FIFO)を使用して解決策を見つけました。プロセスが実行されると、その出力は名前付きパイプに送られ、誰もそれを見たくない場合は、ダムリスナー(tail -f >>/dev/null)がそれに適用されてバッファーが空になります。誰かがこの出力を取得したい場合、テールプロセスが強制終了され(そうでない場合、出力はリーダー間で交互になります)、パイプをキャットします。リスニングフィニッシュすると、別のテールが立ち上がります。

したがって、私の問題は、プロセスを開始し、sshシェルを終了してから、もう一度ログに記録して出力を取得できるようにすることでした。これは、次のコマンドで実行できるようになりました。

#start the process in the first Shell
./runner.sh start "<process-name-with-parameters>"&
#exit the Shell
exit

#start listening in the other Shell
./runner listen "<process-name-params-not-required>"
#
#here comes the output
#
^C

#listening finished. If needed process may be terminated - scripts ensures the clean up
./runner.sh stop "<process-name-params-not-required>"

それを実現するスクリプトを以下に添付します。私はそれが完璧な解決策ではないことを認識しています。あなたの考えを共有してください、多分それは役に立つでしょう。

#!/bin/sh

## trapping functions
trap_with_arg() {
    func="$1" ; shift
    for sig ; do
        trap "$func $sig" "$sig"
    done
}

proc_pipe_name() {
    local proc=$1;
    local pName=/tmp/kfifo_$(basename ${proc%%\ *});
    echo $pName;
}

listener_cmd="tail -f";
func_start_dummy_pipe_listener() {
    echo "Starting dummy reader";
    $listener_cmd $pipeName >> /dev/null&
}

func_stop_dummy_pipe_listener() {
    tailPid=$(func_get_proc_pids "$listener_cmd $pipeName");
    for pid in $tailPid; do
        echo "Killing proc: $pid";
        kill $tailPid;
    done;
}

func_on_stop() {
        echo "Signal $1 trapped. Stopping command and cleaning up";
    if [ -p "$pipeName" ]; then
        echo "$pipeName existed, deleting it";
        rm $pipeName;
    fi;



    echo "Cleaning done!";
}

func_start_proc() {
    echo "Something here"
    if [ -p $pipeName ]; then
        echo "Pipe $pipeName exists, delete it..";
        rm $pipeName;
    fi;
    mkfifo $pipeName;

    echo "Trapping INT TERM & EXIT";
    #trap exit to do some cleanup
    trap_with_arg func_on_stop INT TERM EXIT

    echo "Starting listener";
    #start pipe reader cleaning the pipe
    func_start_dummy_pipe_listener;

    echo "Process about to be started. Streaming to $pipeName";
    #thanks to this hack, the process doesn't  block on the pipe w/o readers
    exec 5<>$pipeName
    $1 >&5 2>&1
    echo "Process done";
}

func_get_proc_pids() {
    pids="";
    OIFS=$IFS;
    IFS='\n';
    for pidline in $(ps -A -opid -ocomm -oargs | grep "$1" | grep -v grep); do
        pids="$pids ${pidline%%\ *}";
    done;
    IFS=$OIFS;
    echo ${pids};
}

func_stop_proc() {
    tailPid=$(func_get_proc_pids "$this_name start $command");
    if [ "_" == "_$tailPid" ]; then
        echo "No process stopped. The command has to be exactly the same command (parameters may be ommited) as when started.";
    else
        for pid in $tailPid; do
            echo "Killing pid $pid";
            kill $pid;
        done;
    fi;
}

func_stop_listening_to_proc() {
    echo "Stopped listening to the process due to the $1 signal";
    if [ "$1" == "EXIT" ]; then
        if [ -p "$pipeName" ]; then
            echo "*Restarting dummy listener"; 
            func_start_dummy_pipe_listener;
        else 
            echo "*No pipe $pipeName existed";
        fi;
    fi;
}

func_listen_to_proc() {
    #kill `tail -f $pipeName >> /dev/null`
    func_stop_dummy_pipe_listener;

    if [ ! -p $pipeName ]; then 
        echo "Can not listen to $pipeName, exitting...";
        return 1;
    fi;

    #trap the kill signal to start another tail... process
    trap_with_arg func_stop_listening_to_proc INT TERM EXIT
    cat $pipeName;
    #NOTE if there is just an end of the stream in a pipe, we have to do nothing 

}

#trap_with_arg func_trap INT TERM EXIT

print_usage() {
    echo "Usage $this_name [start|listen|stop] \"<command-line>\"";
}

######################################3
############# Main entry #############
######################################

this_name=$0;
option=$1;
command="$2";
pipeName=$(proc_pipe_name "$command");


if [ $# -ne 2 ]; then
    print_usage;
    exit 1;
fi;

case $option in 
start)
    echo "Starting ${command}";
    func_start_proc "$command";
    ;;
listen)
    echo "Listening to ${2}";
    func_listen_to_proc "$command";
    ;;
stop)
    echo "Stopping ${2}";
    func_stop_proc "$command";
    ;;
*)
    print_usage;
    exit 1;
esac;
5
Krzysztow

いいえ。コマンドを再起動する必要があります。

Stdioハンドルは、親プロセスから子プロセスに継承されます。子に/ dev/nulへのハンドルを与えました。 dup()を実行したり、自分の子に渡したりするなど、好きなように自由に使用できます。 OSにアクセスして、実行中の別のプロセスのハンドルが指すものを変更する簡単な方法はありません。

おそらく、子でデバッガーを使用してその状態をザッピングし始め、現在のハンドル値のコピーが格納されている場所を新しいもので上書きしたり、カーネルへの呼び出しをトレースしてI/Oを監視したりできます。それは多くのユーザーに尋ねていると思いますが、I/Oで何もおかしくない単一の子プロセスであれば機能します。

しかし、それでも一般的なケースでは失敗します。たとえば、パイプラインを作成するスクリプトなど、ハンドルを複製し、出入りする独自の子を多数作成します。これが、最初からやり直すことにかなり悩まされている理由です(そして、今は見たくない場合でも、後で削除できるファイルにリダイレクトすることもできます)。

5
Nicole Hamilton

reredirect プログラムを使用してそれを行うことができます:

reredirect -m <file> <PID>

次のようなものを使用して、後でプロセスの初期出力を復元できます。

reredirect -N -O <M> -E <N> <PID>

<M>および<N>は、以前のリダイレクトの起動によって提供されます)。

reredirect READMEでは、別のコマンドにリダイレクトする方法、またはstdoutまたはstderrのみをリダイレクトする方法についても説明しています。

4