Reputation: 3285
I want to write a function that always have a non empty output or fails, but I'm missing a command that read stdin and pipe it to stdout if non-empty or fails like:
example() {
do_something_interesting_here $1 | cat_or_fails
}
The idea is that if the command cat_or_fails
is given an empty input it fails (so the function fails) or the input is output without any changes (like cat
).
But I could not find any standard utility capable of that trick, or may be I'm not sure how to use those tools.
Upvotes: 2
Views: 480
Reputation: 26481
There are various ways of doing this, but the simplest is using awk
:
function cat_or_fail () { awk '1;END{exit !NR}' "$@"; }
Here, awk
returns a non-zero status if not a single line is read.
Alternatively, a tool available in the moreutils
package. The command ifne
allows one to execute a command, depending if /dev/stdin
is empty or not:
ifne command
: run command
if the standard input is not emptyifne -n command
: run command
if the standard input is empty. If the standard input is not empty, it sends standard input to standard output.The latter is essentially what the OP expects. The function cat_or_fail
would look like
function cat_or_fail () { ifne -n false; }
Which, if the OP wants to have a similar behaviour as cat
, you could write it as:
function cat_or_fail () { cat -- "${@}" | ifne -n false; }
The latter can take files as arguments as well as read input from pipes similar to cat
.
If you want to expand this to command_or_fail
, where command
is executed using standard input, or fail. You have to work a bit differently.
The return status of ifne
is unaffected by the content of the standard input (empty or not). So you can not use it in an and-or sequence list (foo && bar || baz
).
In order to create the requested behaviour, you need to use the pipefail
option
$ set -o pipefail
$ function command_or_fail() { ifne -n false | ifne "$@"; }
$ echo foo | command_or_fail cat -n
1 foo
$ echo $?
0
$ </dev/null | command_or_fail cat -n
$ echo $?
1
Upvotes: 1
Reputation: 295510
A simple algorithm to accomplish this is trivial and obvious: Try to read one byte. If it works, write that byte back out and then run cat
. If it fails, exit with a nonzero status.
Both below variants can be used in the manner described in the question (... | cat_or_fails
).
A very simple implementation (that doesn't try to handle binary files starting with the NUL character) would look like:
cat_or_fails() {
local firstbyte
IFS= read -r -n 1 firstbyte || return
printf '%s' "${firstbyte:-$'\n'}"
cat
}
A slightly less simple implementation that does try to handle binary files correctly might look like:
cat_or_fails() {
local firstbyte
IFS= read -r -d '' -n 1 firstbyte || return
if [[ $firstbyte ]]; then
printf '%s' "$firstbyte"
else
printf '\0'
fi
cat
}
Upvotes: 1
Reputation: 3776
My usual trick to assure that at least one line of a text file is found follows. It is all standard shell stuff... bash, ksh, zsh.
example() {
set -o pipefail #assure failure on do_something is not suppressed
do_something_interesting_here $1 |
grep . #assure no output returns error
}
If you need pipefail to cancel when the function returns:
example() {
( #isolate pipefail in subshell
set -o pipefail #assure failure on do_something is not suppressed
do_something_interesting_here $1 |
grep . #assure no output returns error
)
}
Upvotes: 1
Reputation: 3285
Following the idea of @William Pursell in the comment it seems that grep can do the trick using something like:
example() {
do_something_interesting_here $1 | grep "[[:alnum:]]"
}
As mentioned in the comments, it will consume any empty lines (or any line that don't match the regex given).
Upvotes: 1