Stdout umleiten, während ein Prozess ausgeführt wird - Was sendet dieser Prozess an / dev / null?

9

Ich habe einen Befehl ausgeführt und seine Ausgabe umgeleitet über > /dev/null
Jetzt, da er deutlich länger ausgeführt wird als erwartet, möchte ich sehen, was er tut.

Gibt es eine Möglichkeit, die Ausgabe so umzuleiten, dass alle neuen Inhalte gedruckt werden stdout? Mir ist klar, dass alle vorherigen Inhalte weg sind.

Mikhail
quelle
+1 gute Frage, ich hätte gedacht, Sie könnten etwas mit / proc / <pid> / fd / 1 sein, aber es hat bei mir nicht funktioniert.
Strg-Alt-Delor
Welches Betriebssystem? unter der Annahme von Gnu / Linux.
Strg-Alt-Delor
Ja, insbesondere Ubuntu
Mikhail
3
Vielleicht funktionieren diese für Sie? Etwas andere Situation.
Daniel Beck
Daniel, danke. Ich bin mir nicht sicher, ob es funktioniert, nachdem die Ausgabe umgeleitet wurde, aber ich werde es versuchen und Sie wissen lassen!
Mikhail

Antworten:

9

Sie können es mit strace tun.

Mit können straceSie ausspionieren, was in den Dateideskriptor 1 geschrieben wird, der der Standard-Dateideskriptor ist. Hier ist ein Beispiel:

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' 

Möglicherweise möchten Sie den Filter verbessern, aber das wäre eine andere Frage. Wir haben die Ausgabe, müssen sie aber jetzt aufräumen.

: WARNUNG: Diese Lösung weist einige Einschränkungen auf, siehe Kommentare unten. Es wird nicht immer funktionieren, Ihr Kilometerstand kann variieren.

Prüfung:

Legen Sie dieses Programm (unten) in die Datei helloundchmod +x hello

#!/bin/bash

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

Dieser in hello1undchmod +x hello1

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

Dieser in hello2undchmod +x hello2

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

Führen Sie dann mit aus ./hello2 >/dev/null, suchen Sie die PID des Prozesses Hallo und geben Sie ein, pid_of_process_you_want_to_see_stdout_of=xyzwobei xyz die PID des Hallo ist. Führen Sie dann die Zeile oben aus.

