linuxbashshell

Bash functions echo differently to stdout when sourced vs. forked


I have a bash script initial-tests.bash that runs some tests, using another script with pure functions. It looks something like:

#! /bin/bash

source ./scripts/_functions.bash

check=$(do_check)

echo "$check"

The script _functions.bash contains this:

#! /bin/bash
# Here we save reusable functions to aid the other scripts

echoerr() {
    # Output to stderr while freeing stdout for returns
    echo -ne $red
    echo "ERROR: $*" | ts '[%Y-%m-%d %H:%M:%S]' 1>&2
    echo -ne $nc
}

first_check() {
  echoerr "This is an echoerr with echo -e"
  echo false
}

do_check() {
  local x
  x=$(first_check)

  if [[ "$x" = 'false' ]]; then
    echo
  else
    echo false
  fi
}

initial-tests.bash is in turn sourced by another script main.bash:

#! /bin/bash

nc='\e[0m'
yellow='\e[1;33m'
green='\e[1;32m'
red='\e[1;31m'
blue='\e[1;34m'


# Fail early in case of argumentation error
echo -e "${blue}- - - Initial Checks - - -${nc}"
source ./scripts/initial-checks-load-config.bash

The tests' outputs are correct when I source that script directly: source scripts/initial-tests.bash:

[2024-10-10 08:30:55] ERROR: This is an echoerr with echo -e

But when I run bash main.bash, the tests are wrong:

- - - Initial Checks - - -
[2024-10-10 08:30:49] ERROR: This is an echoerr with echo -e
false

Note that false should be empty.

It is not clear to me why source is producing a different result from the bash run.

More info:


Solution

  • It seems that my problem is a mixture of:

    1. Unsourced variables not available to initial-tests.bash in case it is run directly and not via main.bash (the colour escape sequences)
    2. Using echo -ne $nc as the last command in echoerr, which is not outputting a newline character and thus, in effect, is prepending $nc to the echo that immediately follows in first_check function.

    Regarding point 1, when I run bash main.bash and then source initial-tests.bash, the sourced script will have access to the colour escape sequences. But when I directly source initial-tests.bash, and since the colour sequences are not (re-)defined within, these colour sequences are blank.

    Regarding point 2, using -n prepends to next echo:

    echo -n hi; echo bye
    > hibye
    

    When $nc is blank due to sourcing initial-tests.bash directly as explained, function first_check is producing a mere false since nothing is prepended to 'false' because $nc is empty:

    echo -ne $nc (last command in echoerr)
    echo false
    

    And the if test in do_check passes:

    if [[ "$x" = 'false' ]]; then
        echo
    else
        echo false
    fi
    

    => so output is ''.

    However, when I run bash main.bash, $nc contains a colour escape sequence, so that sequence is prepended to false and is making the same test fail, and hence going to the else, and echoing 'false' instead of nothing => so output is 'false'. It was tricky to detect since the prepended value was an escape sequence.

    I could fix this by:

    1. Making sure the colour sequences vars are always available before using them
    2. Removing echo -n from echoerr to avoid prepending anything to the immediate next echo.