3

I need the capture the output of a command group in BASH to STDOUT and a log file. Consider this code with command grouping and it's output

#!/usr/bin/bash

main(){
declare -i mycode=1
echo "Declared mycode:${mycode}"

{
  #command group
  echo "mycode:${mycode}"
  mycode=2
  echo "mycode:${mycode}"
} 2>&1

echo "mycode:${mycode}"
}

main

the output is:

Declared mycode:1
mycode:1
mycode:2
mycode:2

I need to capture the command group output to a log file and STDOUT so I add tee as follows:

#!/usr/bin/bash

main(){
declare -i mycode=1
echo "Declared mycode:${mycode}"

{
  #command group
  echo "mycode:${mycode}"
  mycode=2
  echo "mycode:${mycode}"
} 2>&1 | tee ~/log.log

 echo "mycode:${mycode}"
}

main

but now the output is as follows:

Declared mycode:1
mycode:1
mycode:2
mycode:1

So value of the mycode variable does not get set to 2 in the outer scope when tee is used as the left of tee will be run in a subshell. For various reason I need mycode defined in the global scope so I need to avoid subshells.

How can I achieve the behaviour of tee without a subshell whereby I can stream output to STDOUT and a log file.

2 Answers 2

1

One way of approaching this is to implement the pipe connector yourself:

#!/bin/bash

# Initialisation
mycode=1

# Tidy up
trap 'ss=$?; [ -n "$tmpd" ] && [ -d "$tmpd" ] && rm -rf "$tmpd"; exit $ss' 1 2 15

# Unique temporary directory
tmpd=$(mktemp --directory "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX")

# Create pipe
pipe="$tmpd/pipe"
mknod "$pipe" p

# Output
tee <"$pipe" "$HOME/log.log" &

# Subprocess
{
    echo "mycode:${mycode}"

    mycode=2
    echo "mycode:${mycode}"

} >"$pipe" 2>&1

# Destroy temporary, including pipe
wait
rm -rf "$tmpd"

# Done
echo "final mycode:${mycode}"

Output

mycode:1
mycode:2
final mycode:2

Also cat ~/log.log

mycode:1
mycode:2
0

Depending on what you want to achieve with the log file you might like to attach it to descriptor #3. (Remember stdout is #1 and stderr is #3.)

#!/bin/bash

# Initialisation
mycode=1

# Attach fd3 to the logfile and stdout
exec 3> >(tee "$HOME/log.log" >&1)

# Demonstrations
echo this is to stdout
echo this is to stderr >&2
echo this is to logfile and stdout >&3

# Subprocess
{
    echo "mycode:${mycode}"

    mycode=2
    echo "mycode:${mycode}"

} 2>&1 1>&3

# Done
echo "final mycode:${mycode}"
sleep 0.25

An advantage of this approach is that it's then very easy to write to the logfile from anywhere simply by redirecting to file descriptor 3 (see the "Demonstrations" block of code, above).

A disadvantage is that the tee runs asynchronously and so output may be wrongly interleaved. The sleep 0.25 at the end aims to allow time for tee to finish writing its output. Depending on how you're using the feature the interleaving may or may not be a real world problem.

Example output:

this is to stdout
this is to stderr
final mycode:2
this is to logfile and stdout
mycode:1
mycode:2

Log file (cat "$HOME/log.log"):

this is to logfile and stdout
mycode:1
mycode:2

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .