92

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.

zetah
  • 1,997
  • 4
  • 20
  • 21

17 Answers17

67

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 from 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.

muru
  • 69,900
  • 13
  • 192
  • 292
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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 '19 at 21:01
  • 1
    @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](https://github.com/denghuancong/4.4BSD-Lite/blob/c995ba982d79d1ccaa1e8446d042f4c7f0442d5f/usr/src/sys/kern/uipc_syscalls.c#L971), but that was gutted in later versions of *BSD -- though they kept them bi-directional). –  Mar 29 '19 at 22:07
26

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

HalosGhost
  • 4,732
  • 10
  • 33
  • 41
Nick Wirth
  • 261
  • 3
  • 2
  • 4
    As of 2017, it's not there by default in Mac or Ubuntu. – Sridhar Sarnobat Feb 03 '17 at 01:32
  • I installed `ifne` in FreeBSD 11.3 (more specifically, a FreeBSD 11.3 jail in FreeNAS 11.3) using port https://www.freshports.org/sysutils/moreutils/. I then copied the `ifne` binary to `$HOME/bin` in FreeNAS 11.3 so that I could run it from scripts in FreeNAS home directory. – Derek Mahar Feb 04 '20 at 08:41
25

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

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
mviereck
  • 2,377
  • 1
  • 18
  • 18
  • 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
  • 18
    `[ -t 0 ]` checks if fd 0 is opened to a **tty**, not whether it is closed or open. –  Feb 01 '19 at 07:52
  • 1
    @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 '19 at 20:39
  • 3
    @JepZ huh? `./that_script "stdin has data". Or `./that_script <&-` to have the stdin really *closed*. –  Apr 22 '19 at 20:59
21

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

Some commenters rightfully noted that this version will ignore new lines. My answer is focused on the original question. From this point of view a newline symbol is an empty string. Using grep ^, as suggested by @EvgEnZh solves this corner case, but I am not sure it is what the requestor wanted. Please, add this variant in comments, it is a useful correction.

yashma
  • 311
  • 2
  • 4
16

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.

In these situations where -t is supported, you can test for input before reading any data with -t0. (also specifying -u0 for STDIN because awslinux needs it, whereas ubuntu assumes it)

if [ $(read -u0 -t0) ]; then ....

And you can start reading as normal afterward

from help read:

If TIMEOUT is 0, read returns immediately, without trying to read any data, returning success only if input is available on the specified file descriptor.

roaima
  • 107,089
  • 14
  • 139
  • 261
Ladd
  • 161
  • 1
  • 2
10

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.

This should work with most kinds of file descriptors, 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 < 1) 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 '19 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. –  Mar 29 '19 at 20:15
  • See https://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 '19 at 20:45
  • Why is this better than `ifne` from moreutils? – Derek Mahar Feb 04 '20 at 09:08
  • 2
    @DerekMahar Because a) it's much smaller b) much faster c) it's a single process, it execs through to the command directly. –  Feb 04 '20 at 09:11
  • 2
    @DerekMahar d) it doesn't change the _nature_ of the stdin passed to `cmd`; eg. if the stdin is a socket, `cmd` would still be able to call `getpeername(2)` on it to see who's at the end of the connection. As mentioned by Stéphane Chazelas, you can trivially do this in perl, python, ruby, etc. Not in the shell or with any standard utilities, though. –  Feb 04 '20 at 09:27
  • @mosvy The code may be smaller (I haven't checked `ifne.c`), but at least on FreeBSD 11.3, the default binary that `gcc` builds from `fionread.c` is larger (`fionread` is 7896 bytes and `ifne` is 7440 bytes). – Derek Mahar Feb 04 '20 at 09:43
  • @DerekMahar Strip it. `cc -s ...` or `strip a.out` afterwards. –  Feb 04 '20 at 09:44
  • @mosvy, okay, that reduced the size of binary `fionread` to 5456 bytes, 1984 bytes smaller than `ifne`. My 3 TB hard drive is grateful! :) – Derek Mahar Feb 04 '20 at 09:52
  • fwiw binary sizes should be compared with the `size(1)` utility, and take into account the initialization crtX.o code. I've just compiled the silly thing on FreeBSD 12.1 and it came out 549 bytes larger than `int main(){}` (~4.8 times smaller than `ifne` ;-)) –  Feb 04 '20 at 10:14
  • @mosvy I just ran `size(1)` on each binary, but I'll admit that I don't know how to interpret the output. – Derek Mahar Feb 05 '20 at 03:44
  • @DerekMahar it's the 4th column: the sum of text (= code, machine instructions) + data (= initialized static vars) + [bss](https://en.wikipedia.org/wiki/.bss) (= non-initialized static vars) _in decimal_. Of course, a program may appear deceptively small, either because it depends on a lot of shared libraries that nobody else is using (`ldd` it) or because it bloats itself at runtime by doing a lot of non-transient dynamic allocations. Not the case with either `ifne` or my little example, they all depend on just `libc.so`, which is 100% sure already mapped in memory by the time they're run. –  Feb 05 '20 at 07:36
  • `size fionread` => 2691 bytes and `size /usr/local/bin/ifne` => 4479 bytes – Derek Mahar Feb 05 '20 at 21:45
  • Why `FIONREAD` and not just `poll()`? – psqli Feb 04 '23 at 08:30
7

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.

  • 1
    https://github.com/bminor/bash/blob/64447609994bfddeef1061948022c074093e9a9f/lib/sh/input_avail.c#L62 - here is source of how bash detects that something is in file descriptor. – Pavel Patrin Jun 12 '19 at 13:27
  • 3
    @PavelPatrin [That does not work](https://unix.stackexchange.com/questions/33049/how-to-check-if-a-pipe-is-empty-and-run-a-command-on-the-data-if-it-isnt/498065?noredirect=1#comment916641_497121). 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](https://unix.stackexchange.com/questions/33049/how-to-check-if-a-pipe-is-empty-and-run-a-command-on-the-data-if-it-isnt/498065?noredirect=1#comment916652_497121) –  Jun 12 '19 at 18:51
  • Oooh, today i try to understand what is wrong with it for two hours! Thank you, @mosvy! – Pavel Patrin Jun 12 '19 at 19:34
  • @mosvy If that will not work why does it work on my system? –  Dec 18 '19 at 15:17
  • It‎ does‎n‎'‎t. –  Dec 18 '19 at 16:22
  • Then, please, send an email to `bug-bash ` to correct [their opinion](https://lists.gnu.org/archive/html/bug-bash/2019-12/msg00082.html). @mosvy –  Dec 20 '19 at 23:09
  • Your solution (which is but a pointless duplicate of an older [answer](https://unix.stackexchange.com/a/497121/308316)) is broken and does not work, as I've already demonstrated with simple testcases in the original answer. The people at [email protected] better fix their broken stuff than entertain s and equivocate about my coding suggestions. –  Dec 21 '19 at 07:26
4

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')
trent55
  • 57
  • 1
  • 1
4

If you need this functionality for use in xargs and you have GNU xargs, you can use xargs -r. Ex. curl won't run here:

echo -n '' | xargs -r curl 
qwr
  • 647
  • 7
  • 11
  • There's a lot of answers [here](https://stackoverflow.com/questions/8296710/how-to-ignore-xargs-commands-if-stdin-input-is-empty) on how to do this on non GNU xargs – CervEd Sep 20 '22 at 09:12
2

The following approach worked like a charm for me:

function _read_from_stdin() {
  less <&0 2>/dev/null
}

It reads from STDIN and returns its contents (if any). All you need to do afterwards is to check if contents is empty:

stdin_contents="$(_read_from_stdin)"

if [[ ! "${stdin_contents}" ]] ; then
  echo "STDIN is empty!"
else
  echo "Have something in STDIN: ${stdin_contents}"
fi
1

Another hack that you can try is using timeout to read from /dev/stdin like this:

timeout 1s cat /dev/stdin > /tmp/input
if [ $? -eq 124 ] && ! [ -s /tmp/input ]
then
  echo "No input provided."
else
  echo "Input provided"
  # Optionally, you can move or read from /tmp/input...whatever your program needs to do.
fi
Carlos Nunez
  • 111
  • 2
1

Use grep

chris@SR-ENG-P18 /cygdrive/c/Projects
$ ( ! echo -n "" | grep -q '.' && echo "empty" || echo "fill" )
empty

chris@SR-ENG-P18 /cygdrive/c/Projects
$ ( ! echo -n "string" | grep -q '.' && echo "empty" || echo "fill" )
fill

grep will return 1 (i.e. error) when nothing is matched. You want the opposite thus the not operator !.

Be sure to use echo -n "" in your testing because echo "" will output a newline.

shrewmouse
  • 344
  • 1
  • 3
  • 12
  • `grep` will consume the pipe and block when it becomes empty. Also [there is a grep answer](https://unix.stackexchange.com/a/509498/454969) already. – EvgenKo423 Feb 10 '21 at 16:16
  • @EvgenKo423, that answer makes no attempt to explain why this works. 1 for false and 0 for true is a subtlety that needs to be explained. – shrewmouse Feb 10 '21 at 16:50
  • Please note that if you have some minor improvements for a question or answer, you should use Edit. – EvgenKo423 Feb 10 '21 at 17:12
1

Full POSIX solution

Compile the following file containing a call to poll() into a binary called has_data:

#include <poll.h>
int main() {
    struct pollfd fds = { 0, POLLIN, 0};
    poll(&fds, 1, -1);
    return fds.revents & POLLIN ? 0 : 1;
}

gcc -o has_data has_data.c

Usage

with a pipeline

cat only runs if has_data succeeds

echo foobar | ( has_data && cat ) | ...

with a file descriptor

N is the number of the file descriptor opened in the current Shell Execution Environment

if has_data <&N; then
    echo "Has data"
fi

with a filename

if has_data <filename; then
    echo "Has data"
fi

There is a problem with this approach when filename is a FIFO, though. According to the documentation of close:

When all file descriptors associated with a pipe or FIFO special file are closed, any data remaining in the pipe or FIFO shall be discarded.

This means that when has_data closes the FIFO file and the file is not opened by another process, the data yet to be read will be lost.

psqli
  • 292
  • 1
  • 5
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
  • 6
    `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 08: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
  • 2
    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. –  Feb 01 '19 at 08:08
  • 2
    ... 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. –  Feb 01 '19 at 08:38
0

Another approach is reformulating the problem as a pure problem of running a command with the input from a stream and massaging that stream to fit your use case. Ie, you want a command to operate on a stream of data and this data may come from stdin and/or another stream(s).

AND

If its stdin and another stream it should be pretty straightforward using cat

echo bar |
  cat - <(echo biz) |
  cat -A
# output: bar$biz$

This will pipe the contents of stdin and the output of the process substitution echo bar to the stdin of cat -A.

OR

Or on the other hand is a bit trickier. Let's say you want to run a command with input from stdin but fallback to something else if nothing was passed to stdin.

In this case an approach is reformulate the problem as combining streams (including a delimiter sequence say two null-bytes) and sinking the stream IFF stdin is non-empty by using sed and matching the delimiter in the combined stream.

echo bar |
  cat - <(printf '\x0\x0\n') <(echo biz) |
  sed \
    -e '1{/\x0\x0/s@@@;N;s@\n@@}' \
    -e '1!{/\x0\x0/s@@@;q}' | # stdin was non-empty, sink the rest
    cat -A
# output: bar$


< /dev/null |
  cat - <(printf '\x0\x0\n') <(echo biz) |
  sed \
    -e '1{/\x0\x0/s@@@;N;s@\n@@}' \
    -e '1!{/\x0\x0/s@@@;q}' | # stdin was non-empty, sink the rest
    cat -A
# output: biz$

We can combine this approach with xargs -I% for an additional fallback. It uses process substitution but other than that it should be pretty POSIX compatible.

#!/bin/bash

# This script takes a list of paths from stdin
# IFF it's empty the list of files is the existing file 'foo'
# Each file and/or directory in this list is passed to stat
# IFF this list is empty we run stat on the current directory

combined_stream() {
  if [ -t 0 ]; then
    cat <(printf '\x0\x0\n') <(find foo -type f 2> /dev/null)
  else
    cat - <(printf '\x0\x0\n') <(find foo -type f 2> /dev/null)
  fi
}

combined_stream |
  sed \
    -e '1{/\x0\x0/s@@@;N;s@\n@@}' \
    -e '1!{/\x0\x0/s@@@;q}' | # stdin was non-empty, sink the rest
  xargs -I% stat % |
  grep . || stat .
CervEd
  • 156
  • 7
-1

Simplest ternary

doSomethingAndCheckTruth && echo 'yes' || echo 'no'

The right part (after the &&) will not run unless the left part is complete, so you only check truth upon successfully running something else, saving computation.

Example

using homebrew and grep we check if the bat program is installed

  • If true you will see "yes"
  • If false you will see "no"

I added the -q to suppress the grepped string output here, so you only see "yes" or "no"

brew list | grep -q bat && echo 'yes' || echo 'no'

Tested with bash and zsh

jasonleonhard
  • 523
  • 5
  • 7