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

 in

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 <ckpipe.sh
Pipe: .../ckpipe/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 <ckpipe2.sh
Pipe: .../ckpipe/ckpipe2.sh
AttachmentSize
ckpipe_sh.txt263 bytes
ckpipe2_sh.txt156 bytes
______________________

Mitch Frazier is an Associate Editor for Linux Journal.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

-t / isatty / ioctl

Anonymous's picture

I have had the same problem recently and had to implement it in C too -- the solution: isatty

I found out that -t and isatty uses an ioctl to achieve the result, which led me to the following C solution:

#include 
#include 
#include 
#include 

#define STDIO 0

int main()
{
  struct termios NewTermios;

  if (isatty(STDIO))
    printf("Input from a terminal (according to isatty)\n");
  else
    printf("Input NOT from a terminal (according to isatty)\n");
  if (ioctl(STDIO, TCGETS, &NewTermios) == 0)
    printf("Input from a terminal (according to ioctl)\n");
  else
    printf("Input NOT from a terminal (according to ioctl)\n");
}

Really nice post

Pedro Oliveira's picture

I really enjoyed this one, not only because it's a useful thing but makes you remind you how you can play with bash (and Linux) as a Lego.
Apart from the original post the comments are neat too :)
Long live to the community.

Actually both solutions are the same

Aog2000a's picture

$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 2010-02-12 14:33 /dev/fd -> /proc/self/fd

/dev/fd is just a symlink to /proc/self/fd

tty detection in one line

Anonymous's picture

tty -s && echo "Yes, we are on a tty! zomg Ponies!" || echo "Nope, we're not on a tty and it's cool."

I use something like this to detect whether I'm on a tty. Not useful for pipe detection, but hey.

L8N buglet in your first solution

Santi's picture

The stat command (or tty) will localise its output and fail unless the local is a suitable en one, so you need to prefix it with something like: LC_ALL=C. Bad enough, but typical in shell programming... :(

/dev/pts is good only for remote shells or shells in X11 windows

Anonymous's picture

> if [[ "$stdin" =~ ^/dev/pts/[0-9] ]]; then
> echo Terminal
> else
> echo Pipe: $stdin
> fi

The above will not work for shell sessions on (local) virtual consoles.

HI

Olivia Spensor's picture

Well It is not so simple as it's seems to be.I think before reading this post many people must have been unaware from this so you have helped them by providing this quite cool info to them.

"So we use ls to get the file it's linked to"

Jeff Pohlmeyer's picture

I agree the "tty" command might be the better solution here, but with regard to the general situation of following a symbolic link:

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

That looks ugly to me.

how about:

stdin="$(readlink -f /dev/fd/0)"

BTW, this also seems to work:

case "$(stat -L -c %t /dev/fd/0)" in
0)
  echo "pipe" 
  ;;
88)
  echo "stdin"
  ;;
*)
  echo "unknown"
  ;;
esac

- Jeff

Readlink

Mitch Frazier's picture

Agreed, readlink is better.

Mitch Frazier is an Associate Editor for Linux Journal.

This reminds me of perl (tmtowdi) ...

Derek's picture

This post and its comments remind me of that perl saying which I guess applies to the shell as well - there's more than one way to do it (TMTOWTDI) ...

regards

tty gets it done

Ronald Vazquez's picture

Just like Jim Helm, I use tty to test for this. A quick and dirty function like:

am_I_on_a_terminal() {
        if [[ $(tty -s ; echo ${?}) == 0 ]] ; then
                ANSWER=1
            else
                ANSWER=0
        fi
        printf "%d" "${ANSWER}"
}

if [[ $(am_I_on_a_terminal) == 1 ]] ; then
        printf "%s\n" "I am on a terminal"
   else
        printf "%s\n" "I must be runnig from cron"
fi

exit 0

Can accomplish the task.

RV

Check $- to know if the shell is interactive

Yannick Le Saint's picture

Depending on what you want to do, you can check if the character "i" is present in the special bash variable $-

if echo "$-" | grep -q i; then
echo "The shell is interactive"
else
echo "Non-interactive shell (closed stdin, file, pipe, ...)"
fi

two words

Jim Helm's picture

man tty

Thanks

Mitch Frazier's picture

Another good way. The tty command returns "not a tty" when the input is coming from a pipe.

Mitch Frazier is an Associate Editor for Linux Journal.

Shell test

Luchostein's picture

Did you try test -t /proc/self/fd/0 (or just [ -t /proc/self/fd/0 ])? We could define:

function isTTY() {
  local path="${1:-/proc/self/fd/0}"
  [ -t "$path" ]
}

test -t

Mitch Frazier's picture

The -t option (which I just learned about from the comment below) tests a file-descriptor, not a file. So you can't do what you're suggesting, checking a path, you would need to determine the file-descriptor. In the case of stdin, you just use "0":

function isTTY() {
  local fd="${1:-0}"
  [ -t "$fd" ]
}

Mitch Frazier is an Associate Editor for Linux Journal.

What's wrong with -t?

Jack Repenning's picture

Why wasn't the conditional expression "if [ -t 0 ] ; ..." (which is stat-based) sufficient?

> if [ -t 0 ] ; then echo terminal; else echo not; fi
terminal

> if [ -t 0 ] ; then echo terminal; else echo not; fi < /dev/null
not

> echo hi | if [ -t 0 ] ; then echo terminal; else echo not; fi
not

-t refined

Jaime Fernández's picture

According to this reference:
http://tldp.org/LDP/abs/html/intandnonint.html
it proposes:

if [[ -t "$fd" || -S /dev/stdin ]]
then
echo interactive
else
echo non-interactive
fi

so that it's valid even when you are connected via ssh.

Nothing

Mitch Frazier's picture

Nothing's wrong with that, I just wasn't aware of it. Like I said it seemed like it ought to be simple and it would have been had I known about that.

Mitch Frazier is an Associate Editor for Linux Journal.

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix