Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
How to pipe grep(1) and use compatible exit codes?
I need to wrap grep(1) in a script, and I'd like the script to have an exit code compatible with that of grep(1) --that is, it should return 0 if it found something, 1 if it didn't, and 2 on error--.
But I don't find a way to do it. So far, I've written this:
alx@devuan:~/tmp/g$ ls
file file2 script
alx@devuan:~/tmp/g$ cat file
foo
bar
alx@devuan:~/tmp/g$ cat file2
qwe
xcsv
alx@devuan:~/tmp/g$ cat script
#!/bin/bash
set -Eeuo pipefail;
shopt -s lastpipe;
trap 'exit 2;' ERR;
if test -z "$*"; then
trap 'exit 1' ERR;
grep foo;
else
find "$@" -type f \
| {
trap 'exit 1' ERR;
xargs grep foo;
};
fi;
alx@devuan:~/tmp/g$ echo foo | bash script; echo $?
foo
0
alx@devuan:~/tmp/g$ echo bar | bash script; echo $?
1
alx@devuan:~/tmp/g$ bash script file; echo $?
foo
0
alx@devuan:~/tmp/g$ bash script file2; echo $?
1
alx@devuan:~/tmp/g$ bash script file3; echo $?
find: ‘file3’: No such file or directory
1
If find(1) fails, it uses the trap handler set next to grep(1), probably because pipes are executed in parallel, and because lastpipe makes the trap(1) be executed in the same shell.
If I change the script to not use lastpipe, then that is fixed (find(1) errors are properly transformed into code 2), but then when grep(1) doesn't find anything it also is treated as an error with code 2.
alx@devuan:~/tmp/g$ cat script
#!/bin/bash
set -Eeuo pipefail;
#shopt -s lastpipe;
trap 'exit 2;' ERR;
if test -z "$*"; then
trap 'exit 1' ERR;
grep foo;
else
find "$@" -type f \
| {
trap 'exit 1' ERR;
xargs grep foo;
};
fi;
alx@devuan:~/tmp/g$ bash script file; echo $?
foo
0
alx@devuan:~/tmp/g$ bash script file2; echo $?
2
alx@devuan:~/tmp/g$ bash script file3; echo $?
find: ‘file3’: No such file or directory
2
Is there a way to set different traps for different parts of a pipeline?
Alternatively, I guess I could entirely disable set -Ee, and inspect PIPESTATUS for each command, but I was hoping for something simpler. Also, the real script is much more complex, and I think PIPESTATUS would be impractical, so I'd probably fall back to grep || true and say my script also returns 0 if nothing matched, unlike grep(1).
1 answer
Instead of trying to juggle conditional traps, it's far simpler to exit conditionally or send a signal when commands or command groups fail.
set -Eeuo pipefail
shopt -s lastpipe
# Allow a subshell to signal the parent process for a fatal error.
trap 'exit 2' USR1
if test -z "$*"; then
grep foo
# grep's exit codes are assumed to be compatible with the wrapper's.
else
{ find "$@" -type f || kill -USR1 "$$"; } \
| xargs grep foo || exit 1
fi
(Side note: you don't; need; to go nuts; with semicolons! They are equivalent to line terminators, so they're never necessary at the end of a line or a trap handler.)

0 comment threads