Wie es funktioniert. Wenn Hallo ausgeführt wird, Bash-Gabeln, leitet fd 1 an weiter /dev/nullund führt dann Hallo aus. Hello sendet die Ausgabe per Systemaufruf an fd1 write(1, …. Kernel empfängt Systemaufruf write(1, …, sieht, dass fd 1 verbunden ist /dev/nullund…

Wir führen dann strace (Systemaufruf-Trace) auf Hallo aus und sehen, dass es write(1, "hello\nworld\n") den Rest aufruft, wenn die obige Zeile nur die entsprechende Zeile des Trace auswählt.

Strg-Alt-Delor
quelle
Funktioniert nicht, wenn das Kind seine eigenen Kinder hervorbringt.
Nicole Hamilton
@NicoleHamilton, ich habe jetzt einen Test hinzugefügt, wenn ein Kind sein eigenes Kind hervorbringt. Test bestanden.
Strg-Alt-Delor
1
Es ist auch zu beachten, dass strace die Ausgabe automatisch auf 32 Zeichen abschneidet. Sie können den Parameter -s verwenden, um sie zu ändern.
Piotr Jaszkowski
1
Ah, ich habe Ihre Antwort vielleicht gestern falsch verstanden. Ich sehe jetzt, dass Sie sagen: "Ausführen hello2, welche Anrufe hello1, welche Anrufe hello, und dann die PID des Prozesses (dh das Kind des Kindes) finden und den Befehl für diese PID ausführen ." Ich stimme @NicoleHamilton zu - Sie haben ihren Kommentar nicht angesprochen. Wenn Sie Ihren Befehl auf einen Prozess anwenden , der bereits Kinder hervorgebracht hat, wird die Ausgabe der Kinder nicht erfasst. Wenn Sie Ihre Antwort ändern, um den Befehl auf den Enkelprozess anzuwenden, wird das Problem behoben. … (Fortsetzung) hello stracestrace
Scott
1
(Fortsetzung)… PS Wenn das helloSkript besagt echo … > /dev/null, würde Ihre Lösung fehlschlagen. Ich gebe zu, dass es nahezu unmöglich wäre, mit dieser Situation umzugehen, und mir ist klar, dass Sie (in einem Kommentar) zugeben, dass Sie nur eine 90% ige Lösung vorgelegt haben. Es ist jedoch besser, die Einschränkungen und Einschränkungen Ihrer Antwort in der Antwort anzugeben .
Scott
5

Nein, Sie müssen den Befehl neu starten.

Stdio-Handles werden vom übergeordneten zum untergeordneten Prozess vererbt. Sie haben dem Kind / dev / nul einen Griff gegeben. Es ist kostenlos, damit zu tun, was immer es will, einschließlich Dinge wie dup () 'es oder die Weitergabe an seine eigenen Kinder. Es gibt keine einfache Möglichkeit, in das Betriebssystem zu gelangen und zu ändern, worauf die Handles eines anderen laufenden Prozesses verweisen.

Möglicherweise können Sie einen Debugger für das untergeordnete Element verwenden und dessen Status zappen, alle Speicherorte, an denen eine Kopie des aktuellen Handle-Werts gespeichert ist, mit etwas Neuem überschreiben oder seine Aufrufe an den Kernel verfolgen und alle E / A überwachen. Ich denke, das fordert viele Benutzer auf, aber es kann funktionieren, wenn es sich um einen einzelnen untergeordneten Prozess handelt, der mit dem I / O nichts Lustiges macht.

Aber selbst das schlägt im allgemeinen Fall fehl, z. B. ein Skript, das Pipelines usw. erstellt, Handles täuscht und viele eigene Kinder erstellt, die kommen und gehen. Aus diesem Grund müssen Sie nicht mehr von vorne anfangen (und möglicherweise zu einer Datei umleiten, die Sie später löschen können, auch wenn Sie sie jetzt nicht sehen möchten.)

Nicole Hamilton
quelle
-1 für nicht wissen. Wissen Sie, dass es unmöglich ist (Sie wissen, wie es intern funktioniert), oder haben Sie es einfach noch nie gesehen?
Strg-Alt-Delor
4
Ich weiß, wie es funktioniert. Ich bin der Autor der Hamilton C Shell. hamiltonlabs.com/cshell.htm
Nicole Hamilton
Akzeptiert, aber können Sie bitte die Alternativen erklären? Ist eine Umleitung möglich? Warum nicht? Wie kann ich eine Ausgabe vorübergehend unterdrücken?
Mikhail
4
@nicole Hamilton Und ich bin der Autor der Antwort, die funktioniert.
Strg-Alt-Delor
Die meisten Prozesse scheinen jedoch ein Handle für stderr zu haben, daher mag ich die Idee, gdb zu verwenden und dann das aktuelle Handle zu "überschreiben" ... würde vermutlich nicht den
untergeordneten
5

Ich habe lange nach der Antwort auf diese Frage gesucht. Es stehen hauptsächlich zwei Lösungen zur Verfügung:

  1. Wie Sie hier angegeben haben, strace Option;
  2. Abrufen der Ausgabe mit gdb.

In meinem Fall war keiner von ihnen zufriedenstellend, da zuerst die Ausgabe abgeschnitten wurde (und ich sie nicht länger einstellen konnte). Das zweite kommt nicht in Frage, da auf meiner Plattform keine GDB installiert ist - es ist ein eingebettetes Gerät.

Ich sammelte einige Teilinformationen im Internet (habe sie nicht erstellt, sondern nur zusammengestellt) und bin mithilfe von Named Pipes (FIFOs) zur Lösung gelangt. Wenn der Prozess ausgeführt wird, wird seine Ausgabe an die Named Pipe geleitet, und wenn niemand sie sehen möchte, wird ein dummer Listener (tail -f >> / dev / null) darauf angewendet, um den Puffer zu leeren. Wenn jemand diese Ausgabe erhalten möchte, wird der Endprozess abgebrochen (andernfalls wird die Ausgabe zwischen den Lesern gewechselt) und ich katze die Pipe. Am Ende des Hörens startet ein weiterer Schwanz.

Mein Problem war es also, einen Prozess zu starten, die SSH-Shell zu beenden, mich dann erneut anzumelden und die Ausgabe abzurufen. Dies ist jetzt mit folgenden Befehlen möglich:

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

Das Skript, das dies erreicht, ist unten angefügt. Mir ist bewusst, dass es keine perfekte Lösung ist. Bitte teilen Sie Ihre Gedanken, vielleicht wird es hilfreich sein.

#!/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;
Krzysztow
quelle
4

Sie können dies mit dem Reredirect- Programm tun :

reredirect -m <file> <PID>

Sie können die anfängliche Ausgabe Ihres Prozesses später wiederherstellen, indem Sie Folgendes verwenden:

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

( <M>und <N>werden durch vorherigen Start von Reredirect bereitgestellt).

reredirect READMEerklärt auch, wie man zu einem anderen Befehl umleitet oder nur stdout oder stderr umleitet.

Jérôme Pouiller
quelle