5

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?

4 Answers 4

10

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 "%s\n" "$buf" '${logfile:+'> "$logfile"'}

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

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

 printf '%s\n' "$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 '%s\n' "$@" >> "$logfile"
  else
    printf '%s\n' "$@"
  fi
}

log "$buf"
7
  • So basically, using /dev/stdout works in some case, but is not a good idea or can be problematic in some cases, then I'm better off doing the ugly if/else solution ?!
    – Bregalad
    Jan 10, 2017 at 10:50
  • @Bregalad Looks like it.
    – Kusalananda
    Jan 10, 2017 at 10:52
  • Very nicely done with that example of a log file that shouldn't be truncated.
    – Kusalananda
    Jan 10, 2017 at 10:55
  • I do not understand this syntax : [ -z "$logfile" ] || exec 3> "$logfile" It looks like some shorthand if/then/else. Would you care to explain ? Thanks.
    – Bregalad
    Jan 10, 2017 at 14:02
  • @Bregalad || is or. Either $logfile is empty or you run the exec command. Check your shell man page. Jan 10, 2017 at 14:11
6
  1. Set outfile to "/dev/stdout".
  2. Let user choose filename, overriding outfile, or keep default value.
  3. printf '%s\n' "$outbuf" >"$outfile"

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

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

3
  • Completely agree. I'd do it the same way. Jan 10, 2017 at 10:36
  • Beware on Linux-based systems, using /dev/stdout like that is a bad idea (see my answer) Jan 10, 2017 at 10:47
  • @StéphaneChazelas As always, much appreciated!
    – Kusalananda
    Jan 10, 2017 at 10:52
0

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
2
  • 1
    Why go through the process of using touch and then appending?
    – Kusalananda
    Jan 10, 2017 at 10:43
  • you are right . Jan 10, 2017 at 15:43
0
#!/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
3
  • ${1:-} is actually POSIX standard, not bash specific. Using -u with sh (or with set -u) is also standard.
    – Kusalananda
    Jul 14, 2021 at 13:45
  • So are you using a non-POSIX shell then and that explains why you get a - there? Jul 15, 2021 at 0:49
  • No I'm not, I'm instead not reading or thinking properly in the middle of the night in my +30 degree apartment. My apologies. I've deleted the irrelevant comments.
    – Kusalananda
    Jul 15, 2021 at 5:35

You must log in to answer this question.

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