Verifying the Backup Command
Learn what can go wrong with our example backup command.
Let’s check if the following command works correctly.
tar -cjf ~/photo.tar.bz2 ~/photo &&
echo "tar - OK" > results.txt ||
echo "tar - FAILS" > results.txt &&
cp -f ~/photo.tar.bz2 ~/backup &&
echo "cp - OK" >> results.txt ||
echo "cp - FAILS" >> results.txt
We can replace each command call with a Latin letter. Then, we get a convenient form of the boolean expression. The expression looks like this:
B && O1 || F1 && C && O2 || F2
The B
and C
letters represent the tar
and cp
calls. O1
is the echo
call that prints “tar - OK” line in the log file. F1
is the echo
call to print the “tar - FAILS” line. Similarly, O2
and F2
are the commands to log the cp
result.
If the tar
call succeeds, the B
operand of our expression equals true
. Then, Bash performs the sequence of the following steps:
- B
- O1
- C
- O2 or F2
If the tar
fails, the “B” operand equals false. Then Bash does the following steps:
- B
- F1
- C
- O2 or F2
This means that the shell calls the cp
utility even when the archiving fails. It doesn’t make sense.
Unfortunately, the tar
utility makes things even more confusing. It creates an empty archive if it can’t access the target directory or files. Then, the cp
utility copies the empty archive successfully. These operations lead to the following output in the log file:
tar - FAILS
cp - OK
Such output is confusing. It doesn’t clarify what went wrong.
Here is our expression again:
B && O1 || F1 && C && O2 || F2
Why does Bash call the cp
utility when tar
fails? It happens because the echo
command always succeeds. It returns zero code, which means true
. Thus, the O1
, F1
, O2
and F2
operands of our expression are always true
.
Let’s fix the issue caused by the echo
call exit code. We should focus on the tar
call and corresponding echo
commands. They match the following boolean expression:
B && O1 || F1
We can enclose the “B” and “O1” operands in brackets this way:
(B && O1) || F1
It doesn’t change the expression’s result.
We have a logical OR between the (B && O1)
and F1
operands. The F1
operand always equals true
. Therefore, the whole expression is always true
. The value of (B && O1)
does not matter. We want to get another behavior. If the (B && O1)
operand equals false
, the entire expression should be false
.
One possible solution is inverting the F1
operand. The logical NOT operator does that. We get the following expression this way:
B && O1 || ! F1 && C && O2 || F2
Let’s check the behavior that we got. If the B
command fails, Bash evaluates F1
. It always equals false
because of negation. Then, Bash skips the C
and O2
commands. This happens because there is a logical AND between them and F1
. Finally, Bash comes to the F2
operand. The shell needs its value. Bash knows that the LHS operand of the logical OR equals false
. Therefore, it needs to evaluate the RHS operand to deduce the result of the whole expression.
We can make the expression clearer with the following parentheses:
(B && O1 || ! F1 && C && O2) || F2
Now, it’s clear that Bash executes the F2
action when the parenthesized expression equals false
. Otherwise, it cannot deduce the final result.
The last command writes this output into the log file:
tar - FAILS
cp - FAILS
This output looks better than the previous one. Now, the cp
utility does not copy an empty archive.
The current result still has room for improvement. Let’s imagine that we extended the backup command. Then, it contains 100 actions. If an error occurs at the 50th action, all the remaining operations print their failed results into the log file. Such output makes it complicated to find the problem.
The better solution here is to terminate the command right after the first error occurred. Parentheses can help us to reach this behavior. Here is a possible grouping of the expression’s operands:
(B && O1 || ! F1) && (C && O2 || F2)
Let’s check what happens if the B
operand is false. Then, Bash executes the F1
command. The negation inverts the F1
result. Therefore, the entire LHS expression equals false
. Here is the LHS expression:
(B && O1 || ! F1)
Then, the short-circuit evaluation happens. It prevents calculating the RHS operand of the logical AND. Then, Bash skips all commands of the RHS expression. Here is the RHS expression:
(C && O2 || F2)
We created the proper behavior of the backup command.
We can add one last improvement. The F2
operand should be inverted. Then, the whole expression equals false
if the C
command fails. Then, the entire backup command fails if tar
or cp
call fails. Inverting the F2
operand provides the proper non-zero exit status in the error case.
Here is the final version of our expression with all improvements:
(B && O1 || ! F1) && (C && O2 || ! F2)
Let’s come back to the real Bash code. The corrected backup command looks like this:
(tar -cjf ~/photo.tar.bz2 ~/photo &&
echo "tar - OK" > results.txt ||
! echo "tar - FAILS" > results.txt) &&
(cp -f ~/photo.tar.bz2 ~/backup &&
echo "cp - OK" >> results.txt ||
! echo "cp - FAILS" >> results.txt)
Run the commands discussed in this lesson in the terminal below.
Get hands-on with 1200+ tech skills courses.