9

I have two shell scripts, one that serves as the main "program" and another that serves as a "library."

In several places in the "program," I'll do something like: log "$thing" >> "$logfile", where log is a function defined in the "library."

# program.sh

logfile="log.txt"
stuff="hahah heheh hoho"

. library.sh 

for thing in $stuff; do
  log "$thing" >> "$logfile"
done

My question: Is there a way to redirect some of the output from the function back to the terminal without using stderr?

# library.sh

log () {

  # This gets written to the log
  echo "`date --rfc-3339=seconds`: $1"

  # How to write this to the terminal *without* using stderr?
  echo "Info: Message written to log." >&2

}

I want to avoid the use of stderr because in my actual program, there's an option to redirect errors to a file, but the messages I want to send to the terminal are informational, not errors, and should always show up on the terminal.

Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141

2 Answers2

14

Open /dev/tty on another FD.

exec 0< /dev/null
exec 1> /dev/null
exec 2> /dev/null
exec 3> /dev/tty
echo 'Hello, World!' >&3 
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • interesting... can you explain what's happening? Also what's the point of the first three lines? It seems to work okay with just the last two. And, I suppose I should put the fourth line in `program.sh` rather than executing it each time `log` is called? – Dagg Nabbit Feb 23 '12 at 00:16
  • 1
    The first three lines just demonstrate that the original descriptors are now useless. It's an easy stand-in for a more complicated program. – sarnold Feb 23 '12 at 00:17
  • The first three are to show that the script does not have any other way of talking to the outside (e.g. are busy doing other things). I'd put the `exec` line in library.sh, but give it a higher number (say, 8 or so) just in case. – Ignacio Vazquez-Abrams Feb 23 '12 at 00:18
  • Sending it to /dev/tty seems to work fine also, should I just do that? I tried giving it the number `123` and that seemed to work :) – Dagg Nabbit Feb 23 '12 at 00:19
  • Sure, sending it directly to `/dev/tty` should work as well, if you only need to do it a few times. Otherwise, the FD will be fewer keystrokes (and easier to replace if you need to send it somewhere different). – Ignacio Vazquez-Abrams Feb 23 '12 at 00:21
  • Thanks for the quick answer and for explaining this. I'm still pretty lost on exactly what `exec` does; the man page is terrible. I'll look around a bit. – Dagg Nabbit Feb 23 '12 at 00:25
  • 5
    If you just run `man exec`, you'll get the wrong page. `man bash`, and then search for the phrase `it replaces the shell`. (Searching for `exec` in the `bash(1)` manpage is a quick way to go nuts.) – sarnold Feb 23 '12 at 00:27
  • @sarnold these are the kind of things i would never figure out myself in a million years. Thanks. So `exec` is an internal bash command, not an program. – Dagg Nabbit Feb 23 '12 at 00:40
  • 1
    @GGG: correct; any POSIX-compliant shell will provide an `exec` shell built-in. (Well, it doesn't _have_ to be built-in, but providing it as an external program would be immensely _difficult_. The only reasonable implementation I can imagine is shell-builtin.) I just now remembered that newer `bash` also provides a `help` shell-builtin that you might like: `help exec`. It isn't as detailed as the manpage, but might be exactly what you need some day. :) – sarnold Feb 23 '12 at 00:50
  • @sarnold well it definitely beats already knowing what to search for in the bash manpage... thanks ;) – Dagg Nabbit Feb 23 '12 at 01:27
8

You can write directly to /dev/tty each time you want to write to the terminal:

echo "hello world" > /dev/tty

For a small example:

$ cat writer.sh 
#!/bin/sh

echo "standard output"
echo "standard error" >&2

echo "direct to terminal" > /dev/tty
$ ./writer.sh > /tmp/out 2> /tmp/err
direct to terminal
$ cat /tmp/out
standard output
$ cat /tmp/err
standard error
$ 
sarnold
  • 102,305
  • 22
  • 181
  • 238