如何检查管道是否为空,如果不是,则对数据运行命令?


42

我已经在bash脚本中通过管道传递了一条线,并希望在将其提供给程序之前检查管道是否具有数据。

我发现有关搜索,test -t 0但在这里不起作用。始终返回false。那么如何确保管道中有数据呢?

例:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

输出: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

输出: fill

有别于标准/规范方式来测试上述管道是否产生了输出?输入需要保留,以将其传递给程序。这概括了如何将输出从一个进程传递到另一个进程,但仅在第一个进程具有输出时才执行?重点是发送电子邮件。


Answers:


37

使用通用的Shell实用程序无法窥视管道的内容,也无法读取管道中的字符然后放回管道。知道管道具有数据的唯一方法是读取一个字节,然后必须将该字节发送到其目的地。

这样做:读取一个字节;如果检测到文件结尾,则在输入为空时执行您想做的事情;如果您确实读取了一个字节,则在输入不为空时分叉要执行的操作,将该字节传送到该字节中,然后传送其余数据。

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

如果输入不为空ifne,则Joey Hess的moreutils中的实用程序运行命令。通常默认情况下未安装它,但是它应该可用或可以在大多数Unix变体上轻松构建。如果输入为空,ifne则不执行任何操作并返回状态0,该状态不能与成功运行的命令区分开。如果要在输入为空的情况下执行某些操作,则需要安排命令不返回0,这可以通过使成功案例返回可区分的错误状态来完成:

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0与此无关 它测试标准输入是否为端子。对于任何输入是否可用,它并没有说任何一种方式。


在具有基于STREAMS的管道的系统(Solaris HP / UX)上,我相信您可以使用I_PEEK ioctl来窥视管道中的内容而不消耗它。
斯特凡Chazelas

不幸的是,@StéphaneChazelas无法从* BSD上的管道/ fifo窥视数据,因此没有实现可移植 peek实用程序的观点,该实用程序可以从管道返回实际数据,而不仅仅是返回其中的实际数据。(在4.4 BSD中,386BSD等管道被实现为套接字对,但这是在* BSD的更高版本中删除的-尽管它们保持双向)。
mosvy

bash有一个例程来检查通过a公开的输入read -t 0(在这种情况下,t表示超时,如果您想知道的话)。
艾萨克

11

一个简单的解决方案是使用ifne命令(如果输入不为空)。在某些发行版中,默认情况下未安装它。moreutils在大多数发行版中,它是软件包的一部分。

ifne 当且仅当标准输入不为空时,运行给定命令

请注意,如果标准输入不为空,则将其传递ifne给给定命令


2
截至2017年,Mac或Ubuntu默认情况下不提供该功能。
Sridhar Sarnobat

7

这是一个老问题,但是如果有人像我那样遇到它:我的解决方案是超时阅读。

while read -t 5 line; do
    echo "$line"
done

如果stdin为空,则将在5秒钟后返回。否则,它将读取所有输入,您可以根据需要进行处理。


虽然我喜欢这个主意,但-t可惜它不是POSIX的一部分:pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ

6

检查stdin(0)的文件描述符是打开还是关闭:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"

当您传递一些数据并且想要检查是否有数据时,无论如何都要通过FD,因此这也不是一个很好的测试。
贾库耶

1
[ -t 0 ]检查fd 0是否对tty打开,而不是关闭还是打开。
mosvy

@mosvy您能否详细说明在脚本中使用该解决方案会有什么影响?是否有某些情况下它不起作用?
JepZ

@JepZ是吗?./that_script </dev/null=>“ stdin有数据”。或者./that_script <&-真正关闭标准输入。
mosvy

5

您也可以使用test -s /dev/stdin(在显式子外壳中)。

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

8
对我不起作用。总是说管道是空的。
amphetamachine 2014年

2
在Mac上可以使用,但在Linux机器上则不能。
达到峰值

3

在bash中:

read -t 0 

检测输入是否有数据(不读取任何内容)。然后,您可以读取输入(如果在执行读取时输入可用):

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

注意:请了解,这取决于时间。这将检测输入是否仅在read -t运行时已经有数据。

例如,

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

输出为1(读取失败,即:空输入)。回波写入一些数据,但是启动和写入它的第一个字节不是很快,因此read -t 0,由于程序尚未写入任何内容,因此将报告其输入为空。


github.com/bminor/bash/blob/…-这是bash如何检测文件描述符中有内容的来源。
Pavel Patrin

感谢@PavelPatrin
艾萨克

1
@PavelPatrin 无效。从您的链接bash可以清楚地看到,要么做一个select()要么一个ioctl(FIONREAD),要么做一个,要么都不做,但不能两个都做,因为它应该起作用。read -t0被打破。不要使用它
mosvy

噢,今天我试了两个小时,以了解它的问题所在!谢谢@mosvy!
Pavel Patrin

3

使用FIONREADioctl 是检查Unix是否有可用数据的一种简单方法。

我不能想到任何标准实用程序都可以做到这一点,所以这是一个简单的程序(比ifnefrom moreutils IMHO ;-更好)。

fionread [ prog args ... ]

如果stdin上没有可用数据,它将以状态1退出。如果有数据,它将运行prog。如果未prog给出,它将以状态0退出。

poll如果您仅对立即可用的数据感兴趣,则可以删除呼叫。这应该适用于大多数fds,而不仅仅是管道。

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}

这个程序真的有效吗?您是否也不需要等待POLLHUP事件来处理空箱?如果管道另一端有多个文件描述,那行得通吗?
吉尔斯(Gilles)'所以

是的,它有效。POLLHUP仅通过轮询返回,您应该使用POLLIN等待POLLHUP。管道的任何一端有多少个打开的手柄都没有关系。
mosvy

unix.stackexchange.com/search?q=FIONREAD+user%3A22565如何从perl的运行FIONREAD(比编译器更容易地获得)
斯特凡Chazelas

使用FIONREAD(或HAVE_SELECT)的例程在此处bash中实现
艾萨克

2

如果您喜欢短而神秘的单线:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

我使用了原始问题中的示例。如果您不希望-qgrep 使用管道数据使用选项


0

如果您可以阅读第一行的全部内容,那么这似乎是bash中合理的ifne实现

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo

5
read如果输入为非空但不包含换行符,则对输入也将返回falseread,对其输入进行某些处理,并且可能会读取多行,除非您将其称为IFS= read -r lineecho不能用于任意数据。
斯特凡Chazelas

0

这对我有用 read -rt 0

来自原始问题的示例,没有数据:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi

不,那是行不通的。尝试使用{ sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(假阴性)和true | { sleep .1; read -rt0 && echo YES; }(假阳性)。实际上,read即使在仅写模式下打开的fds也会欺骗bash { read -rt0 && echo YES; cat; } 0>/tmp/foo。它似乎唯一要做的就是select(2)在该FD上执行。
mosvy

...并且select如果f read(2)不会阻塞,则返回fd为“就绪” ,无论返回EOF还是出错。结论:read -t0bash。不要使用它。
mosvy

@mosvy您是否已与bashbug进行了汇报?
艾萨克

@mosvy { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }不是假否定的,因为(在执行读取时)没有输入。后来(睡眠0.1)该输入可用(猫)。
艾萨克

@mosvy为什么r选项影响检测?:echo "" | { read -t0 && echo YES; }打印YES,但echo "" | { read -rt0 && echo YES; }不打印。
艾萨克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.