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

Mik*_*ail 9 linux operating-systems bash

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

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

ctr*_*lor 9

你可以使用 strace 来做到这一点。

使用strace您可以窥探写入文件描述符 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' 
Run Code Online (Sandbox Code Playgroud)

您可能想要改进过滤器,但这将是另一个问题。我们有输出,但现在需要整理它。

:警告:此解决方案有一些限制,请参阅下面的评论。它并不总是有效,您的里程可能会有所不同。

测试:

把这个程序(下面)放在文件中hello,然后chmod +x hello

#!/bin/bash

while true
do
    echo -en  "hello\nworld\n"
done
Run Code Online (Sandbox Code Playgroud)

这个在hello1chmod +x hello1

#!/bin/bash
dir=$(dirname $0)
$dir/hello >/dev/null
Run Code Online (Sandbox Code Playgroud)

这个在hello2chmod +x hello2

#!/bin/bash
dir=$(dirname $0)
$dir/hello1 >/dev/null
Run Code Online (Sandbox Code Playgroud)

然后用 运行./hello2 >/dev/null,然后找到进程 hello 的 pid 并键入pid_of_process_you_want_to_see_stdout_of=xyz其中 xyz 是 hello 的 pid,然后在顶部运行 line。

这个怎么运作。当 hello 运行时,bash 分叉,将 fd?1 重定向到/dev/null,然后执行 hello。Hello 使用系统调用将输出发送到 fd1 write(1, …。内核收到系统调用write(1, …,看到 fd 1 连接到/dev/null……

然后我们在 hello 上运行 strace (system-call tr​​ace),write(1, "hello\nworld\n") 如果上面的行只是选择了跟踪的适当行,就会看到它正在调用其余部分。


Nic*_*ton 5

不,您必须重新启动命令。

Stdio 句柄从父进程继承到子进程。您已经为孩子提供了 /dev/nul 的句柄。它可以随心所欲地使用它,包括像 dup() 或将它传递给它自己的孩子之类的事情。没有简单的方法可以进入操作系统并更改另一个正在运行的进程的句柄指向的内容。

可以说,您可以在子进程上使用调试器并开始改变它的状态,用新的东西覆盖它存储当前句柄值副本的任何位置,或者跟踪它对内核的调用,监视任何 I/O。我认为这对大多数用户来说是有问题的,但如果它是一个对 i/o 不做任何有趣的事情的单个子进程,它就可以工作。

但即使在一般情况下,这也会失败,例如,创建管道等的脚本,重复句柄并创建许多自己的来来去去的孩子。这就是为什么您几乎无法从头开始(并且可能重定向到以后可以删除的文件,即使您现在不想看它。)

  • 我知道它是如何工作的。我是 Hamilton C shell 的作者。http://hamiltonlabs.com/cshell.htm (4认同)
  • @nicole Hamilton 我是答案的作者,这很有效。 (4认同)

小智 5

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

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

在我的情况下,它们都不是令人满意的,因为首先截断了输出(并且我无法将其设置得更久)。第二个是没有问题的,因为我的平台没有安装 gdb - 它是一个嵌入式设备。

在互联网上收集了一些部分信息(没有创建它,只是将碎片放在一起),我使用命名管道(FIFO)找到了解决方案。当进程运行时,它的输出被定向到命名管道,如果没有人想看到它,一个哑监听器 (tail -f >> /dev/null) 被应用到它以清空缓冲区。当有人想要获得这个输出时,尾部进程被终止(否则输出在读取器之间交替),然后我就截取了管道。听完后,另一条尾巴开始了。

所以我的问题是启动一个进程,退出 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>"
Run Code Online (Sandbox Code Playgroud)

完成该任务的脚本附在下面。我知道这不是一个完美的解决方案。请分享您的想法,也许它会有所帮助。

#!/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;
Run Code Online (Sandbox Code Playgroud)