10

Ideally, this would be scriptable in shell, but Perl or Python would be fine. C code could be helpful, but probably fails cost/benefit.

I recognize that redirection to a FIFO (named pipe) may be indistinguishable from a real pipe, and that is enough of an edge case that I don't really care.

Strict POSIX solutions are best, UNIX/Linux variant-independent are next best, but at least something that works on Darwin (MacOS X) is what I need right now.

Before you write your answer - I already know about test -t - that will tell me whether stdout is a terminal (in which case it is definitely not a pipe - but it will not tell me whether stdout has been redirected to a file, non-terminal character device, or UNIX-domain socket rather than a pipe.

Intended use case: I have a command that should be run within backquotes in the shell, so that it can output commands that set environment variables. I would like the command to abort with an error if stdout is not redirected to a pipe, as in that case it definitely wasn't invoked by eval `mycommand`;.

If there's some special environment variable that a shell will set when running a command within backquotes that would be helpful, but as it is likely to be specific to bash or zsh or something, pipe-detection is more important.

jww
  • 97,681
  • 90
  • 411
  • 885
Alex Dupuy
  • 5,984
  • 3
  • 39
  • 35
  • I wouldn't technically enforce this, I'd just output whatever it needs to be eval'ed and expect the coder/user knows how to use my script properly (using eval) because they read my script documentation (RTFM, right?). But anyways, good question and I hope you get the proper technical answer. – KurzedMetal May 26 '14 at 15:19
  • Generally, I'd agree with you, @KurzedMetal, but for MacOS X, getting users to even type something in at the Terminal is scary - properly understanding eval backquotes and such is a black art, they would have no idea that anything was actually wrong. – Alex Dupuy May 26 '14 at 15:37
  • 1
    Since you said you can use perl, I guess you can use its `-p` file test operator (http://perldoc.perl.org/functions/-X.html); something like `perl -e 'exit(-p STDOUT ? 0 : 1);'` will tell you if stdout if a pipe or a fifo (without distinguishing between them). – loreb May 27 '14 at 15:10
  • @loreb - that Perl test is good and quite short enough to use even in a shell script (I'd simplify it a bit to ``perl -e 'exit ! -p STDOUT'``). You should put this as an answer, probably the best so far. – Alex Dupuy May 28 '14 at 09:37

5 Answers5

4

You could do an fstat to the file descriptor and check returning structure, for example, st_mode = 0x1000 (S_IFIFO) indicates a Named Pipe.

Example with Python:

from __future__ import print_function
import sys
import os

print(os.fstat(sys.stdout.fileno()), file=sys.stderr)

Output on windows:

C:> python test_fd.py | more
nt.stat_result(st_mode=4096, st_ino=0L, st_dev=0, st_nlink=0, st_uid=0, st_gid=0, st_size=0L, st_atime=0L, st_mtime=0L, st_ctime=0L)

C:> python test_fd.py > test_fd.txt
nt.stat_result(st_mode=33206, st_ino=16888498602769633L, st_dev=0, st_nlink=1, st_uid=0, st_gid=0, st_size=0L, st_atime= 1401119520L, st_mtime=1401119520L, st_ctime=1401119349L)
KurzedMetal
  • 12,540
  • 6
  • 39
  • 65
  • 1
    Yes, the ``st_mode`` bit ``S_IFIFO`` (010000 octal = 4096 decimal) does the trick, and by checking for ``st_ino == 0`` you can eliminate redirection to named pipes as well. With a bit of tuning this is probably a better solution than my use of ``/usr/bin/stat`` because it is more portable (only depends on POSIX features of a common scripting language rather than a Unix/Linux-centric utility that may not be present on many distributions, and likely never on Windows). – Alex Dupuy May 26 '14 at 18:38
  • Looking at the `fstat` system call documentation I found out there's a macro defined to check Pipes: [S_ISFIFO, which is included in the Python `stat` module](https://docs.python.org/2/library/stat.html#stat.S_ISFIFO) too. Although checking other stats/flags may be helpful too. – KurzedMetal May 27 '14 at 12:26
2

(my comment turned to answer per Alex Dupuy's suggestion) Since you said you can use perl, I guess you can use its -p file test operator (perldoc.perl.org/functions/-X.html); something like perl -e 'exit(-p STDOUT ? 0 : 1);' will tell you if stdout if a pipe or a fifo (without distinguishing between them).

Fwiw, calling that from the shell is exactly the reason why I used perl -e :)

loreb
  • 1,327
  • 1
  • 7
  • 6
2

In Python:

import sys
print sys.stdout.isatty()

Examples:

$ ./a
True
$ ./a | less
False
Omer Dagan
  • 14,868
  • 16
  • 44
  • 60
1

The stat command works on MacOS X and (with some finessing of variant implementations) other Linux/Unix variants as well, so it is a solution for me:

FORMOPT=
TYPEFORM=
stat() {
  if [ -z "$FORMOPT" ]; then
    if /usr/bin/stat --version >/dev/null 2>&1; then
      FORMOPT=--format
      TYPEFORM=%F
    else
      FORMOPT=-f
      TYPEFORM=%HT
    fi
  fi
  case $1 in
    type) FORMARG="$FORMOPT $TYPEFORM" ; shift ;;
  esac
  /usr/bin/stat -L $FORMARG "$@"
}

exec 9>&1
case `stat type /dev/fd/9` in
  [Ff]ifo*) echo stdout is a pipe ;;
  *) echo stdout is not a pipe ;;
esac
Alex Dupuy
  • 5,984
  • 3
  • 39
  • 35
0

There is a Linux-specific solution that does what I want:

exec 9>&1
case `readlink /dev/fd/9` in
    pipe:\[*\]) echo stdout is a pipe ;;
    *) echo stdout is not a pipe ;;
esac

but that isn't the answer I am looking for as I want this to run on xBSD as well.

I've made this answer community wiki, so feel free to improve it, but if you have a non-Linux solution (or a Linux-specific solution using another technique), please create a new answer.

Alex Dupuy
  • 5,984
  • 3
  • 39
  • 35