I have a wrapper script for a CI pipeline which works great, but it always returns with 0 even though subcommands in a for loop fails. Here is an example:
#!/bin/bash
file_list=("file1 file2 file_nonexistant file3")
for file in $file_list
do
cat $file
done
>./listfiles.sh
file1 contents
file2 contents
cat: file_nonexistant: No such file or directory
file3 contents
>echo $?
>0
Since the last iteration of the loop is successfull the entire script exits with 0.
What i want is for the loop to continue on fail and for the script to exit 1 if any of the loop iterations returned errors.
What i have tried so far:
set -e but it halts the loop and exits when an iteration fails
replaced done with done || exit 1 - no effect
replaced cat $file with cat $file || continue - no effect
Alternative 1
#!/bin/bash
for i in `seq 1 6`; do
if test $i == 4; then
z=1
fi
done
if [[ $z == 1 ]]; then
exit 1
fi
With files
#!/bin/bash
touch ab c d e
for i in a b c d e; do
cat $i
if [[ $? -ne 0 ]]; then
fail=1
fi
done
if [[ $fail == 1 ]]; then
exit 1
fi
The special parameter $? holds the exit value of the last command. A value above 0 represents a failure. So just store that in a variable and check for it after the loop.
The $? parameter actually holds the exit status of the previous pipeline, if present. If the command is killed with a signal then the value of $? will be 128+SIGNAL. For example 128+2 in case of SIGINT (ctrl+c).
Overkill solution with trap
#!/bin/bash
trap ' echo X $FAIL; [[ $FAIL -eq 1 ]] && exit 22 ' EXIT
touch ab c d e
for i in c d e a b; do
cat $i || export FAIL=1
echo F $FAIL
done
Related
I am trying to test if certain variables are set and if ping exits with 0. When I do
var=1 #set for later
#"If" checks exit status correctly
if ping -c 1 an-inaccessible-thing
then
echo T
else
echo F
fi
#returns F
#"if" does not like the program in the [[ ]]
if [[ -n $var && ping -c 1 an-inaccessible-thing ]]
then
echo T
else
echo F
fi
#returns this error for obvious reasons
-bash: conditional binary operator expected
-bash: syntax error near `-c'
#if runs its test on the output of the shell, not its exit code.
if [[ -n $var && $(ping -c 1 an-inaccessible-thing) ]]
then
echo T
else
echo F
fi
#returns T, probably because it's being evaluated with -n and no the exit code
how can I test programs exit code inside the double square brackets?
Because [[ is a separate (bash builtin) program, like ping or any other program.
Do && outside of [[ ]].
[[ -n $var ]] && ping -c 1 an-inaccessible-thing
For all the files (in the current directory) that contain the WORD that is the 1st command line argument, I have to insert the second command line argument on a new line at the head of the file.Then I have to Print usage and exit with status 4 if not given two args. Exit status 1 is there were there are no such files. Exit status 2 if any of the files could not be altered. Exit status 0 otherwise.
For Example:
Suppose the script filename is addwarn.sh then,
echo "I am a string" > f1
echo "I am not a string > f2
./addwarn.sh "not" '*** WARNING ***'
cat f1 f2
I am a string
*** WARNING ***
I am not a string
What I have tried so far:
#! bin/sh
if [ $? -eq 0 ]; then
exit
fi
if [ $? -ne 0 ]; then
cat *
fi
I am not sure how to make a script, any ideas?
#!/bin/sh
found=0
usage(){
echo "$0 takes two args!"
exit 4
}
#check for two args
i=0
for a in "$#"; do
((i++))
done
[ "$i" -ne "2" ] && usage
uneditable=0
for f in $(find ./ -maxdepth 1 -type f); do
if grep -q $1 $f ; then
found=1
echo -e "$2\n$(cat $f)" > $f || uneditable=1
fi
done
[ "$found" = "0" ] && exit 1
[ "$uneditable" = "1" ] && exit 2
exit 0
I want to read each lines from an input. Each line is successfully read in a while loop. However the loop ends with the status 1:
$ incr=0
$ while IFS='' read -r line || [[ -n "$line" ]] ; do
incr=$((incr+1));
echo "$incr: $line";
done < <(echo -e "one \ntwo\tthree\nfour")
1: one
2: two three
3: four
$ echo "status ${PIPESTATUS[#]}"
status 1
Why do I get an exit status different than 0?
1 appears to be the exit status of the command ([[ -n "$line" ]]) that caused the while loop to exit in the first place. It's possible this is a bug in bash, or at least an undocumented difference in which command(s) set $? vs PIPESTATUS.
You can observe the same difference in a much simpler command:
$ while false; do echo foo; done
$ printf '%s\n' "$?" "${PIPESTATUS[#]}"
0
1
In the below code, I am trying to check if the command within the if condition completed successfully and that the data was pushed into the target file temp.txt.
Consider:
#!/usr/bin/ksh
A=4
B=1
$(tail -n $(( $A - $B )) sample.txt > temp.txt)
echo "1. Exit status:"$?
if [[ $( tail -n $(( $A - $B )) sample.txt > temp.txt ) ]]; then
echo "2. Exit status:"$?
echo "Command completed successfully"
else
echo "3. Exit status:"$?
echo "Command was unsuccessfully"
fi
Output:
$ sh sample.sh
1. Exit status:0
3. Exit status:1
Now I can't get why the exit status changes above.. when the output of both the instances of the tail commands are identical. Where am I going wrong here..?
In the first case, you're getting the exit status of a call to the tail command (the subshell you spawned with $() preserves the last exit status)
In the second case, you're getting the exit status of a call to the [[ ]] Bash built-in. But this is actually testing the output of your tail command, which is a completely different operation. And since that output is empty, the test fails.
Consider :
$ [[ "" ]] # Testing an empty string
$ echo $? # exit status 1, because empty strings are considered equivalent to FALSE
1
$ echo # Empty output
$ echo $? # exit status 0, the echo command executed without errors
0
$ [[ $(echo) ]] # Testing the output of the echo command
$ echo $? # exit status 1, just like the first example.
1
$ echo foo
foo
$ echo $? # No errors here either
0
$ [[ $(echo foo) ]]
$ echo $? # Exit status 0, but this is **NOT** the exit status of the echo command.
0
In ksh shell, I wanna to check the return value after running a command, I've wrote two styles:
if [ $? -ne 0 ] ; then
echo "failed!"
exit 1
else
exit 0
fi
[ $? -ne 0 ] && echo "failed!" && exit 1
Are they equivalent? If not, what could I do if I wanna to write it in one line?
They're close, but not the same. First, the if will execute the exit 1 even if the echo failed for some reason; the chained expression won't. Also, the chained version lacks an equivalent of the else exit 0.
A better equivalent would be this:
[ $? -ne 0 ] && { echo "failed!"; exit 1; } || exit 0
This is tagged ksh, so you might find the numeric expression syntax cleaner:
(( $? )) && { echo "failed!"; exit 1; } || exit 0
But you can also write an if on one line, if you like:
if (( $? )); then echo "failed!"; exit 1; else exit 0; fi
If the command that you just ran above this expression in order to set $? is short, you may want to just use it directly as the if expression - with reversed clauses, since exit code 0 is true:
if grep -q "pattern" /some/filename; then exit 0; else echo "failed!"; exit 1; fi
It doesn't matter for this simple case, but in general you probably want to avoid echo. Instead, use printf - or if you don't mind being ksh-only, you can use print. The problem with echo is that it doesn't provide a portable way to deal with weird strings in variables:
$ x=-n
$ echo "$x"
$
While both printf and print do:
$ printf '%s\n' "$x"
-n
$ print - "$x"
-n
Again, not a problem here, or any time you're just printing out a literal string, but I found it was easier to train myself out of the echo habit entirely.