How can I detect if my shell script is running through a pipe?

BashShellPipe

Bash Problem Overview


How do I detect from within a shell script if its standard output is being sent to a terminal or if it's piped to another process?

The case in point: I'd like to add escape codes to colorize output, but only when run interactively, but not when piped, similar to what ls --color does.

Bash Solutions


Solution 1 - Bash

In a pure POSIX shell,

if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi

returns "terminal", because the output is sent to your terminal, whereas

(if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat

returns "not a terminal", because the output of the parenthetic element is piped to cat.


The -t flag is described in man pages as

> -t fd True if file descriptor fd is open and refers to a terminal.

... where fd can be one of the usual file descriptor assignments:

Solution 2 - Bash

There is no foolproof way to determine if STDIN, STDOUT, or STDERR are being piped to/from your script, primarily because of programs like ssh.

Things that "normally" work

For example, the following bash solution works correctly in an interactive shell:

[[ -t 1 ]] && \
    echo 'STDOUT is attached to TTY'

[[ -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a pipe'

[[ ! -t 1 && ! -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a redirection'

But they don't always work

However, when executing this command as a non-TTY ssh command, STD streams always looks like they are being piped. To demonstrate this, using STDIN because it's easier:

# CORRECT: Forced-tty mode correctly reports '1', which represents
# no pipe.
ssh -t localhost '[[ -p /dev/stdin ]]; echo ${?}'

# CORRECT: Issuing a piped command in forced-tty mode correctly
# reports '0', which represents a pipe.
ssh -t localhost 'echo hi | [[ -p /dev/stdin ]]; echo ${?}'

# INCORRECT: Non-tty mode reports '0', which represents a pipe,
# even though one isn't specified here.
ssh -T localhost '[[ -p /dev/stdin ]]; echo ${?}'

Why it matters

This is a pretty big deal, because it implies that there is no way for a bash script to tell whether a non-tty ssh command is being piped or not. Note that this unfortunate behavior was introduced when recent versions of ssh started using pipes for non-TTY STDIO. Prior versions used sockets, which COULD be differentiated from within bash by using [[ -S ]].

When it matters

This limitation normally causes problems when you want to write a bash script that has behavior similar to a compiled utility, such as cat. For example, cat allows the following flexible behavior in handling various input sources simultaneously, and is smart enough to determine whether it is receiving piped input regardless of whether non-TTY or forced-TTY ssh is being used:

ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'

You can only do something like that if you can reliably determine if pipes are involved or not. Otherwise, executing a command that reads STDIN when no input is available from either pipes or redirection will result in the script hanging and waiting for STDIN input.

Other things that don't work

In trying to solve this problem, I've looked at several techniques that fail to solve the problem, including ones that involve:

  • examining SSH environment variables
  • using stat on /dev/stdin file descriptors
  • examining interactive mode via [[ "${-}" =~ 'i' ]]
  • examining tty status via tty and tty -s
  • examining ssh status via [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Note that if you are using an OS that supports the /proc virtual filesystem, you might have luck following the symbolic links for STDIO to determine whether a pipe is being used or not. However, /proc is not a cross-platform, POSIX-compatible solution.

I'm extremely interesting in solving this problem, so please let me know if you think of any other technique that might work, preferably POSIX-based solutions that work on both Linux and BSD.

Solution 3 - Bash

The command test (builtin in Bash), has an option to check if a file descriptor is a tty.

if [ -t 1 ]; then
    # Standard output is a tty
fi

See "man test" or "man bash" and search for "-t".

Solution 4 - Bash

You don't mention which shell you are using, but in Bash, you can do this:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi

Solution 5 - Bash

On Solaris, the suggestion from Dejay Clayton works mostly. The -p does not respond as desired.

File bash_redir_test.sh looks like:

[[ -t 1 ]] && \
    echo 'STDOUT is attached to TTY'

[[ -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a pipe'

[[ ! -t 1 && ! -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a redirection'

On Linux, it works great:

:$ ./bash_redir_test.sh
STDOUT is attached to TTY

:$ ./bash_redir_test.sh | xargs echo
STDOUT is attached to a pipe

:$ rm bash_redir_test.log
:$ ./bash_redir_test.sh >> bash_redir_test.log

:$ tail bash_redir_test.log
STDOUT is attached to a redirection

On Solaris:

:# ./bash_redir_test.sh
STDOUT is attached to TTY

:# ./bash_redir_test.sh | xargs echo
STDOUT is attached to a redirection

:# rm bash_redir_test.log
bash_redir_test.log: No such file or directory

:# ./bash_redir_test.sh >> bash_redir_test.log
:# tail bash_redir_test.log
STDOUT is attached to a redirection

:#

Solution 6 - Bash

The following code (tested only in Linux Bash 4.4) should not be considered portable nor recommended, but for the sake of completeness here it is:

ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags:    00$' /proc/$$/fdinfo/0 && echo "pipe detected"

I don't know why, but it seems that file descriptor "3" is somehow created when a Bash function has standard input piped.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser111422View Question on Stackoverflow
Solution 1 - Bashdmckee --- ex-moderator kittenView Answer on Stackoverflow
Solution 2 - BashDejay ClaytonView Answer on Stackoverflow
Solution 3 - BashBeanoView Answer on Stackoverflow
Solution 4 - BashDan MouldingView Answer on Stackoverflow
Solution 5 - Bashsbj3View Answer on Stackoverflow
Solution 6 - BashATorrasView Answer on Stackoverflow