Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

To capture the standard output of parentheses evaluation, the parentheses expression can be "evaluated" with a dollar sign ($). Consider:

  • today=`date +%Y_%m_%d``date`
  • today=$(date +%Y_%m_%d)
    • because it is enclosed in parentheses, the date command is run in a sub-shell, writing its data to its standard output
    • date's standard output stream is connected to the calling shell's standard input by the dollar sign ($) before the opening parenthesis.
  • In both cases the caller's standard input text is stored in the today variable

...

Unlike most other programming languages, bash functions and scripts can only return a single integer between 0 and 255. By convention a return value of 0 means success (true), and any other return value is an error code (false).

A function can return this value using the return keyword (e.g. return 0). The return value is then stored in the special $? variable, which can be checked by the caller. Since this not very much information, function return values are not often used or checked. Instead, as we've seen, functions are often called for their standard output, which serves as a return value proxy.

...

Tip
titleTip

We will do this in a new tmux or screen session, since accidentally calling exit at top-level (instead of in a sub-shell) will log you off the server!

See this nice tmux cheat sheet: http://atkinsam.com/documents/tmux.pdf


Code Block
languagebash
# Invoke tmux newfrom your (login exitcommand 0line
)
res=$?
echo "exit code: $res" 

( exit 255 )
res=$?
echo "exittmux new

# Now you're in a tmux. Mine has a green bar at the bottom
( exit 0 )
echo $?

( exit 255 )
res=$?
echo "exit code: $res"

# exit tmux session
exit
# You're back at your login command line now

More on capturing output

We've already seen some examples of capturing output from echo using backtick evaluation. Now let's read the contents of a file into a variable using parentheses evaluation.

...

Rather than checking an exit code, it is often more robust to sanity check the returned output; for example, checking to see if it is empty:

...

languagebash

. If you execute this in your tmux, be sure to enclose it all in parentheses or else your tmux will exit!

Code Block
languagebash
dat=$( cat not_a_file )

if [[ "$dat" == "" ]]; then
  echo "ERROR: no data found" 1>&2; exit 255
else
  echo "Data is: '$dat'"
fi
else
  echo "Data is: '$dat'"
fi

# or using -z to test for an empty string
if [[ -z "$dat" ]]; then echo "ERROR: no data found" 1>&2; exit 255;  
else echo "Data is '$dat'"; fi

See https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html for conditional expressions, and https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html for conditional constructs such as if or case.

Setting environment variables for a script

...

Code Block
languagebash
#!/bin/bash

# Script version global variable. Edit this whenever changes are made.
__ADVANCED_BASH_VERSION__="step_03"

# =======================================================================
#  Helper functions
# =======================================================================

# Shorter format date
date2() { date '+%Y-%m-%d %H:%M'; }

# Echo's its arguments and the date to std error
echo_se() { echo "$@ - `date2`" 1>&2; }
maybe_echo() {
  local do_echo=${ECHO_VERBOSE:-1}
  if [[ "$do_echo" == "1" ]]; then echo_se "$@"; fi
}

# Sets up auto-logging to a log file in the current directory
# using the specified logFileTag (arg 1) in the log file name.
auto_log() {
  local logFileTag="$1"
  if [[ "$logFileTag" != "" ]]; then
    local logFilePath="./autoLog_${logFileTag}.log"
    maybe_echo ".. logging to $logFilePath"
    exec 1> >(tee "$logFilePath") 2>&1
    res=$?
    if [[ "$res" != "0" ]]; then
      echo_se "** ERROR: auto logging returned non-0 exit code $res"
      exit 255
    fi
  else
    echo_se "** ERROR in auto_log: no logFile argument provided"
    exit 255
  fi
}

# =======================================================================
#  Command processing functions
# =======================================================================

# function that says "Hello World!" and displays user-specified text.
function helloWorld() {
  local txt1=$1
  local txt2=$2
  shift; shift
  local rest=$@
  echo "Hello World!"
  echo "  text 1: '$txt1'"
  echo "  text 2: '$txt2'"
  echo "  rest:   '$rest'"
}

