如果您只需要知道它是管道还是重定向,那么确定标准输入是否是终端就足够了:
if [ -t 0 ]; then
# stdin is a tty: process command line
else
# stdin is not a tty: process standard input
fi
[ (aka test) 和 -t 等效于 libc isatty() 函数。
以上内容适用于something | myscript 和myscript < infile。这是最简单的解决方案,假设您的脚本用于交互式使用。
[ 命令是 bash 和其他一些 shell 中的内置命令,并且由于 [/test 和 -tis in POSIX,它也是可移植的(不依赖于 Linux、bash 或 GNU 实用程序功能)。
有一种极端情况,如果文件描述符无效,test -t 也会返回 false,但要安排这一点需要一些轻微的逆境。 test -e 会检测到这一点,但假设您有一个文件名,例如 /dev/stdin 可以使用。
也可以使用 POSIX tty 命令,并处理上述逆境。如果 stdin 是终端,它将打印 tty 设备名称并返回 0,并且在任何其他情况下将打印“not a tty”并返回 1:
if tty >/dev/null ; then
# stdin is a tty: process command line
else
# stdin is not a tty: process standard input
fi
(使用 GNU tty,您可以使用 tty -s 进行静默操作)
虽然在典型的 Linux 上肯定可以接受,但可移植性较差的方法是使用 GNU stat 及其 %F 格式说明符,这将返回文本“字符特殊文件”、“fifo”和“常规文件”分别是终端、管道和重定向的情况。 stat 需要文件名,因此您必须提供/dev/stdin、/dev/fd/0 或/proc/self/fd/0 形式的特殊命名文件,并使用-L 来跟踪符号链接:
stat -L -c "%F" /dev/stdin
这可能是处理非交互式使用的最佳方式(因为那时您无法对终端做出假设),或者检测不同于重定向的实际管道 (FIFO)。
%F 有一个小问题,因为您无法使用它来区分 终端 和某些其他设备文件,例如 /dev/zero 或 /dev/null,它们也是“字符特殊文件”并且可能合理地出现。一个不太好的解决方案是使用%t 报告底层设备类型(主要,以十六进制表示),假设您知道底层 tty 设备编号范围是什么......这取决于您使用的是 BSD 样式 ptys 还是 Unix98 ptys,或者您是否在实际控制台上,等等。在简单的情况下,%t 将是 0,但对于管道或普通(非特殊)文件的重定向。
此类问题的更通用解决方案是使用 bash 的 read 超时 (read -t 0 ...) 或非阻塞 I/O 与 GNU dd (dd iflag=nonblock)。
后者将允许您检测标准输入是否缺少输入,dd 如果没有准备好读取的内容,将返回退出代码 1。但是,这些更适合非阻塞轮询循环,而不是一次性检查:当您在管道中启动两个或多个进程时存在竞争条件,因为一个进程可能在另一个进程写入之前准备好读取。