How do you determine the actual command that is piping into you?
Solution 1:
Akira suggested using lsof
.
Here's how you could script it:
whatpipe2.sh
#!/bin/bash
pid=$$
pgid=$(ps -o pgid= -p $pid)
lsofout=$(lsof -g $pgid)
pipenode=$(echo "$lsofout" | awk '$5 == "0r" { print $9 }')
otherpids=$(echo "$lsofout" | awk '$5 == "1w" { print $2 }')
for pid in $otherpids; do
if cmd=$(ps -o cmd= -p $pid 2>/dev/null); then
echo "$cmd"
break
fi
done
Running it:
$ tail -f /var/log/messages | ./whatpipe2.sh
tail -f /var/log/messages
^C
Another way is using process groups.
whatpipe1.sh
#!/bin/bash
pid=$$
# ps output is nasty, can (and usually does) start with spaces
# to handle this, I don't quote the "if test $_pgrp = $pgrp" line below
pgrp=$(ps -o pgrp= -p $pid)
psout=$(ps -o pgrp= -o pid= -o cmd=)
echo "$psout" | while read _pgrp _pid _cmd; do
if test $_pgrp = $pgrp; then
if test $_pid != $pid; then
case $_cmd in
ps*)
# don't print the "ps" we ran to get this info
# XXX but this actually means we exclude any "ps" command :-(
;;
*)
echo "$_cmd"
;;
esac
fi
fi
done
Running it:
$ tail -f /var/log/messages | ./whatpipe1.sh
tail -f /var/log/messages
^C
Note they both only work if the command on the left side of the pipe runs for long enough for ps
to see it. You said you were using it with tail -f
, so I doubt this is an issue.
$ sleep 0 | ./whatpipe1.sh
$ sleep 1 | ./whatpipe1.sh
sleep 1
Solution 2:
the pipe will appear as an entry in the list of open filedescriptors of your process:
% ls -l /proc/PID/fd
lr-x------ 1 xyz xyz 64 Feb 11 08:05 0 -> pipe:[124149866]
lrwx------ 1 xyz xyz 64 Feb 11 08:05 1 -> /dev/pts/2
lrwx------ 1 xyz xyz 64 Feb 11 08:05 2 -> /dev/pts/2
lr-x------ 1 xyz xyz 64 Feb 11 08:05 10 -> /tmp/foo.sh
you could also use something like:
% lsof -p PID
sh 29890 xyz cwd DIR 0,44 4096 77712070 /tmp
sh 29890 xyz rtd DIR 0,44 4096 74368803 /
sh 29890 xyz txt REG 0,44 83888 77597729 /bin/dash
sh 29890 xyz mem REG 0,44 1405508 79888619 /lib/tls/i686/cmov/libc-2.11.1.so
sh 29890 xyz mem REG 0,44 113964 79874782 /lib/ld-2.11.1.so
sh 29890 xyz 0r FIFO 0,6 124149866 pipe
sh 29890 xyz 1u CHR 136,2 4 /dev/pts/2
sh 29890 xyz 2u CHR 136,2 4 /dev/pts/2
sh 29890 xyz 10r REG 0,44 66 77712115 /tmp/foo.sh
so, than you have the inode of the pipe :) you can now search every other process under /proc/
for that pipe. then you will have the command that is piping to you:
% lsof | grep 124149866
cat 29889 xyz 1w FIFO 0,6 124149866 pipe
sh 29890 xyz 0r FIFO 0,6 124149866 pipe
in this example, cat
piped to wards sh
. in /proc/29889
you can find a file called cmdline
which tells you, what exactly was called:
% cat /proc/29889/cmdline
cat/dev/zero%
the fields of the command line are separated by NUL, thus it looks a bit ugly :)
Solution 3:
Here's a compact solution using modern lsof
on modern Linux distributions:
cmd=$(lsof -t -p $$ -a -d 0 +E | while read p; do
[ $p -ne $$ ] && echo "$(tr \\000 " " </proc/$p/cmdline)"
done)
This lists the endpoint files (+E
) of FD 0 on the current shell process (-p $$ -a -d 0
), then limits the output to PIDs only (-t
), yielding the PIDs on both sides of the pipe.
Note that:
- There may be more than one PID found on the source end, e.g.
{ echo Hi; sleep 5 ; } | whatpipe.sh
will likely yield abash
(the input subshell) andsleep 5
. -
+E
is only available iflsof
was compiled with-DHASUXSOCKEPT
. That should be true for most modern Linux distros, but check your installation anyway with:lsof -v 2>&1 | grep HASUXSOCKEPT