Please see the first part of this other answer of mine and links therein. You want to detect a string. Making curl fail according to HTML error code seems more elegant. If it's possible to get the desired result this way then it's the Right Way.
Otherwise use the following general-purpose shell function:
failon() (
pattern="$1"
shift
[ "$#" -eq 0 ] && set cat
FAILON_STATUS="${FAILON_STATUS:-125}"
set -o pipefail
{ "$@" \
| tee -p /proc/self/fd/3 \
| { grep -q -- "$pattern" && return "$FAILON_STATUS" || return 0; }
} 3>&1
)
Usage:
failon [pattern [command [arg...]]]
The main purpose of the function is to return non-zero exit status if the output of command [arg...] contains a line matching the pattern. The output is printed to the stdout of the function, regardless of whether there is a match or not.
Example:
failon ERROR curl …
Notes and explanations:
The pattern is supplied as-is to grep, so it's a regex. An empty or undefined pattern will match any line, if only there is a line.
The function should work in bash, zsh and many other shells. AFAIK the shell syntax I used is portable. Non-portable things are independent from the shell, except set -o pipefail which belongs to the shell and it's not portable. I mean not yet. It's widely supported and it's going to be added to the POSIX standard.
Thanks to pipefail, if the pattern is not found and tee doesn't fail (normally it shouldn't fail) then the function will return the exit status of the specified command (e.g. curl).
If the pattern is found then the function will return 125. I chose 125 because:
You can adjust this number to your needs via FAILON_STATUS environment variable. E.g. FAILON_STATUS=120 failon …. Note FAILON_STATUS=0 is technically valid but rather useless.
tee -p is not portable. Without -p tee will exit if it gets SIGPIPE after grep exits early. grep -q exits as soon as it finds the pattern. Thanks to -p tee will relay its whole input to /proc/self/fd/3 (which is ultimately the stdout of the function), even if grep exits early.
If you tee does not support -p, the most straightforward workaround is to use grep -- "$pattern" >/dev/null. Now grep will silently process all the data, it will not exit early. The downside is it will do unnecessary job matching lines after the pattern is first found.
/proc/self/fd/… is not portable; hopefully your OS supports it. Even if it doesn't, there's always a method of curl … | tee /temporary/file followed by ! grep -q ERROR /temporary/file to get falsy exit status upon ERROR. This method is quite straightforward and KISS, but:
in one of your comments you said you don't want to save the output to a file;
in general it's not easy to safely and reliably create a /temporary/file; mktemp is the right tool, but it's not specified by POSIX.
In zsh you can do "$@" >&3 | { grep -q … (instead of "$@" | tee … | { grep -q …) and thus get rid of tee -p /proc/self/fd/3 and any potential problem from it.
-- is explained here: What does -- (double-dash) mean?
If command is not specified then the function will use cat. This allows you to use failon as a "filter":
curl … | failon ERROR
Note it's easy to lose the exit status of curl this way. On the other hand it's possible to do this:
curl … | failon ERROR | failon WARNING
and examine PIPESTATUS (in bash) or pipestatus (in zsh) to detect ERROR and WARNING in the output independently.
Some programs change their behavior, depending on stdout being a terminal, a pipe or a regular file (see how to trick a command into thinking its output is going to a terminal). Our function internally uses a pipe, so in some cases the output from failon foo program will be different than the output from the same program invoked directly.