Redirecionar stdout enquanto um processo está em execução - O que é esse processo enviando para / dev / null

7

Eu executei um comando e redirecionei sua saída via > /dev/null
Agora que está funcionando há muito mais tempo do que eu esperava, quero ver o que está fazendo.

Existe uma maneira de re-redirecionar a saída, de modo que todo o novo conteúdo seja impresso em stdout ? Eu percebo que todos os conteúdos anteriores se foram.

    
por Mikhail 12.09.2012 / 15:43

4 respostas

8

Você pode fazer isso usando strace.

Usando strace , você pode espionar o que está sendo gravado no descritor de arquivo 1, que é o descritor de arquivo stdout. Aqui está um exemplo:

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]+%%g' 

Você pode querer melhorar o filtro, mas isso seria outra pergunta. Nós temos a saída, mas agora precisamos arrumar isso.

: AVISO: Esta solução tem algumas limitações, veja os comentários abaixo. Nem sempre funcionará, sua milhagem pode variar.

Teste:

Coloque este programa (abaixo) no arquivo hello e chmod +x hello

#!/bin/bash

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

Este em hello1 e chmod +x hello1

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

Este em hello2 e chmod +x hello2

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

execute com ./hello2 >/dev/null , então encontre pid de processo hello e digite pid_of_process_you_want_to_see_stdout_of=xyz onde xyz é o pid de olá, então corra a linha no topo.

Como funciona. Quando o hello é executado, o bash forks redireciona o fd1 para /dev/null e executa o hello. Olá envia a saída para fd1 usando a chamada do sistema write(1, … . O kernel recebe a chamada do sistema write(1, … , vê que o fd 1 está conectado a /dev/null e…

Em seguida, executamos strace (system-call trace) no olá e vemos que ele está chamando write(1, "hello\nworld\n") O resto, se a linha acima estiver apenas selecionando a linha apropriada do traço.

    
por 14.09.2012 / 01:04
5

Não. Você terá que reiniciar o comando.

Os manipuladores de stdio são herdados do processo pai para filho. Você deu ao filho um identificador para / dev / nul. É livre para fazer o que quiser, incluindo coisas como dup () ou passando para seus próprios filhos. Não há uma maneira fácil de acessar o sistema operacional e mudar para o que as alças de outro processo em execução apontam.

Indiscutivelmente, você poderia usar um depurador no filho e começar a zerar seu estado, sobrescrevendo quaisquer locais onde ele armazenou uma cópia do valor atual do manipulador com algo novo ou para rastrear suas chamadas ao kernel, monitorando qualquer i / o . Eu acho que é pedir a maioria dos usuários, mas pode funcionar se for um processo único filho que não faz nada engraçado com o i / o.

Mas até isso falha no caso geral, por exemplo, um script que cria pipelines e assim por diante, enganando alças e criando muitos de seus próprios filhos que vêm e vão. É por isso que você está praticamente empenhado em começar de novo (e talvez redirecionar para um arquivo que você pode excluir mais tarde, mesmo se você não quiser assisti-lo agora).

    
por 12.09.2012 / 15:48
5

Eu estava procurando a resposta a esta pergunta há bastante tempo. Existem basicamente duas soluções disponíveis:

  1. Como você declarou aqui, opção strace;
  2. Obtendo a saída usando o gdb.

No meu caso, nenhum deles foi satisfatório, porque primeiro trunca a saída (e eu não poderia defini-lo por mais tempo). O segundo está fora de questão, já que minha plataforma não tem o gdb instalado - é um dispositivo embutido.

Coletando algumas informações parciais na Internet (não as criei, apenas coloquei as peças juntas), cheguei à solução usando pipes nomeados (FIFOs). Quando o processo é executado, sua saída é direcionada para o pipe nomeado e, se ninguém quiser vê-lo, um ouvinte mudo (tail -f > > / dev / null) é aplicado a ele para esvaziar o buffer. Quando alguém quer obter esta saída, o processo de cauda é morto (caso contrário, a saída é alternada entre os leitores) e eu cato o tubo. No final da audição, outra cauda é iniciada.

Então, meu problema era iniciar um processo, sair do shell ssh e, em seguida, fazer o log novamente e ser capaz de obter a saída. Isso é possível agora com os seguintes comandos:

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

O script que realiza isso é anexado abaixo. Estou ciente de que não é uma solução perfeita. Por favor, compartilhe seus pensamentos, talvez seja útil.

#!/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;
    
por 14.01.2013 / 12:23
2

Você pode fazer isso usando o programa reredirect :

reredirect -m <file> <PID>

Você pode restaurar a saída inicial do seu processo mais tarde usando algo como:

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

( <M> e <N> são fornecidos pelo lançamento anterior do reredirect).

reredirect README também explica como redirecionar para outro comando ou redirecionar somente stdout ou stderr.

    
por 14.10.2014 / 16:25