0

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

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"
  • 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. – Stéphane Chazelas Jan 10 '17 at 14:11
4
  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.

  • Completely agree. I'd do it the same way. – Sergiy Kolodyazhnyy Jan 10 '17 at 10:36
  • Beware on Linux-based systems, using /dev/stdout like that is a bad idea (see my answer) – Stéphane Chazelas 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
  • Why go through the process of using touch and then appending? – Kusalananda Jan 10 '17 at 10:43
  • you are right . – Talal Jan 10 '17 at 15:43

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.