Is there a simple way to do something like:
if ! some_command; then
some_commands;
fi
or
if [[ com1 && com2 ]]; then
something;
fi
where it's the exit status of com1 and com2 that are used.
I realize that I can do things like get the exit status even with -e set by using || and check that, etc. Just wondering if there was something simpler that I am missing.
ADDENDUM: I also realize that I could do:
if some_command; then
else
some_commands;
fi
! can be used outside of an if statement; it's the general exit-status inverter, not part of the if syntax.
! some_command && { some_commands; }
and
some_command || { some_commands; }
are equivalent. You can also use
com1 && com2 && { some_commands; }
for first: you can write something like: where <command1/2> is/are some command(s)/script(s) with/without parameter
$ <command1> 2>/dev/null || <command2> 2>/dev/null
for second: you can write something like:
$ ( <command1> && <command2> ) 2>/dev/null && <command3> 2>/dev/null
ex:
$ echo 1 2>/dev/null || echo 0 2>/dev/null
1
$ ech1o 1 2>/dev/null|| echo 0 2>/dev/null
0
AND
$ ( echo 1 && echo 2 ) 2>/dev/null && echo 3
1
2
3
The following won't print anything as echo 3 will never be executed (due to echo1 is not a valid command)
$ ( echo1 1 && echo 2 ) 2>/dev/null && echo 3
Related
This script is used for detecting USB drive input any copy some log files to USB.
on line 47,
cp "/home/root/io/log/${end}.txt" "${USB}/log" && let sfile++ || let err3=1
three commands are all executed, whether the cp command is succeed or not. And if I swap this line with line 49
cp "/home/root/io/log/${begin}.txt" "${USB}/log" && let sfile++ || let err3=1
, only the upper line of codes didn't work as expected.
echo $USB > /dev/null
echo $FILE > /dev/null
echo $begin > /dev/null
echo $end > /dev/null
echo $destfile > /dev/null
echo $tmp > /dev/null
echo $err > /dev/null
echo $sfile > /dev/null
echo $DATE > /dev/null
echo 84 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio84/direction
while true
do
USB=""
FILE=""
begin=""
end=""
destfile=""
DATE=""
let err1=0
let err2=0
let err3=0
let err4=0
let err=0
let sfile=0
ls /dev/sda1 > /dev/null 2>&1 || rm -rf /media/sda1
ls /media/sda1 > /dev/null 2>&1 && USB="/media/sda1"
if [[ "$USB" != "" ]]; then
ls $USB | grep 'cp.txt' > /dev/null && FILE="cp.txt"
if [[ "$FILE" != "" ]]; then
ls "${USB}/log" > /dev/null 2>&1 && rm -rf "${USB}/log"
mkdir "${USB}/log" && echo 0 >> /dev/null || let err1=1
begin=$(cat "${USB}/${FILE}" | cut -d 'd' -f 2)
end=$(cat "${USB}/${FILE}" | cut -d 'd' -f 3)
cp "/home/root/io/log/${end}.txt" "${USB}/log" && let sfile++ || let err3=1
cp "/home/root/io/log/${begin}.txt" "${USB}/log" && let sfile++ || let err2=1
destfile=$(ls /home/root/io/log)
destfile=${destfile#*${begin}.txt}
destfile=${destfile%${end}.txt*}
echo $destfile
while [[ "$(echo $destfile | cut -d ' ' -f 1)" != "" ]]; do
tmp=""
tmp=$(echo $destfile | cut -d ' ' -f 1)
destfile=${destfile#*$tmp}
cp "/home/root/io/log/${tmp}" "${USB}/log" && let sfile++ || let err4=1
done
DATE=$(date +%Y%m%d-%H%M%S)
LOG="${DATE} Requested: from-${begin} to-${end}. Succeed:${sfile} err1:${err1}err2:${err2}err3:${err3}err4:${err4}"
echo $LOG >> /home/root/io/cplog/cplog.txt
echo $LOG >> /media/sda1/cplog.txt
if [[ err -eq 0 ]]; then
echo 1 > /sys/class/gpio/gpio84/value
sleep 1
echo 0 > /sys/class/gpio/gpio84/value
else
for (( i = 0; i < 10; i++ )); do
echo 1 > /sys/class/gpio/gpio84/value
sleep 0.05
echo 0 > /sys/class/gpio/gpio84/value
sleep 0.05
done
fi
umount "${USB}"
fi
fi
sleep 2
done
I guess you are using bash, but unless you stick with POSIX-compatibility, always indicate the shell you are using. Assuming bash, if you have a command sequence X && Y || Z, and if X returns a non-zero exit status, Y won't be executed.
Aside from this, you do have a logical flaw in your script. Note that let also sets an exit status. The exit status is 1 if the value of the expression to be evaluated, is 0, and the exit status is 0 otherwise. Now regarding your let sfile++, the value of the expression is the value which sfile had before incrementing.
In the first iteration of your loop, sfile is zero. Assuming that cp returns exit code 0, sfile will be incremented, the let command returns exit code 1, and the let err4=1 will also be executed.
In the subsequent iterations, this won't happen anymore, because let sfile++ returns exit code 0.
My advice is to not be too fancy with command sequences mixing && and ||. In this case, a simple if would be the better choice:
if cp ...
then
let ...
else
let
fi
I have a string of commands I want to run in succession, if the previous command has been successful. Here is an example:
echo "hello" && sleep 5 && cd /tmp && rm -r * && echo "googbye"
If any part fails, I need to know which part failed. Also, I am trying to implement a spinner while the commands are running. Is there a way to do this?
In terms of detecting which bit failed, you could use:
bad=0
[[ $bad -eq 0 ]] && { echo hello || bad=1; }
[[ $bad -eq 0 ]] && { sleep 5 || bad=2; }
[[ $bad -eq 0 ]] && { cd /tmp || bad=3; }
[[ $bad -eq 0 ]] && { rm -r * || bad=4; }
[[ $bad -eq 0 ]] && { echo goodbye || bad=5; }
The variable bad would then be set based on which bit failed (if any).
Alternatively, if that's too verbose, you can still do it in a single line, albeit a longer one:
bad=1 && echo hello && bad=2 && sleep 5 && bad=3 && cd /tmp && bad=4 && echo goodbye && bad=0
In terms of a spinner, that may not work so well if you actually have output in the job you're doing but you can do it with a simple background function:
spinner() {
while : ; do
echo -ne '\b|' ; sleep 1
echo -ne '\b/' ; sleep 1
echo -ne '\b-' ; sleep 1
echo -ne '\b\\' ; sleep 1
done
}
echo -n 'Starting: .'
spinner & pid=$!
sleep 13 ; kill $pid
echo
echo Done.
With regard to putting it all together based on your question and comments, I'd probably have two functions, the first to run the spinner in the background and the second to do the payload, including capturing its output so it doesn't affect the spinner.
To that end, have a look at the following demo code:
spinner() {
echo -n 'Starting: .'
chars='\-/|'
while [[ -f "$1" ]] ; do
echo -ne "\b${chars:3}" ; sleep 1
chars="${chars:3}${chars:0:3}"
done
echo -e ' .. done.'
}
payload() {
echo Running task 1
sleep 3
true || return 1
echo Running task 2
sleep 3
false || return 2
echo Running task 3
sleep 3
true || return 3
return 0
}
touch /tmp/sentinel.$$
spinner /tmp/sentinel.$$ & pid=$!
payload >/tmp/out.$$ 2>&1 ; rc=$?
rm /tmp/sentinel.$$ ; wait
echo "Return code was $rc, output was:"
cat /tmp/out.$$
rm /tmp/out.$$
In the payload function, simply replace each of the steps with whatever you wish to actually do (such as shut down a systemd job or delete some files).
The code as it stands will fail step 2 (false) but, for your own code, this would be running actual commands and evaluating their return value.
I've also used a sentinel file to terminate the spinner function so that it's not shut down "violently".
I'm new to bash and having an issue where exit is always called in my script. Consider this simple code:
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| echo "Error.. something went wrong." && exit 1
fi
How can I handle errors, considering && takes precedence over || ?
Using GNU bash, version 3.2.51(1).
Thanks
You can do it like this :
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| { echo "Error.. something went wrong." && exit 1 ; }
fi
Note : I used { ; }, instead of (), because () will open your command in a subshell, so it will not exit.
&& and || have the same precedence in shell; the implicit parenthesization is (a || b) && c, not a || (b && c). Mixing || and && in the same list is rarely a good idea; use an explicit if statement.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
echo "Error.. something went wrong"
exit 1
fi
fi
For arithmetic comparisons, prefer the arithemetic command ((...)) over [[ ... ]] for readability.
if (( x >= 1 && x <= 4 )); then
You can use braces to regroup commands without creating a new subshell :
{ true || false; } && echo true || echo false # echoes true
{ false || false; } && echo true || echo false # echoes false
Its syntax is pretty annoying : the opening brace must be followed by a space (or another character of $IFS, such as a linefeed or a tab), and the closing brace must be preceded by a linefeed or a ;, denoting the end of the last command of the block.
Parenthesis don't have those difficulties, but they will execute their instructions in a subshell, which has multiple other effects :
calling exit will only exit the subshell, not the shell running your script : (exit) is a no-op
updating variables will only apply to the subshell and will have no effect on the values known to your script : a=0;( (( a++ )) ; echo $a) ; echo $a will echo 1 from the subshell, then 0 from the outer shell.
I prefer doing explicit tests on scripts using if so that I can clean up after myself if things go pear shaped. Helps keep the code looking cleaner, too.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
err="Error.. something went wrong."
test -t 0 && echo "$err" >&2 # send errors to stderr if on terminal
logger -p local0.critical -t $(hostname -s) "$err" # send to syslog
# You could even add some code here to clean up after script1.sh.
exit 1
fi
fi
How do I convert the below code into CMD line code for windows?
r=`wget -q www.someurl.com`
if [ $r -ne 0 ]
then echo "Not there"
else echo "OK"
fi
something like this:
wget.exe -q www.someurl.com
if errorlevel 1 (
echo not there
) ELSE (
echo ok
)
the error can be printed with
echo Failure is %errorlevel%
Using cmd's short-circuit && and || operators:
wget.exe -q www.someurl.com && (
echo OK
) || (
echo Not there
)
If the statements are short, you can make this into a one-liner
wget.exe -q www.someurl.com && ( echo OK ) || ( echo Not there )
This method only notes if the exit code is zero or nonzero. If you need the actual exit code, use the if statements in Stuart Siegler's answer.
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.