smart
smart

Reputation: 2065

Check if command error contains a substring

I have a lot of bash commands.Some of them fail for different reasons. I want to check if some of my errors contain a substring.

Here's an example:

#!/bin/bash

if [[ $(cp nosuchfile /foobar) =~ "No such file" ]]; then
    echo "File does not exist. Please check your files and try again."
else
    echo "No match"
fi

When I run it, the error is printed to screen and I get "No match":

$ ./myscript
cp: cannot stat 'nosuchfile': No such file or directory
No match

Instead, I wanted the error to be captured and match my condition:

$ ./myscript
File does not exist. Please check your files and try again.

How do I correctly match against the error message?

P.S. I've found some solution, what do you think about this?

out=`cp file1 file2 2>&1`
if [[ $out =~ "No such file" ]]; then
    echo "File does not exist. Please check your files and try again."
elif [[ $out =~ "omitting directory" ]]; then
    echo "You have specified a directory instead of a file"
fi

Upvotes: 6

Views: 3766

Answers (3)

Grzegorz Olbryś
Grzegorz Olbryś

Reputation: 1

Both examples:

out=`cp file1 file2 2>&1`

and

case $(cp file1 file2 2>&1) in 

have the same issue because they mixing the stderr and stdout into one output which can be examined. The problem is when you trying the complex command with interactive output i.e top or ddrescueand you need to preserve stdout untouched and examine only the stderr. To omit this issue you can try this (working only in bash > v4.2!):

shopt -s lastpipe
declare errmsg_variable="errmsg_variable UNSET"

command 3>&1 1>&2 2>&3 | read errmsg_variable

if [[ "$errmsg_variable" == *"substring to find"* ]]; then
    #commands to execute only when error occurs and specific substring find in stderr
fi

Explanation

This line

command 3>&1 1>&2 2>&3 | read errmsg_variable

redirecting stderr to the errmsg_variable (using file descriptors trick and pipe) without mixing with stdout. Normally pipes spawning own sub-processes and after executing command with pipes all assignments are not visible in the main process so examining them in the rest of code can't be effective. To prevent this you have to change standard shell behavior by using:

shopt -s lastpipe

which executes last pipe manipulation in command as in the current process so:

| read errmsg_variable

assignes content "pumped" to pipe (in our case error message) into variable which resides in the main process. Now you can examine this variable in the rest of code to find specific sub-string:

if [[ "$errmsg_variable" == *"substring to find"* ]]; then
    #commands to execute only when error occurs and specific substring find in stderr
fi

Upvotes: 0

that other guy
that other guy

Reputation: 123490

PSkocik's answer is the correct one for one you need to check for a specific string in an error message. However, if you came looking for ways to detect errors:

I want to check whether or not a command failed

Check the exit code instead of the error messages:

if cp nosuchfile /foobar
then
  echo "The copy was successful."
else
  ret="$?"
  echo "The copy failed with exit code $ret"
fi

I want to differentiate different kinds of failures

Before looking for substrings, check the exit code documentation for your command. For example, man wget lists:

EXIT STATUS
   Wget may return one of several error codes if it encounters problems.

   0   No problems occurred.
   1   Generic error code.
   2   Parse error---for instance, when parsing command-line options
   3   File I/O error.
   (...)

in which case you can check it directly:

wget "$url"
case "$?" in
    0) echo "No problem!";;
    6) echo "Incorrect password, try again";;
    *) echo "Some other error occurred :(" ;;
esac

Not all commands are this disciplined in their exit status, so you may need to check for substrings instead.

Upvotes: 1

Petr Skocik
Petr Skocik

Reputation: 60067

I'd do it like this

# Make sure we always get error messages in the same language
# regardless of what the user has specified.
export LC_ALL=C

case $(cp file1 file2 2>&1) in 
    #or use backticks; double quoting the case argument is not necessary
    #but you can do it if you wish
    #(it won't get split or glob-expanded in either case)
    *"No such file"*)
        echo >&2 "File does not exist. Please check your files and try again." 
        ;;
    *"omitting directory"*)
        echo >&2 "You have specified a directory instead of a file"
        ;;
esac

This'll work with any POSIX shell too, which might come in handy if you ever decide to convert your bash scripts to POSIX shell (dash is quite a bit faster than bash).

You need the first 2>&1 redirection because executables normally output information not primarily meant for further machine processing to stderr. You should use the >&2 redirections with the echos because what you're ouputting there fits into that category.

Upvotes: 8

Related Questions