This question is a follow-up to Bash conditional based on exit code of command .
I understand, and have made use of the accepted answer to that question:
$ cat ./txt
foo
bar
baz
$ if ! grep -Fx bax txt &>/dev/null; then echo "not found"; fi
not found
$
But I can't figure out the syntax to pull off a seemingly simple twist to the premise: how can I write a conditional for a specific exit code of a command?
This post pointed out that grep might fail with error code > 1; so I wanted to write the above conditional to print "not found" specifically if grep returned error code 1. How can I write such a statement? The following don't seem to work (I'm obviously flailing):
$ # Sanity-check that grep really fails with error code 1:
$ grep -Fx bax txt &>/dev/null
$ echo $?
1
$
$
$ if grep -Fx bax txt &>/dev/null -eq 1; then echo "not found"; fi
$ if grep -Fx bax txt &>/dev/null == 1; then echo "not found"; fi
$ if grep -Fx bax txt &>/dev/null = 1; then echo "not found"; fi
$ if [ grep -Fx bax txt &>/dev/null -eq 1 ]; then echo "not found"; fi
$ if [ grep -Fx bax txt &>/dev/null == 1 ]; then echo "not found"; fi
$ if [ grep -Fx bax txt &>/dev/null = 1 ]; then echo "not found"; fi
$
Note: I'm specifically trying to avoid running the command first, then using $? in the conditional, for the reasons pointed out by the accepted answer to the first noted post.
If you want to respond differently to different error codes (as opposed to just success/failure, as in the linked question), you need to run the command and then check $? to get its specific exit status:
grep -Fx bax txt &>/dev/null
if [ $? -eq 1 ]; then
echo "not found"
fi
(In my answer to the linked question, I described testing whether $? is zero as cargo cult programming; this does not apply to checking it for specific nonzero values.)
Note that $? is re-set for every command that runs -- including one that tests $? from the previous command -- so if you want to do more than one thing with it you must immediately store it in a variable, then run your tests on that variable:
curl "$url"
curl_status=$?
if [ "$curl_status" -eq 6 ]; then
echo "Couldn't resolve host name"
elif [ "$curl_status" -eq 7 ]; then
echo "Couldn't connect to host"
elif [ "$curl_status" -ne 0 ]; then
echo "Some sort of error occurred; curl status was $curl_status"
fi
Note that using ! to negate the success/failure of a command effectively destroys the specific error code, because it converts all nonzero codes to the same result: zero. If you want to do the equivalent of if ! command ... and still have access to the specific code, my favorite idiom is to use || and a brace group, like this:
curl "$url" || {
curl_status=$?
...
# Do error handling/reporting based on $curl_status
...
}
Related
I can't tell if something I'm trying here is simply impossible or if I'm really lacking knowledge in bash's syntax. This is the first script I've written.
I've got a Nextcloud instance that I am backing up daily using a script. I want to log the output of the script as it runs to a log file. This is working fine, but I wanted to see if I could also pipe the Nextcloud occ command's output to the log file too.
I've got an if statement here checking if the file scan fails:
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
This works fine and I am able to handle the error if the system cannot execute the command. The error string above is sent to this function:
Print()
{
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1" | tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
echo "$1" >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1"
fi
}
How can I make it so the output of the occ command is also piped to the Print() function so it can be logged to the console and log file?
I've tried piping the command after ! using | Print without success.
Any help would be appreciated, cheers!
The Print function doesn't read standard input so there's no point piping data to it. One possible way to do what you want with the current implementation of Print is:
if ! occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
Print "'occ' output: $occ_output"
Since there is only one line in the body of the if statement you could use || instead:
occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1) \
|| Print "Error: Failed to scan files. Are you in maintenance mode?"
Print "'occ' output: $occ_output"
The 2>&1 causes both standard output and error output of occ to be captured to occ_output.
Note that the body of the Print function could be simplified to:
[[ $quiet_mode == No ]] && printf '%s\n' "$1"
(( logging )) && printf '%s\n' "$1" >> "$log_file"
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I replaced echo "$1" with printf '%s\n' "$1".
How's this? A bit unorthodox perhaps.
Print()
{
case $# in
0) cat;;
*) echo "$#";;
esac |
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
cat >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
cat
fi
}
With this, you can either
echo "hello mom" | Print
or
Print "hello mom"
and so your invocation could be refactored to
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
echo "Error: Failed to scan files. Are you in maintenance mode?"
fi |
Print
The obvious drawback is that piping into a function loses the exit code of any failure earlier in the pipeline.
For a more traditional approach, keep your original Print definition and refactor the calling code to
if output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
: nothing
else
Print "error $?: $output"
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
I would imagine that the error message will be printed to standard error, not standard output; hence the addition of 2>&1
I included the error code $? in the error message in case that would be useful.
Sending and receiving end of a pipe must be a process, typically represented by an executable command. An if statement is not a process. You can of course put such a statement into a process. For example,
echo a | (
if true
then
cat
fi )
causes cat to write a to stdout, because the parenthesis put it into a child process.
UPDATE: As was pointed out in a comment, the explicit subprocess is not needed. One can also do a
echo a | if true
then
cat
fi
What would be the best way to check the exit status in an if statement in order to echo a specific output?
I'm thinking of it being:
if [ $? -eq 1 ]
then
echo "blah blah blah"
fi
The issue I am also having is that the exit statement is before the if statement simply because it has to have that exit code. Also, I know I'm doing something wrong since the exit would obviously exit the program.
Every command that runs has an exit status.
That check is looking at the exit status of the command that finished most recently before that line runs.
If you want your script to exit when that test returns true (the previous command failed) then you put exit 1 (or whatever) inside that if block after the echo.
That being said, if you are running the command and are wanting to test its output, using the following is often more straightforward.
if some_command; then
echo command returned true
else
echo command returned some error
fi
Or to turn that around use ! for negation
if ! some_command; then
echo command returned some error
else
echo command returned true
fi
Note though that neither of those cares what the error code is. If you know you only care about a specific error code then you need to check $? manually.
Note that exit codes != 0 are used to report errors. So, it's better to do:
retVal=$?
if [ $retVal -ne 0 ]; then
echo "Error"
fi
exit $retVal
instead of
# will fail for error codes == 1
retVal=$?
if [ $retVal -eq 1 ]; then
echo "Error"
fi
exit $retVal
An alternative to an explicit if statement
Minimally:
test $? -eq 0 || echo "something bad happened"
Complete:
EXITCODE=$?
test $EXITCODE -eq 0 && echo "something good happened" || echo "something bad happened";
exit $EXITCODE
$? is a parameter like any other. You can save its value to use before ultimately calling exit.
exit_status=$?
if [ $exit_status -eq 1 ]; then
echo "blah blah blah"
fi
exit $exit_status
For the record, if the script is run with set -e (or #!/bin/bash -e) and you therefore cannot check $? directly (since the script would terminate on any return code other than zero), but want to handle a specific code, #gboffis comment is great:
/some/command || error_code=$?
if [ "${error_code}" -eq 2 ]; then
...
Just to add to the helpful and detailed answer:
If you have to check the exit code explicitly, it is better to use the arithmetic operator, (( ... )), this way:
run_some_command
(($? != 0)) && { printf '%s\n' "Command exited with non-zero"; exit 1; }
Or, use a case statement:
run_some_command; ec=$? # grab the exit code into a variable so that it can
# be reused later, without the fear of being overwritten
case $ec in
0) ;;
1) printf '%s\n' "Command exited with non-zero"; exit 1;;
*) do_something_else;;
esac
Related answer about error handling in Bash:
Raise error in a Bash script
If you are writing a function – which is always preferred – you can propagate the error like this:
function()
{
if <command>; then
echo worked
else
return
fi
}
Now, the caller can do things like function && next as expected! This is useful if you have a lot of things to do in the if block, etc. (otherwise there are one-liners for this). It can easily be tested using the false command.
Using Z shell (zsh) you can simply use:
if [[ $(false)? -eq 1 ]]; then echo "yes" ;fi
When using Bash and set -e is on, you can use:
false || exit_code=$?
if [[ ${exit_code} -ne 0 ]]; then echo ${exit_code}; fi
This might only be useful in a limited set of use-cases, I use this specifically when I need to capture the output from a command and write it to a log file if the exit code reports that something went wrong.
RESULT=$(my_command_that_might_fail)
if (exit $?)
then
echo "everything went fine."
else
echo "ERROR: $RESULT" >> my_logfile.txt
fi
you can just add this if statement:
if [ $? -ne 0 ];
then
echo 'The previous command was not executed successfully';
fi
Below test scripts below work for
simple bash test commands
multiple test commands
bash test commands with pipe included:
if [[ $(echo -en "abc\n def" |grep -e "^abc") && ! $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
if [[ $(echo -en "abc\n def" |grep -e "^abc") && $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
The output is:
pipe true
pipe false
I am trying to see if I found something using grep or not with this
found=`grep -F "something" somefile.txt`
if ((${#found} == 0)); then
echo "Not Found"
else
echo "Found"
fi
I succeeded using above logic that if grep found something it stores the output in found variable but the issue I am facing is with if condition. Whenever found=0 it gives me some error like that
final.sh: 13: final.sh: 0: not found
FYI: final.sh is the script name
The problem is that you're writing bash specific code, but running it with sh. In bash, (( .. )) is an arithmetic context, while in POSIX sh, it's merely two nested subshells, causing it to try to execute the number as a command.
You can run it with bash instead of sh by specifying #!/bin/bash in the shebang, and/or using bash yourfile instead of sh yourfile if you invoke it that way.
The correct way for your example, however, is to use grep's exit status directly:
if grep -q something somefile
then
echo "found"
else
echo "not found"
fi
To check whether some string is in your file, you can use the return status from grep
grep -q something somefile.txt
if [ $? -eq 0 ]
then
echo "found"
else
echo "not found"
fi
a shorter form will be
grep -q something somefile.txt && echo found || echo not found
found=$(grep -F "something" somefile.txt)
if [ $? = 0 ]; then # $? is the return status of a previous command. Grep will return 0 if it found something, and 1 if nothing was found.
echo "Something was found. Found=$found"
else
echo 'Nothing was found'
fi
I find this code more elegant than other answers.
But anyway, why are you writing in sh? Why don't you use bash? Are you sure that you need that portability? Check out this link to see if you really need sh
Here's how I do that sort of thing:
found=$(grep -F "something" somefile.txt)
if [[ -z $found ]]; then
echo "Not found"
else
echo "Found"
fi
I have created a simple BASH script that checks every hour for the presence of a file on a remote server. It worked error-free until I was asked to move it to a server that runs KSH.
The portion of code that errors-out is this one:
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp -b "$connect_string" 2>&1`
if [ echo "$result" | grep "not found" ]; then
echo "not found"
else
echo "found"
fi
These are the errors it throws:
-ksh: .[51]: [: ']' missing
grep: ]: No such file or directory
found
It still runs though and confirms that the file I am polling for is there but I need to fix this. I changed the if statement like so
if [[ echo "$result" | grep "not found" ]]; then
but it fails right away with this error
-ksh: .: syntax error: `"$result"' unexpected
What am I missing?
Your basic syntax assumptions for if are incorrect. The old [...] syntax, calls the test builtin, and [[...]] is for textual pattern matching.
As #shelter's comment, the correct syntax is:
connect_string="$UID#$SERVER:$srcdir/$EVENTFILE"
result=`sftp -b "$connect_string" 2>&1`
if echo "$result" | grep "not found" ; then
echo "not found"
else
echo "found"
fi
But this is an unnecessary use of the external grep program, you can use shell text comparison:
if [[ $result == *not\ found* ]] ; then
echo "not found"
else
echo "found"
fi
(tested with bash and ksh)
Your solution:
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
...
fi
Can be improved. First, if you are going to do an arithmetic comparison, then use ((...)), not test, and I can't figure out why you have the EXIT variable:
if (( $? != 0 ))
then
...
fi
But to go full circle, you actually only need:
if sftp -b "$connect_string" 2>&1
then
...
fi
echo "$result" | grep "not found"
#capture exit status code from previous command ie grep.
if [[ $? == 0 ]]
than
echo "not found"
else
echo "found"
fi
It appears you're struggling with a basic tenet of bash/ksh control structures.
Between the if and the then keywords, the shell expects one or more commands, with
the last command in the series deciding how the if statement is processed.
The square brackets are only needed if you actually need to perform a comparison. Internally they are equivalent to the test command - if the comparison succeeds, it
results in an exit status of 0.
Example:
$ [ a == a ]
$ echo $?
0
$ [ a == b ]
$ echo $?
1
Which is equivalent to:
$ test a == a
$ echo $?
0
$ test a == b
$ echo $?
1
I changed my approach to this.
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp "$connect_string" 2>&1`
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
echo "file not found"
exit 1
else
echo "file found"
exit 0
fi
It takes care of my problem. Thanks to all.
I want to check if a file contains a specific string or not in bash. I used this script, but it doesn't work:
if [[ 'grep 'SomeString' $File' ]];then
# Some Actions
fi
What's wrong in my code?
if grep -q SomeString "$File"; then
Some Actions # SomeString was found
fi
You don't need [[ ]] here. Just run the command directly. Add -q option when you don't need the string displayed when it was found.
The grep command returns 0 or 1 in the exit code depending on
the result of search. 0 if something was found; 1 otherwise.
$ echo hello | grep hi ; echo $?
1
$ echo hello | grep he ; echo $?
hello
0
$ echo hello | grep -q he ; echo $?
0
You can specify commands as an condition of if. If the command returns 0 in its exitcode that means that the condition is true; otherwise false.
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
As you can see you run here the programs directly. No additional [] or [[]].
In case if you want to check whether file does not contain a specific string, you can do it as follows.
if ! grep -q SomeString "$File"; then
Some Actions # SomeString was not found
fi
In addition to other answers, which told you how to do what you wanted, I try to explain what was wrong (which is what you wanted.
In Bash, if is to be followed with a command. If the exit code of this command is equal to 0, then the then part is executed, else the else part if any is executed.
You can do that with any command as explained in other answers: if /bin/true; then ...; fi
[[ is an internal bash command dedicated to some tests, like file existence, variable comparisons. Similarly [ is an external command (it is located typically in /usr/bin/[) that performs roughly the same tests but needs ] as a final argument, which is why ] must be padded with a space on the left, which is not the case with ]].
Here you needn't [[ nor [.
Another thing is the way you quote things. In bash, there is only one case where pairs of quotes do nest, it is "$(command "argument")". But in 'grep 'SomeString' $File' you have only one word, because 'grep ' is a quoted unit, which is concatenated with SomeString and then again concatenated with ' $File'. The variable $File is not even replaced with its value because of the use of single quotes. The proper way to do that is grep 'SomeString' "$File".
Shortest (correct) version:
grep -q "something" file; [ $? -eq 0 ] && echo "yes" || echo "no"
can be also written as
grep -q "something" file; test $? -eq 0 && echo "yes" || echo "no"
but you dont need to explicitly test it in this case, so the same with:
grep -q "something" file && echo "yes" || echo "no"
##To check for a particular string in a file
cd PATH_TO_YOUR_DIRECTORY #Changing directory to your working directory
File=YOUR_FILENAME
if grep -q STRING_YOU_ARE_CHECKING_FOR "$File"; ##note the space after the string you are searching for
then
echo "Hooray!!It's available"
else
echo "Oops!!Not available"
fi
grep -q [PATTERN] [FILE] && echo $?
The exit status is 0 (true) if the pattern was found; otherwise blankstring.
if grep -q [string] [filename]
then
[whatever action]
fi
Example
if grep -q 'my cat is in a tree' /tmp/cat.txt
then
mkdir cat
fi
In case you want to checkif the string matches the whole line and if it is a fixed string, You can do it this way
grep -Fxq [String] [filePath]
example
searchString="Hello World"
file="./test.log"
if grep -Fxq "$searchString" $file
then
echo "String found in $file"
else
echo "String not found in $file"
fi
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of
which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by
POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero
status if any match is
found, even if an error was detected. Also see the -s or --no-messages
option. (-q is specified by
POSIX.)
Try this:
if [[ $(grep "SomeString" $File) ]] ; then
echo "Found"
else
echo "Not Found"
fi
I done this, seems to work fine
if grep $SearchTerm $FileToSearch; then
echo "$SearchTerm found OK"
else
echo "$SearchTerm not found"
fi
grep -q "something" file
[[ !? -eq 0 ]] && echo "yes" || echo "no"