Vous pouvez le faire en utilisant strace.
Utilisation de strace
vous pouvez espionner ce qui est écrit dans le descripteur de fichier 1, qui est le descripteur de fichier stdout. Voici un exemple :
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'
Vous voudrez peut-être améliorer le filtre, mais ce serait une autre question. Nous avons la sortie, mais nous devons maintenant la ranger.
:ATTENTION :Cette solution présente certaines limitations, voir les commentaires ci-dessous. Cela ne fonctionnera pas toujours, votre kilométrage peut varier.
Test :
Mettez ce programme (ci-dessous) dans le fichier hello
, et chmod +x hello
#!/bin/bash
while true
do
echo -en "hello\nworld\n"
done
Celui-ci en hello1
et chmod +x hello1
#!/bin/bash
dir=$(dirname $0)
$dir/hello >/dev/null
Celui-ci en hello2
et chmod +x hello2
#!/bin/bash
dir=$(dirname $0)
$dir/hello1 >/dev/null
puis exécutez avec ./hello2 >/dev/null
, puis trouvez le pid du processus hello et tapez pid_of_process_you_want_to_see_stdout_of=xyz
où xyz est le pid de hello, puis exécutez la ligne en haut.
Comment ça marche.Lorsque hello est exécuté, bash fork, redirige fd1 vers /dev/null
, puis execs hello.Hello envoie la sortie à fd1 en utilisant l'appel système write(1, …
.Kernel reçoit l'appel système write(1, …
, voit que fd 1 est connecté à /dev/null
et …
Nous exécutons ensuite strace (system-call trace) sur hello, et voyons qu'il appelle write(1, "hello\nworld\n")
Le reste si la ligne ci-dessus ne fait que sélectionner la ligne appropriée de la trace.
Non. Vous devrez redémarrer la commande.
Les poignées Stdio sont héritées du processus parent au processus enfant. Vous avez donné à l'enfant un handle vers /dev/nul. Il est libre d'en faire ce qu'il veut, y compris des choses comme le duper() ou le transmettre à ses propres enfants. Il n'y a pas de moyen facile d'accéder au système d'exploitation et de modifier ce vers quoi pointent les descripteurs d'un autre processus en cours d'exécution.
On peut dire que vous pouvez utiliser un débogueur sur l'enfant et commencer à zapper son état, en écrasant tous les emplacements où il est stocké une copie de la valeur actuelle du handle avec quelque chose de nouveau, ou pour tracer ses appels au noyau, en surveillant toute entrée/sortie. Je pense que cela demande beaucoup à la plupart des utilisateurs, mais cela peut fonctionner s'il s'agit d'un seul processus enfant qui ne fait rien de bizarre avec les entrées/sorties.
Mais même cela échoue dans le cas général, par exemple, un script qui crée des pipelines, etc., en dupant les poignées et en créant beaucoup de ses propres enfants qui vont et viennent. C'est pourquoi vous êtes pratiquement obligé de recommencer (et peut-être de rediriger vers un fichier que vous pourrez supprimer plus tard, même si vous ne voulez pas le regarder maintenant.)
J'ai longtemps cherché la réponse à cette question. Il existe principalement deux solutions :
- Comme vous l'avez indiqué ici, l'option strace ;
- Obtenir la sortie à l'aide de gdb.
Dans mon cas, aucun d'entre eux n'était saisfactory, car tronque d'abord la sortie (et je ne pouvais pas la définir plus longtemps). La seconde est hors de question, car ma plate-forme n'a pas installé gdb - c'est un périphérique intégré.
En collectant des informations partielles sur Internet (je ne les ai pas créées, j'ai juste assemblé des morceaux), j'ai atteint la solution en utilisant des canaux nommés (FIFO). Lorsque le processus est exécuté, sa sortie est dirigée vers le tube nommé et si personne ne veut le voir, un écouteur muet (tail -f>> /dev/null) lui est appliqué pour vider le tampon. Lorsque quelqu'un veut obtenir cette sortie, le processus de queue est tué (sinon la sortie est alternée entre les lecteurs) et je cat le tuyau. À la fin de l'écoute, une autre queue démarre.
Mon problème était donc de démarrer un processus, de quitter le shell ssh, puis de vous reconnecter et de pouvoir obtenir la sortie. C'est faisable maintenant avec les commandes suivantes :
#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>"
Le script qui accomplit cela est joint ci-dessous. Je suis conscient que ce n'est pas une solution parfaite. S'il vous plaît, partagez vos pensées, cela sera peut-être utile.
#!/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;