# function that displays its 1st argument on standard output and
# its 2nd argument on standard error
function stdStreams() {
  local outTxt=${1:-"text for standard output"}
  local errTxt=${2:-"text for standard error"}
  echo    "to standard output: '$outTxt'"
  echo_se "to standard error:  '$errTxt'"
}

# function that illustrates auto-logging and capturing function output
#  arg 1 - (required) tag to identify the logfile
#  arg 2 - (optional) text for standard output
#  arg 3 - (optional) text for standard error
function testAutolog() {
  local logFileTag="$1"
  local outTxt=${2:-"text for standard output"}
  local errTxt=${3:-"text for standard error"}

  auto_log "$logFileTag"

  echo -e "\n1) Call stdStreams with output and error text:"
  stdStreams "$outTxt" "$errTxt"

  echo -e "\n2) Capture echo output in a variable and display it:"
  local output=`echo $outTxt`
  echo -e "   echo output was:\n$output"

  echo -e "\n3) Call echo_se with error text:"
  echo_se "$errTxt"

  echo -e "\n4)Capture echo_se function output in a variable and display it:"
  output=`echo_se "$errTxt"`
  echo -e "echo_se output was: '$output'"
}

# =======================================================================
#  Main script command-line processing
# =======================================================================

function usage() {
  echo "
advanced_bash.sh, version $__ADVANCED_BASH_VERSION__

Usage: advanced_bash.sh <command> [arg1 arg2...]

Commands:
  helloWorld [text to display]
  stdStreams [text for stdout] [text for stderr]
  testAutolog <logFileTag> [text for stdout] [text for stderr]
"
  exit 255
}

CMD=$1    # initially $1 will be the command
shift     # after "shift", $1 will be the 2nd command-line argument; $2 the 3rd", $1 will be the 2nd command-line argument; $2 the 3rd, etc.
          # and $@ will be arguments 2, 3, etc.

# Only show usage if there is a command argument,
# making it possible to source this file
if [[ "$CMD" != "" ]]; then
  case "$CMD" in
    helloWorld) helloWorld "$@"
      ;;
    stdStreams) stdStreams "$1" "$2"
      ;;
    testAutolog) testAutolog "$1" "$2" "$3"
      ;;
    *) usage
      ;;
  esac
fi

...

Code Block
languagebash
CMD=$1    # initially $1 will be the command
shift     # after "shift", $1 will be the 2nd command-line argument; $2 the 3rd, etc.
          # and $@ will be arguments 2, 3, etc.
# Only show usage if there is a command argument,
# making it possible to source this file
if [[ "$CMD" != "" ]]; then
  case "$CMD" in
    helloWorld) helloWorld "$@"
      ;;
    stdStreams) stdStreams "$1" "$2"
      ;;
    testAutolog) testAutolog "$1" "$2" "$3"
      ;;
    *) usage
      ;;
  esac
fi

...

Code Block
languagebash
tmux new
source ~/workshop/step_03.sh
( helloWorld My name is Anna )
( stdStreams )

exercise 1

Test We can also test the new maybe_echo function, with and without verbose output:

Expand
titleSolution
Code Block
languagebash
tmux new
source ~/workshop/step_03.sh

# Normal verbose output
( maybe_echo "hello world" )

# Suppress verbose output
export ECHO_VERBOSE=0
( maybe_echo "hello world" )

# exit tmux session
exit 

auto_log function changes

...

Code Block
languagebash
# Sets up auto-logging to a log file in the current directory
# using the specified logFileTag (arg 1) in the log file name.
auto_log() {
  local logFileTag="$1"
  if [[ "$logFileTag" != "" ]]; then
    local logFilePath="./autoLog_${logFileTag}.log"
    maybe_echo ".. logging to $logFilePath"
    exec 1> >(tee "$logFilePath") 2>&1
    res=$?
    if [[ "$res" != "0" ]]; then
      echo_se "** ERROR: auto logging returned non-0 exit code $res"
      exit 255
    fi
  else
    echo_se "** ERROR in autoLog: no logFile argument provided"
    exit 255
  fi
}

exercise

...

1

In a sub-shell, test the auto_log function – with and without a logFileTag argument – and check the exit code.

...