Wednesday, March 17, 2010

Determine If Shell Input is Coming From the Terminal or From a Pipe

Working on a little script the other day I had the need to determine if the input to the script was coming from a pipe or from the terminal.

Seems like a simple enough thing to determine but nothing jumped immediately to mind and a quick internet search didn't help much either.

After a bit of pondering I came up with two solutions: the stat command and using information from the proc file system.

The first solution uses the stat command to determine what type of file is connected to standard input. First we find out what is connected to standard input:

stdin="$(ls -l /dev/fd/0)"
stdin="${stdin/*-> /}"

The file /dev/fd/0 is the standard input, which is a symbolic link. So we use ls to get the file it's linked to.

Then we remove everything that matches *-> from the front of the value, that leaves us with the linked to file.

Now we use stat to get the file type:

ftype="$(stat --printf=%F $stdin)"

Then we just test the file type:
if   [[ "$ftype" == 'character special file' ]]; then
    echo Terminal
elif [[ "$ftype" == 'regular file' ]]; then
    echo Pipe: $stdin
else
    echo Unknown: $stdin
fi

We can test it via:

$ sh ckpipe.sh
Terminal

$ sh ckpipe.sh 

The next solution I came up with involves using information from the proc file system.

In the proc file system the files for each process appear in the directory /proc/PROCESS_ID/fd (for the current process the special directory /proc/self/fd can be used):

$ ls -la /proc/self/fd
total 0
dr-x------ 2 mitch users  0 2010-02-10 11:04 .
dr-xr-xr-x 7 mitch users  0 2010-02-10 11:04 ..
lrwx------ 1 mitch users 64 2010-02-10 11:04 0 -> /dev/pts/2
lrwx------ 1 mitch users 64 2010-02-10 11:04 1 -> /dev/pts/2
lrwx------ 1 mitch users 64 2010-02-10 11:04 2 -> /dev/pts/2
lr-x------ 1 mitch users 64 2010-02-10 11:04 3 -> /proc/29328/fd

As before we need the name of file that is linked to, so we get that with:

stdin="$(ls -l /proc/self/fd/0)"
stdin="${stdin/*-> /}"

From there we can just test to see if it's linked to a /dev/pts file:
if [[ "$stdin" =~ ^/dev/pts/[0-9] ]]; then
    echo Terminal
else
    echo Pipe: $stdin
fi

We test this the same way:
$ sh ckpipe2.sh
Terminal

$ sh ckpipe2.sh 
AttachmentSize
ckpipe_sh.txt263 bytes
ckpipe2_sh.txt156 bytes

No comments:

Post a Comment