4

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?

8

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 '17 at 10:50
  • @Bregalad Looks like it.
    – Kusalananda
    Jan 10 '17 at 10:52
  • Very nicely done with that example of a log file that shouldn't be truncated.
    – Kusalananda
    Jan 10 '17 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 '17 at 14:02
  • @Bregalad || is or. Either $logfile is empty or you run the exec command. Check your shell man page. Jan 10 '17 at 14:11
5
  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 '17 at 10:36
  • Beware on Linux-based systems, using /dev/stdout like that is a bad idea (see my answer) Jan 10 '17 at 10:47
  • @StéphaneChazelas As always, much appreciated!
    – Kusalananda
    Jan 10 '17 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 '17 at 10:43
  • you are right . Jan 10 '17 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 at 13:45
  • So are you using a non-POSIX shell then and that explains why you get a - there? Jul 15 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 at 5:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.