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 -e
to 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 -e
again, but I'd rather not because it doesn't match the style of the rest of my script. - I'm using
set -o pipefail
to pass a non-zero errorlevel down the pipeline so that mytee -a "$logfile"
doesn't always override my exiterrorlevel
. - I'm using
++count
instead 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++ )) || true
because of theset -e
explained 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 ? – steeldriver Oct 12 '17 at 01:36