bash脚本读取管道或参数

时间:2013-12-03 12:30:03

标签: linux bash pipe stdin

我希望我的脚本从stdin读取字符串,如果是管道,或者从参数读取。所以首先我想检查一些文本是否是管道,如果不是,它应该使用一个参数作为输入。我的代码看起来像这样:

value=$(cat) # read from stdin
if [ "$pipe" != "" ]; then #check if pipe is not empty
 #Do something with pipe string
else
 #Do something with argument string
fi

问题是当它没有被管道传输时,脚本将停止并等待“ctrl d”,我不想要那个。关于如何解决这个问题的任何建议?

提前致谢。 /托马斯

3 个答案:

答案 0 :(得分:10)

首先检查参数怎么样?

if (($#)) ; then
     process "$1"
else
     cat | process
fi

或者,只是利用cat的相同行为:

cat "$@" | process

答案 1 :(得分:4)

如果您只需要知道它是管道还是重定向,那么确定stdin是否是终端就足够了:

if [ -t 0 ]; then
    # stdin is a tty: process command line
else 
    # stdin is not a tty: process standard input
fi
带有[

test(又名-t)等同于libc isatty()函数。 以上内容适用于something | myscriptmyscript < infile。这是最简单的解决方案,假设您的脚本是供交互使用的。

[命令是内置在bash和其他一些shell中,自[ / test -t is in POSIX以来,它也是可移植的(不是依赖于Linux,bash或GNU实用程序功能。)

有一个边缘情况,如果文件描述符无效,test -t也返回false,但是安排它会有一些轻微的逆境。 test -e会检测到这一点,但假设您使用了/dev/stdin这样的文件名。

也可以使用POSIX tty命令,并处理上面的逆境。如果stdin是终端,它将打印tty设备名称并返回0,并且将打印“not 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格式说明符,这将返回文本“character special file”,“fifo”和“常规文件“分别在终端,管道和重定向的情况下。 stat需要文件名,因此您必须提供/dev/stdin/dev/fd/0/proc/self/fd/0形式的特别命名文件,并使用-L来追踪符号链接:

stat -L -c "%F" /dev/stdin

这可能是处理非交互式使用的最佳方式(因为您无法对终端进行假设),或者检测与重定向不同的实际管道(FIFO)。

%F存在轻微问题,因为您无法用它来区分终端和某些其他设备文件,例如/dev/zero或{{ 1}}也是“字符特殊文件”,可能合理地出现。一个不合适的解决方案是使用/dev/null报告底层设备类型(主要,十六进制),假设您知道底层设备编号范围是什么......这取决于您是否使用BSD样式ptys或Unix98 ptys,或者你是否在实际的控制台上,等等。在简单的情况下,%t对于管道或正常(非特殊)文件的重定向将为0。

针对此类问题的更一般解决方案是使用带有超时(%t)的bash read或带有GNU read -t 0 ...的非阻塞I / O(dd )。

后者将允许您检测stdin上缺少输入,如果没有任何可读内容,dd iflag=nonblock将返回退出代码1。但是,这些更适合非阻塞轮询循环,而不是一次性检查:当您在管道中启动两个或多个进程时存在竞争条件,因为在另一个进程写入之前可能已准备好读取。

答案 2 :(得分:2)

如果没有参数,首先检查命令行参数和回退到stdin会更容易。 Shell Parameter Expansion是一个很好的速记,而不是if-else:

value=${*:-`cat`}