42

I have piped a line in bash script and want to check if the pipe has data, before feeding it to a program.

Searching I found about test -t 0 but it doesn't work here. Always returns false. So how to be sure that the pipe has data?

Example:

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

Output: fill

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

Output: fill

Unlike Standard/canonical way to test whether foregoing pipeline produced output? the input needs to be preserved to pass it to the program. This generalizes How to pipe output from one process to another but only execute if the first has output? which focuses on sending email.

10 Answers 10

37

There's no way to peek at the content of a pipe using commonly available shell utilities, nor is there a way to read a character to the pipe then put it back. The only way to know that a pipe has data is to read a byte, and then you have to get that byte to its destination.

So do just that: read one byte; if you detect an end of file, then do what you want to do when the input is empty; if you do read a byte then fork what you want to do when the input is not empty, pipe that byte into it, and pipe the rest of the data.

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

The ifne utility from Joey Hess's moreutils runs a command if its input is not empty. It usually isn't installed by default, but it should be available or easy to build on most unix variants. If the input is empty, ifne does nothing and returns the status 0, which cannot be distinguished from the command running successfully. If you want to do something if the input is empty, you need to arrange for the command not to return 0, which can be done by having the success case return a distinguishable error status:

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

test -t 0 has nothing to do with this; it tests whether standard input is a terminal. It doesn't say anything one way or the other as to whether any input is available.

  • On systems with STREAMS based pipes (Solaris HP/UX), I believeve you can use the I_PEEK ioctl to peek at what's on a pipe without consuming it. – Stéphane Chazelas Mar 29 at 21:01
  • @StéphaneChazelas unfortunately there's no way to peek data from a pipe/fifo on *BSD, so no perspective to implement a portable peek utility which could return the actual data from a pipe, not just how much of it there is. (in 4.4 BSD, 386BSD etc pipes were implemented as socket pairs, but that was gutted in later versions of *BSD -- though they kept them bi-directional). – mosvy Mar 29 at 22:07
  • bash has a routine to check input exposed via a read -t 0 (t in this case means timeout, if you wonder). – Isaac Jun 12 at 18:27
11

A simple solution is to use ifne command (if input not empty). In some distributions, it is not installed by default. It is a part of the package moreutils in most distros.

ifne runs a given command if and only if the standard input is not empty

Note that if the standard input is not empty, it is passed through ifne to the given command

  • 2
    As of 2017, it's not there by default in Mac or Ubuntu. – Sridhar Sarnobat Feb 3 '17 at 1:32
7

Old question, but in case someone comes across it as I did: My solution is to read with a timeout.

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

If stdin is empty, this will return after 5 seconds. Otherwise it will read all the input and you can process it as needed.

6

check if file descriptor of stdin (0) is open or closed:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
  • When you pass some data and you want to check if there is some, you pass the FD anyway so this is also not a good test. – Jakuje Jan 31 '18 at 15:40
  • 1
    [ -t 0 ] checks if fd 0 is opened to a tty, not whether it is closed or open. – mosvy Feb 1 at 7:52
  • @mosvy could you please elaborate on how that would affect using that solution in a script? Are there cases when it doesn't work? – JepZ Apr 22 at 20:39
  • @JepZ huh? ./that_script </dev/null => "stdin has data". Or ./that_script <&- to have the stdin really closed. – mosvy Apr 22 at 20:59
5

You may use test -s /dev/stdin (in an explicit subshell) as well.

# 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
    Doesn't work for me. Always says pipe is empty. – amphetamachine Dec 15 '14 at 16:53
  • 2
    Works on my Mac, but not on my Linux box. – peak Jul 5 '16 at 17:45
3

In bash:

read -t 0 

Detects if an input has data (without reading anything). Then you can read the input (if the input is available at the time the read is executed):

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

Note: Understand that this depends on timing. This detects if input already has data only at the time read -t runs.

For example, with

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

the output is 1 (read failure, i.e.: empty input). The echo writes some data but is not very fast to start and write its first byte, thus, read -t 0 will report that its input is empty, since program has not written anything yet.

  • github.com/bminor/bash/blob/… - here is source of how bash detects that something is in file descriptor. – Pavel Patrin Jun 12 at 13:27
  • Thanks @PavelPatrin – Isaac Jun 12 at 18:30
  • 1
    @PavelPatrin That does not work. As clearly seen from your link, bash will either do a select() or an ioctl(FIONREAD), or neither of them, but not both, as it should for it to work. read -t0 is broken. Do not use it – mosvy Jun 12 at 18:51
  • Oooh, today i try to understand what is wrong with it for two hours! Thank you, @mosvy! – Pavel Patrin Jun 12 at 19:34
3

If you like short and cryptic one-liners:

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

I used the examples from the original question. If you don't want the piped data use -q option with grep

3

One easy way to check if there's data available for reading in Unix is with the FIONREAD ioctl.

I cannot think of any standard utility doing just that, so here is a trivial program doing it (better than the ifne from moreutils IMHO ;-)).

fionread [ prog args ... ]

If there's no data available on stdin, it will exit with status 1. If there's data, it will run prog. If no prog is given, it will exit with status 0.

You can remove the poll call if you're only interested in data immediately available. This should work with most kinds of fds, not just pipes.

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);
}
  • Does this program actually work? Don't you need to wait for a POLLHUP event as well to handle the empty case? Does that work if there are multiple file descriptions on the other end of the pipe? – Gilles 'SO- stop being evil' Mar 29 at 19:33
  • Yes it works. POLLHUP is only returned by poll, you should use POLLIN to wait for a POLLHUP. It doesn't matter how many open handles are to any end of the pipe. – mosvy Mar 29 at 20:15
  • See unix.stackexchange.com/search?q=FIONREAD+user%3A22565 for how to run FIONREAD from perl (more commonly available than compilers) – Stéphane Chazelas Mar 29 at 20:45
  • The routine that use FIONREAD (or HAVE_SELECT) is implemented in bash here. – Isaac Jun 12 at 18:29
0

This seems to be a reasonable ifne implementation in bash if you're ok with reading the whole first line

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 will also return false if the input is non-empty but contains no newline character, read does some processing on its input and may read more than one line unless you call it as IFS= read -r line. echo can't be used for arbitrary data. – Stéphane Chazelas May 27 '15 at 8:34
0

This works for me using read -rt 0

example from original question, with no data:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi
  • no, that doesn't work. try with { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; } (false negative) and true | { sleep .1; read -rt0 && echo YES; } (false positive). In fact, bash's read will be fooled even by fds opened in write-only mode: { read -rt0 && echo YES; cat; } 0>/tmp/foo. The only thing it seem to do is a select(2) on that fd. – mosvy Feb 1 at 8:08
  • ... and select will return a fd as "ready" if a read(2) on it would not block, no matter if it will return EOF or an error. Conclusion: read -t0 is broken in bash. Don't use it. – mosvy Feb 1 at 8:38
  • @mosvy Have you reported it with bashbug? – Isaac Jun 12 at 21:50
  • @mosvy The { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; } is not a false negative because (at the time the read is executed) there is no input. Later (sleep .1) that input is available (for the cat). – Isaac Jun 12 at 21:55
  • @mosvy Why does the r option affects detection?: echo "" | { read -t0 && echo YES; } prints YES but echo "" | { read -rt0 && echo YES; } doesn't. – Isaac Jun 12 at 22:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.