41

When a script runs, commands in it may output some text to stdout/stderr. Bash itself may also output some text.

But if a few scripts are running at the same time, it is hard to identify where does an error come from.

So is it possible to insert a prefix to all output of the script? Something like:

#!/bin/bash
prefix 'PREFIX' &2
echo "wrong!" >&2

Then:

$ ./script.sh
PREFIXwrong!
Magicloud
  • 753

3 Answers3

56

You can redirect stderr/stdout to a process substitution that adds the prefix of choice. For example, this script:

#! /bin/bash
exec > >(trap "" INT TERM; sed 's/^/foo: /')
exec 2> >(trap "" INT TERM; sed 's/^/foo: (stderr) /' >&2)
echo foo
echo bar >&2
date

Produces this output:

foo: foo
foo: (stderr) bar
foo: Fri Apr 27 20:04:34 IST 2018

The first two lines redirect stdout and stderr respectively to sed commands that add foo: and foo: (stderr) to the input.
The calls to the shell built-in command trap make sure that the subshell does not exit when terminating the script with Ctrl+C or by sending the SIGTERM signal using kill $pid. This ensures that your shell won't forcefully terminate your script because the stdout file descriptor disappears when sed exits because it received the termination signal as well. Effectively you can still use exit traps in your main script and sed will still be running to process any output generated while running your exit traps. The subshell should still exit after your main script ends so sed process won't be left running forever.

muru
  • 72,889
  • 8
    Note that doing this one loses the guarantee that ordering will be consistent between the two streams -- if you write, say, five lines to stdout, one line to stderr, and five more lines to stdout, it's not at all guaranteed that the line written to stderr will have five stdout lines before it and five after it when it finally gets flushed. – Charles Duffy Apr 27 '18 at 19:54
  • @CharlesDuffy Do you have any extra information on that ? A couple links perhaps ? – Sergiy Kolodyazhnyy Apr 28 '18 at 01:47
  • @SergiyKolodyazhnyy I think the dupe candidate suggested by you and the possible dupe for that question are good for that - both have some discussion on retaining ordering of the output. – muru Apr 28 '18 at 03:39
  • If the job is only add a prefix, I'd save on sed call with something like while read -r; do echo "$PREFIX$REPLY"; done – jno May 18 '21 at 09:57
  • @jno depends on the prefix. Something like ts could be used for nicely timestamped output. – muru May 18 '21 at 10:04
  • @muru, who keeps you from usung, say, $(date) instead of mere $PREFIX? – jno May 18 '21 at 11:27
  • @jno the thought of running a date for each line instead of having a long-running ts which processes all lines. – muru May 18 '21 at 11:28
  • @muru, the use of ts (as well as, say, cat -n to number lines) is the same as sed — just another filter. I meant the original question for arbitrary prefix insertion. The exact choice obviously depends on what one want to insert (static text, "serializers" like line numbers and stamps, or something even more "dynamic") and expected output rate. – jno May 18 '21 at 11:38
  • @jno well, yes, that's why I gave an example of process substitution that readers can adapt to run whatever command they want, instead of worrying about premature optimization on sed calls. – muru May 18 '21 at 11:46
13

You could pipe the output through some way of replacing lines:

some long running stuff | sed -e 's/^/Some said: /;'

Also check 24337

Or just direct separate outputs to separate files/screen(1) tabs/tabs in your terminal/...

vonbrand
  • 18,253
7

One option in bash is to do this by redirecting to process substitutions, something like this:

./script.sh > >(sed 's/^/script: /') 2> >(sed 's/^/script (err): /' >&2)

This has the problem that output may be out of order (as Charles Duffy mentioned in a comment). It's also really annoyingly unweildy. But you could make a wrapper function for it:

prefixwith() {
    local prefix="$1"
    shift
    "$@" > >(sed "s/^/$prefix: /") 2> >(sed "s/^/$prefix (err): /" >&2)
}
prefixwith "From script" ./script.sh

Or make it even simpler by having it use the command name as a prefix:

prefixoutput() {
    local prefix="From ${1##*/}"
    "$@" > >(sed "s/^/$prefix: /") 2> >(sed "s/^/$prefix (err): /" >&2)
}
prefixoutput ./script.sh