Elegant solution to echo to either stdout or file in bash

I have a bash application that is producing some result, and I’d like to echo the result to either stdout or to a user chosen file. Because I also echo other interactive messages going to the screen, requiring the user to explicitly use the > redirection when he wants to echo the result to a file is not an option (*), as those messages would also appear in the file.

Right now I have a solution, but it’s ugly.

if [ -z $outfile ]
then
    echo "$outbuf"    # Write output buffer to the screen (stdout)
else
    echo "$outbuf" > $outfile  # Write output buffer to file
fi

I tried to have the variable $outfile to be equal to stdout, to &1 and perhaps something else but it would just write to file having that name and not actually to stdout. Is there a more elegant solution?

(*) I could cheat and use stderr for that purpose, but I think it’s also quite ugly, isn’t it?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

First, you should avoid echo to output arbitrary data.

On systems other than Linux-based ones, you could use:

logfile=/dev/stdout

For Linux, that works for some types of stdout, but that fails when stdout is a socket or worse, if stdout is a regular file, that would truncate that file instead of writing at the current position stdout is in the file.

Other than that, in Bourne-like shell, there’s no way to have conditional redirection, though you could use eval:

eval 'printf "%sn" "$buf" '${logfile:+'> "$logfile"'}

Instead of a variable, you could use a dedicated file descriptor:

exec 3>&1
[ -z "$logfile" ] || exec 3> "$logfile"

 printf '%sn' "$buf" >&3

A (small) downside with that is that except in ksh, that fd 3 would be leaked to every command run in the script. With zsh, you can do sysopen -wu 3 -o cloexec -- "$logfile" || exit in place of exec 3> "$logfile" but bash has no equivalent.

Another common idiom is to use a function like:

log() {
  if [ -n "$logfile" ]; then
    printf '%sn' "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="250165">[email protected]</a>" >> "$logfile"
  else
    printf '%sn' "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="042044">[email protected]</a>"
  fi
}

log "$buf"

Method 2

  1. Set outfile to "/dev/stdout".
  2. Let user choose filename, overriding outfile, or keep default value.
  3. printf '%sn' "$outbuf" >"$outfile"

I used printf because of “Why is printf better than echo?“.

For caveats to this solution, see Stéphane Chazelas’ answer.

Method 3

Try this

#!/bin/bash
TOSCREEN="1" # empty OR 0 to log to file
if [[ ! -z "$TOSCREEN" && $TOSCREEN == "1" ]]; then
    echo "$outbuf"    # Write output buffer to the screen (stdout)
else
    if [[ ! -f $outfile ]]; then
      #echo -e "Making file "
      touch "$outfile"
    fi  
    echo "$outbuf" >> $outfile  # Write output buffer to file
fi

Method 4

#!/bin/bash -ue

function stdout_or_file()
{
    DUMP_FILE=${1:-}

    if [ -z "${DUMP_FILE}" ]; then
        # print stdin to stdout
        cat
    else
        # dump stdin to a file
        sed -n "w ${DUMP_FILE}"
    fi
}

echo "foo" | stdout_or_file "${outfile}"

Note that ${1:-} is bash specific and especially useful in “-u” mode. A generic one-line that even works in other shells might be

echo "foo" | if [ -z "${outfile}" ]; then cat; else sed -n "w ${outfile}"; fi


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x