This script just tries to copy file foo to bar/, and if it`s unsuccessful, retries 2 more times.
EDIT: as it turns out, the reason the second example works and the first does not, is because the use of (...) to run a group of commands after the && or || branch is considered a subshell and does not affect the outer variable.
So revised question: how can I group some commands like the first example, but not use a subshell to do so?
EDIT 2: the answer is to replace ( ... ) with { ...; }!
#!/usr/bin/env bash
#
# Be safe
set -euo pipefail
# Init variables
__file="foo"
output_dir="bar"
logfile="test.log"
errorlevel=8
count=0
# Try three times to copy a file
until (( errorlevel == 0 || count == 3 )); do
cp -Lv --no-preserve=mode \
"$__file" \
"$output_dir/" \
| tee -a "$logfile" \
&& (errorlevel=$?; echo "zero return, $errorlevel") \
|| (errorlevel=$?; echo "non-zero return, $errorlevel")
echo "errorlevel $errorlevel"
(( ++count ))
echo "count $count"
done
unset errorlevel
unset count
The problem:
When the copy fails, or is successful, the || or && branch catches correctly and sets variable errorlevel to 1 or 0, respectively, as evidenced with the echo $errorlevel contained within the branch.
But the very next command, also an echo, returns the initial variable value of 8! I don't understand why this is happening.
A few notes:
- I'm using
&&and||explicitly because I haveset -eto make this script as safe as possible (this is actually a small routine in a 1500-line script). If I don't use||to catch a non-zero return, the script exits. Alternatively I couldset +e, do the copy routine without having to use&&and||(by catching the errorlevel once), and thenset -eagain, but I'd rather not because it doesn't match the style of the rest of my script. - I'm using
set -o pipefailto pass a non-zero errorlevel down the pipeline so that mytee -a "$logfile"doesn't always override my exiterrorlevel. - I'm using
++countinstead ofcount++because the former returns a zero errorlevel, and the latter returns a non-zero errorlevel (and I would have to do an ugly(( count++ )) || truebecause of theset -eexplained in note #1).
I have worked around it for now by setting the errorlevel variable explicitly to 1 or 0, since I actually don't care what the non-zero errorlevel is, but I would still really like to know why the above isn`t working as I expect.
Here's the workaround:
#!/usr/bin/env bash
#
set -euo pipefail
__file="foo"
output_dir="bar"
logfile="test.log"
errorlevel=1
count=0
until (( errorlevel == 0 || count == 5 )); do
cp -Lv --no-preserve=mode \
"$__file" \
"$output_dir/" \
| tee -a "$logfile" \
&& errorlevel=0 \
|| errorlevel=1
(( ++count ))
sleep 1
done
unset errorlevel
unset count
Any insight is greatly appreciated!
{ . . . }command grouping - as described in the accepted answer ?