Můžete to udělat pomocí strace.
Pomocí strace
můžete špehovat, co se zapisuje do deskriptoru souboru 1, což je deskriptor souboru stdout. Zde je příklad:
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'
Možná budete chtít filtr vylepšit, ale to by byla jiná otázka. Výstup máme, ale teď ho musíme uklidit.
:UPOZORNĚNÍ:Toto řešení má určitá omezení, viz komentáře níže. Ne vždy to bude fungovat, váš počet najetých kilometrů se může lišit.
Test:
Vložte tento program (níže) do souboru hello
a chmod +x hello
#!/bin/bash
while true
do
echo -en "hello\nworld\n"
done
Tento v hello1
a chmod +x hello1
#!/bin/bash
dir=$(dirname $0)
$dir/hello >/dev/null
Tento v hello2
a chmod +x hello2
#!/bin/bash
dir=$(dirname $0)
$dir/hello1 >/dev/null
pak spusťte s ./hello2 >/dev/null
,potom najděte pid procesu hello a napište pid_of_process_you_want_to_see_stdout_of=xyz
kde xyz je pid z hello, pak spustit řádek nahoře.
Jak to funguje. Když se spustí hello, bash se rozvětví, přesměruje fd1 na /dev/null
, pak provede hello.Hello odešle výstup na fd1 pomocí systémového volání write(1, …
.Kernel přijme systémové volání write(1, …
, vidí, že fd 1 je připojen k /dev/null
a …
Poté spustíme strace (trasování systémového volání) na hello a uvidíme, že volá write(1, "hello\nworld\n")
Zbytek, pokud řádek výše pouze vybírá vhodný řádek trasování.
Ne. Budete muset restartovat příkaz.
Úchyty Stdio se dědí z nadřazeného na podřízený proces. Dali jste dítěti popisovač /dev/nul. Může si s ním dělat, co chce, včetně věcí, jako je dup()'ing nebo předání jeho vlastním dětem. Neexistuje žádný snadný způsob, jak se dostat do operačního systému a změnit to, na co ukazují úchyty jiného běžícího procesu.
Pravděpodobně byste mohli použít debugger na potomka a začít přepínat jeho stav, přepisovat všechna umístění, kde je uložena kopie aktuální hodnoty handle, něčím novým, nebo sledovat jeho volání do jádra a monitorovat jakékoli vstupy a výstupy. Myslím, že to žádá spousta většiny uživatelů, ale může to fungovat, pokud jde o proces s jedním potomkem, který s i/o nedělá nic vtipného.
Ale i to selhává v obecném případě, např. skript, který vytváří potrubí a tak dále, dupá rukojeti a vytváří spoustu vlastních dětí, které přicházejí a odcházejí. To je důvod, proč jste do značné míry uvízli v tom, že začínáte znovu (a možná přesměrováváte na soubor, který můžete později smazat, i když jej nyní nechcete sledovat.)
Odpověď na tuto otázku jsem hledal poměrně dlouho. K dispozici jsou především dvě řešení:
- Jak jste zde uvedli, možnost strace;
- Získání výstupu pomocí gdb.
V mém případě nebyl žádný z nich uspokojivý, protože nejprve zkrátil výstup (a nemohl jsem ho nastavit déle). Druhý nepřipadá v úvahu, protože moje platforma nemá nainstalovanou gdb - je to embedded zařízení.
Shromážděním některých dílčích informací na internetu (nevytvořil jsem je, jen jsem je dal dohromady) jsem dosáhl řešení pomocí pojmenovaných rour (FIFO). Když je proces spuštěn, jeho výstup je směrován do pojmenované roury a pokud jej nikdo nechce vidět, použije se na něj hloupý posluchač (tail -f>> /dev/null), aby se vyprázdnil buffer. Když někdo chce získat tento výstup, ukončí se proces ocasu (jinak se výstup střídá mezi čtečkami) a já kačkuji. Po dokončení poslechu se spustí další ocas.
Můj problém byl tedy spustit proces, ukončit ssh shell a pak se znovu přihlásit a být schopen získat výstup. To je nyní proveditelné pomocí následujících příkazů:
#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>"
Skript, který toho dosáhne, je přiložen níže. Jsem si vědom toho, že to není dokonalé řešení. Prosím, podělte se o své myšlenky, možná to bude užitečné.
#!/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;