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.

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState