Bash脚本读取管道或参数

13

我希望我的脚本可以从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",而我不想要这种情况。有什么建议可以解决这个问题吗?

谢谢您的帮助。 /Tomas


1
你总是可以从标准输入中读取数据。如果你想通过命令行传递输入,那么你可以使用 your_script <<< 'some string' -- 你的脚本将把它视为来自标准输入的普通输入。 - jfs
如何在Bash中从管道输入或命令行参数中读取数据? - Akhil
3个回答

13

先检查参数怎么样?

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

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

cat "$@" | process

无法让我想要的东西工作。在fish中,使用test命令获取参数列表计数时,无论是否使用if或其他命令,都会窃取潜在的管道输入,而echo $argv | cat | read ...也是如此。如果我真的需要这个功能,也许可以使用ruby的ARGF或者其他更大的编程语言,但我并不需要它。现在,我只能强制将所有内容通过提示或参数传递。 - Pysis

8
如果你只需要知道它是管道还是重定向,那么判断标准输入是否为终端就足够了:
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在POSIX中,因此它也是可移植的(不依赖于Linux、bash或GNU实用程序特性)。

有一种边缘情况,如果文件描述符无效,则test -t也会返回false,但需要进行一些轻微的调整才能安排。test -e将检测到这一点,尽管假定您有一个文件名(例如/dev/stdin)可供使用。

POSIXtty命令也可以使用,并处理上述问题。如果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格式说明符,这会返回文本"character special file"、"fifo"和"regular file",分别对应于终端、管道和重定向。 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样式pty还是Unix98样式pty,或者您是否在实际控制台上等等。在简单情况下,% t将为0,但适用于常规(非特殊)文件的管道或重定向。 解决此类问题的更一般方法是使用带有超时的bash read(read -t 0 ...)或具有GNU dd的非阻塞I / O(dd iflag = nonblock)。后者将使您能够检测stdin上的输入缺失,如果没有准备好读取,则dd将返回退出代码1。然而,这些更适合于非阻塞轮询循环,而不是一次性检查:当您在管道中启动两个或多个进程时,存在竞争条件,因为一个进程可能准备好读取,而另一个进程尚未写入。

6

首先检查命令行参数,如果没有参数则使用标准输入。使用Shell参数展开可以很方便地代替if-else语句:

value=${*:-`cat`}
# do something with $value

你能提供一个更完整的例子吗?这看起来是一个有趣的想法,但很难理解它如何用来解决问题。 - typeracer
这个问题与输入无关,因此没有太多要补充的。它创建一个变量,其值为所有参数。如果未提供参数,则会回退到标准输入。因此,只需将$value用作常规变量即可。 - gregers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接