Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Let's say I have a bash script called log.sh. In this script, I want to read in input from a pipe, but I also want to know the command used to pipe input into me. Example:

tail -f /var/log/httpd/error | log.sh

In the shell script, I want to know the command tail -f /var/log/httpd/error.

share|improve this question
    
I am extremely curious as to why you want this. –  Ignacio Vazquez-Abrams Feb 11 '11 at 6:41
    
My guess is your making some type of GUI program which captures & processes pids? –  palbakulich Feb 11 '11 at 6:50
    
I would like to know so I can distinguish where to put the results. Depending on the command and filename, I would want to perform different actions. –  St. John Johnson Feb 11 '11 at 6:57
3  
That sounds like a major violation of the Principle of Least Surprise to me. If the script should do different things under different circumstances, then that should be controlled by a command-line option rather than by where its input is coming from. –  Dave Sherohman Feb 11 '11 at 10:10
    
@Dave, I agree. Let's just say, for the sake of this example, I just want to 'know' what the incoming command is. –  St. John Johnson Feb 11 '11 at 16:57
add comment

2 Answers 2

up vote 5 down vote accepted

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
share|improve this answer
    
instead of this huge post i would have given a 2nd answer with the lsof-based script. nice work for that one. –  akira Feb 11 '11 at 10:02
    
@akira Thanks. It took a few attempts to make it clean and portable. Learned a few things about procfs and lsof along the way. Thanks for the idea. –  Mikel Feb 11 '11 at 10:04
    
I accepted yours as it gives an answer other people can directly use. @Akira, you did most of the work, sorry I couldn't accept yours as well. –  St. John Johnson Feb 11 '11 at 17:02
add comment

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 :)

share|improve this answer
    
I don't know which answer to accept. @Akira, you gave the actual breakdown as to how to determine it, while @Mikel gave me a script. Way to make things awesome + difficult. –  St. John Johnson Feb 11 '11 at 16:59
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.