7

Я запустил команду и перенаправил ее вывод через > /dev/null
Теперь, когда он работает значительно дольше, чем я ожидал, я хочу посмотреть, что он делает.

Есть ли способ перенаправить вывод, так что все новое содержимое будет напечатано на стандартный stdout? Я понимаю, что все предыдущее содержание ушло.

4 ответа4

8

Вы можете сделать это, используя 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

Это в 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 , затем найдите pid процесса hello и введите pid_of_process_you_want_to_see_stdout_of=xyz где xyz - это pid hello, затем запустите строку сверху.

Как это устроено. Когда запускается hello, bash-форки перенаправляют fd 1 в /dev/null , а затем исполняют hello. Hello отправляет вывод в fd1, используя системный вызов write(1, … . Ядро получает запись системного вызова write(1, … , видит, что fd 1 подключен к /dev/null и…

Затем мы запускаем strace (системный вызов trace) в hello и видим, что он вызывает write(1, "hello\nworld\n") . Остальное, если строка выше просто выбирает соответствующую строку трассировки.

5

Я долго искал ответ на этот вопрос. В основном доступны два решения:

  1. Как Вы указали здесь, опция strace;
  2. Получение вывода с использованием GDB.

В моем случае ни один из них не был удовлетворительным, потому что сначала усекает вывод (и я не мог установить его дольше). Второе не подлежит сомнению, поскольку на моей платформе не установлен gdb - это встроенное устройство.

Собрав некоторую частичную информацию в Интернете (не создавая ее, просто соединив кусочки), я нашел решение, используя именованные каналы (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

Нет. Вам придется перезапустить команду.

Дескрипторы Stdio наследуются от родительского к дочернему процессу. Вы дали ребенку дескриптор /dev /nul. Он может делать с ним все, что захочет, включая такие вещи, как dup() или передавать его своим дочерним элементам. Нет простого способа получить доступ к ОС и изменить то, на что указывают дескрипторы другого запущенного процесса.

Возможно, вы могли бы использовать отладчик на дочернем объекте и начать заменять его состояние, перезаписывая любые места, где хранится копия текущего значения дескриптора, чем-то новым, или отслеживать его обращения к ядру, отслеживая любой ввод-вывод. Я думаю, что этого требует большинство пользователей, но это может сработать, если это один дочерний процесс, который не делает ничего смешного с вводом / выводом.

Но даже в общем случае это не работает, например, сценарий, который создает конвейеры и т.д., Дублирует дескрипторы и создает множество собственных дочерних элементов, которые приходят и уходят. Вот почему вы в значительной степени застряли, начав заново (и, возможно, перенаправив на файл, который вы можете удалить позже, даже если вы не хотите смотреть его сейчас).

2

Вы можете сделать это с помощью программы reredirect :

reredirect -m <file> <PID>

Вы можете восстановить первоначальный вывод вашего процесса позже, используя что-то вроде:

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

(<M> и <N> предоставляются при предыдущем запуске переадресации).

Переадресация README также объясняет, как перенаправить на другую команду или перенаправить только stdout или stderr.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .