Search code examples
bashcommand-substitution

Wrong order of commands execution in Bash script when using command substitution


Goal

I want to write a Bash script in which there are two .sh files:

  • main.sh - main script, in which I call the subscript subscripts/checksum.sh and write its result to the variable called checksum
  • subscripts/checksum.sh - script that returns the output of diff command comparing contents of two text files (they differs, so in my example diff command will always return non-zero exit code with some description)

Files structure

$ tree .
.
├── main.sh
└── subscripts
    ├── checksum.sh
    ├── file1.sha512sum.txt
    ├── file1.txt
    ├── file2.sha512sum.txt
    └── file2.txt

2 directories, 6 files

main.sh:

#!/bin/bash


set -e

echo "*** Before checksum"
checksum=$(subscripts/checksum.sh)
echo "*** After checksum"
echo "Checksum value: ${checksum}"

subscripts/checksum.sh:

#/bin/bash


set -e

checksum1=subscripts/file1.sha512sum.txt
checksum2=subscripts/file2.sha512sum.txt

echo "----- Before diff"
if diff_res=$(diff ${checksum1} ${checksum2})
then
    echo "Checksums are equal."
else
    echo "There is a difference between the checksums."
fi
echo "----- After diff"
echo ${diff_res}

Result (wrong)

Executing main.sh gives me below result:

*** Before checksum
*** After checksum
Checksum value: ----- Before diff
There is a difference between the checksums.
1c1 < a5f4d3e657ad3ef1b208e16f47b6d766cf3e2f1db8d6b5c0d2bf0573d10eb2fefcc75c9f91d5f4e047fa9a7d00848911477005015052f7005a2b85eeb8c4eabf file1.txt --- > 576bfc364f00634e8227f71fdf379168e4c3ee013da9eae2bdcdb905746b8514a959a9c01a8f11b8ea62a67ba08a3041a46b8065b73bdaad754e1d6153131896 file2.txt
----- After diff
1c1 < a5f4d3e657ad3ef1b208e16f47b6d766cf3e2f1db8d6b5c0d2bf0573d10eb2fefcc75c9f91d5f4e047fa9a7d00848911477005015052f7005a2b85eeb8c4eabf file1.txt --- > 576bfc364f00634e8227f71fdf379168e4c3ee013da9eae2bdcdb905746b8514a959a9c01a8f11b8ea62a67ba08a3041a46b8065b73bdaad754e1d6153131896 file2.txt

It seems that every command in main.sh has been ran first and after that the commands from subscripts/checksum.sh. Why is that and how can I get the correct result (described below)?

Correct result I want to get

By correct result I mean printing *** Before checksum, then getting into subscripts/checksum.sh and executing all of its command from first to the last one with printing all three echo commands to the console and after that assigning the value of diff_res to checksum in main.sh. The whole action should be ended with the last print which is *** After checksum. In my example the checksum variable should have value:

1c1
< a5f4d3e657ad3ef1b208e16f47b6d766cf3e2f1db8d6b5c0d2bf0573d10eb2fefcc75c9f91d5f4e047fa9a7d00848911477005015052f7005a2b85eeb8c4eabf file1.txt
---
> 576bfc364f00634e8227f71fdf379168e4c3ee013da9eae2bdcdb905746b8514a959a9c01a8f11b8ea62a67ba08a3041a46b8065b73bdaad754e1d6153131896 file2.txt
*** Before checksum
----- Before diff
There is a difference between the checksums.
----- After diff
*** After checksum
Checksum value: 1c1
< a5f4d3e657ad3ef1b208e16f47b6d766cf3e2f1db8d6b5c0d2bf0573d10eb2fefcc75c9f91d5f4e047fa9a7d00848911477005015052f7005a2b85eeb8c4eabf file1.txt
---
> 576bfc364f00634e8227f71fdf379168e4c3ee013da9eae2bdcdb905746b8514a959a9c01a8f11b8ea62a67ba08a3041a46b8065b73bdaad754e1d6153131896 file2.txt


Solution

  • If I understand correctly, you want to see the before/after lines (ignoring other lines) print in this order:

    *** Before checksum
    ----- Before diff
    ----- After diff
    *** After checksum
    

    But it's actually printing in this order:

    *** Before checksum
    *** After checksum
    ----- Before diff
    ----- After diff
    

    This is because of how you've written main.sh. You wrote this:

    checksum=$(subscripts/checksum.sh)
    

    This means that the output of checksum.sh, including the Before diff and After diff lines, won't be printed straight away. It will be stored in a variable to be accessed later.

    To put it another way, your code is following these steps:

    1. Print *** Before checksum
    2. Run checksum.sh and store its output (the output has Before diff and After diff)
    3. Print *** After checksum
    4. Print Checksum value:
    5. Print the data that was stored from step 2.

    So that's why lines are displayed in a different order from what you wanted.

    If you want checksum.sh to print Before diff and After diff directly to the terminal, without those lines being stored in a variable, you should change checksum.sh like so:

    echo "----- Before diff" >&2
    ...
    echo "----- After diff" >&2
    

    This works because by default, all output goes to stdout which can be stored in variables with $(). But using >&2 sends the output to stderr, which is displayed in the terminal instead of being stored by $().