コマンドを実行し、その出力を> /dev/null
経由でリダイレクトしました
予想よりも大幅に長く実行されているので、何が行われているのかを確認したいと思います。
すべての新しいコンテンツがstdout
に出力されるように、出力をリダイレクトする方法はありますか?以前の内容がすべてなくなっていることに気づきました。
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")
を呼び出していることを確認します。上の行がトレースの適切な行を選択しているだけの場合は残りの部分です。
私はこの質問に対する答えをかなり長い間探していました。主に2つの解決策があります。
私の場合、最初に出力を切り捨てたため(そして、それ以上設定できなかったため)、どれも満足のいくものではありませんでした。私のプラットフォームには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;
いいえ。コマンドを再起動する必要があります。
Stdioハンドルは、親プロセスから子プロセスに継承されます。子に/ dev/nulへのハンドルを与えました。 dup()を実行したり、自分の子に渡したりするなど、好きなように自由に使用できます。 OSにアクセスして、実行中の別のプロセスのハンドルが指すものを変更する簡単な方法はありません。
おそらく、子でデバッガーを使用してその状態をザッピングし始め、現在のハンドル値のコピーが格納されている場所を新しいもので上書きしたり、カーネルへの呼び出しをトレースしてI/Oを監視したりできます。それは多くのユーザーに尋ねていると思いますが、I/Oで何もおかしくない単一の子プロセスであれば機能します。
しかし、それでも一般的なケースでは失敗します。たとえば、パイプラインを作成するスクリプトなど、ハンドルを複製し、出入りする独自の子を多数作成します。これが、最初からやり直すことにかなり悩まされている理由です(そして、今は見たくない場合でも、後で削除できるファイルにリダイレクトすることもできます)。
reredirect プログラムを使用してそれを行うことができます:
reredirect -m <file> <PID>
次のようなものを使用して、後でプロセスの初期出力を復元できます。
reredirect -N -O <M> -E <N> <PID>
(<M>
および<N>
は、以前のリダイレクトの起動によって提供されます)。
reredirect README
では、別のコマンドにリダイレクトする方法、またはstdoutまたはstderrのみをリダイレクトする方法についても説明しています。