在进程运行时重定向标准输出——该进程向 /dev/null 发送了什么

在进程运行时重定向标准输出——该进程向 /dev/null 发送了什么

我已经运行了一个命令并通过它重定向了输出,> /dev/null
现在它的运行时间比我预期的要长得多,我想看看它在做什么。

有没有办法重新重定向输出,以便将所有新内容打印到stdout?我意识到所有以前的内容都消失了。

答案1

您可以使用 strace 来完成此操作。

使用strace它可以监视写入文件描述符 1(即 stdout 文件描述符)的内容。以下是示例:

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

这一个在hello1chmod +x hello1

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

这一个在hello2chmod +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 使用系统调用 将输出发送到 fd1 write(1, …。内核接收系统调用write(1, …,看到 fd 1 已连接到/dev/null并且……

然后我们在 hello 上运行 strace(系统调用跟踪),并看到它正在调用write(1, "hello\nworld\n") 其余部分,如果上面的行只是选择跟踪的适当行。

答案2

我找这个问题的答案很久了。主要有两种解决方案:

  1. 正如您在此处所述,strace 选项;
  2. 使用 gdb 获取输出。

就我而言,没有一个方法令人满意,因为第一个方法会截断输出(而且我无法将其设置得更长)。第二个方法也不行,因为我的平台没有安装 gdb - 它是一个嵌入式设备。

在互联网上收集了一些部分信息(我并没有创建这些信息,只是将各个部分拼凑在一起),我使用命名管道 (FIFO) 找到了解决方案。当进程运行时,其输出被定向到命名管道,如果没有人想看到它,则会对其应用一个愚蠢的侦听器 (tail -f >> /dev/null) 来清空缓冲区。当有人想要获取此输出时,tail 进程将被终止(否则输出将在读取器之间交替),然后我会 cat 管道。侦听完成后,另一个 tail 启动。

所以我的问题是启动一个进程,退出 ssh shell,然后再次登录并能够获取输出。现在可以使用以下命令完成此操作:

#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;

答案3

不可以。您必须重新启动该命令。

Stdio 句柄是从父进程继承到子进程的。您已为子进程提供了 /dev/nul 的句柄。它可以随意处理它,包括对其进行 dup() 或将其传递给自己的子进程。没有简单的方法可以进入操作系统并更改另一个正在运行的进程的句柄指向的内容。

可以说,您可以在子进程上使用调试器并开始改变其状态,用新内容覆盖其存储当前句柄值副本的任何位置,或者跟踪其对内核的调用,监控任何 i/o。我认为这对大多数用户来说要求太高了,但如果它是一个不对 i/o 进行任何奇怪的操作的单个子进程,那么它可以工作。

但即使在一般情况下,这也失败了,例如,创建管道等的脚本,复制句柄并创建大量来来去去的子代。这就是为什么你几乎只能从头开始(也许重定向到一个你可以稍后删除的文件,即使你现在不想看它。)

答案4

您可以使用重新重定向程序:

reredirect -m <file> <PID>

您可以稍后使用以下命令恢复流程的初始输出:

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

<M>并由<N>之前启动的重新重定向提供)。

reredirectREADME还解释了如何重定向到另一个命令或仅重定向 stdout 或 stderr。

相关内容