I currently use the following setup to redirect the output of multiple commands:

echo "Some normal commands"
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"

This is pretty useful, and it also works with pipes.

Is this the best way to do this? Is there an alternative that I should consider?

  • That's how I'd do it. Are you running into a particular problem with this approach?
    – Bratchley
    Oct 27, 2014 at 16:55
  • @JoelDavis Nope, just wondering if there was a better way to do it. From the answers I've received, it looks like there is! :)
    – wchargin
    Oct 27, 2014 at 18:10

3 Answers 3


The alternative is to use braces instead of parentheses. This change executes the commands in the current shell, not in a subshell

echo "Some normal commands"
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"

ref: https://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

This is particularly relevant when you're modifying variables inside the group:

$ x=5; ( x=10; echo inside: $x; ); echo outside: $x
inside: 10
outside: 5

$ x=5; { x=10; echo inside: $x; }; echo outside: $x
inside: 10
outside: 10
  • Excellent—thanks! This works better with my indentation as well. (Vim indents { } but not ( ).)
    – wchargin
    Oct 27, 2014 at 17:14
  • 2
    Note that you can collapse the command group into a single line either way; e.g., (echo msg1; echo msg2) – but, with braces, it has to be { echo msg1; echo msg2;}, with a space after the { and a semicolon (;) or an ampersand (&) before the }. Oct 27, 2014 at 23:10

Glenn's answer is a good one -- the distinction between ( ... ) and { ... } is important.

One strategy I often use for error output like what's in your question is the tee command. You could do something like this:

echo "Normal output"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "Warning text"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "This event is logged."
} | tee -a $logfile >&2
echo "More normal output"

The tee command will send output to two places; -a option "appends" output to the named file, and the command will also pass input along to stdout. The >&2 at the end of the line redirects tee's stdout to stderr, which may be handled differently (i.e. in a cron job).

One other tip that I often use in shell scripts is to change the behaviour of debug or verbose output based on whether the script is running on a terminal or has a -v option provided. For example:


# Set defaults
if [ -t 0 ]; then
  Verbose=true; vflag="-v"
  Verbose=false; vflag=""
Debug=false; AskYN=true; Doit=true

# Detect options (altering defaults)
while getopts vdqbn opt; do
  case "$opt" in
    v)  Verbose=true; vflag="-v" ;;             # Verbose mode
    d)  Debug=true; Verbose=true; vflag="-v" ;; # Very Verbose
    q)  Verbose=false; vflag="" ;;              # quiet mode (non-verbose)
    b)  AskYN=false ;;                          # batch mode
    n)  Doit=false ;;                           # test mode
    *)  usage; exit 1 ;;

# Shift our options for further processing
shift $(($OPTIND - 1))

$Verbose && echo "INFO: Verbose output is turned on." >&2
$Debug && echo "INFO: In fact, expect to be overrun." >&2

# Do your thing here
if $AskYN; then
  read -p "Continue? " choice
  case "$choice" in
    Y|y) $Doit && somecommand ;;
    *) echo "Done." ;;

Scripts can start with something generic like this at the top, with Verbose and Debug output spattered throughout the script. It's just one way to do it -- there are many, and different folks will all have their own way to handle this stuff, especially if they've been around a while. :)

One more option is to handle your output with a "handler" -- a shell function that may do more intelligent things. For example:


logme() {
  case "${1^^}" in
    [IN]*)  level=notice ;;
    W*)     level=warning ;;
    A*)     level=alert ;;
    E*)     level=emerg ;;
    *)      level=notice ;;
  if [[ "$#" -eq 1 ]]; then
    # Strip off unnecessary prefixes like "INFO:"
    string="${1#+([A-Z])?(:) }"
  logger -p "${facility}.${level}" -t "$(hostname -s)" "$string"

echo "Normal output"
logme INFO "Here we go..."
somecommand | logme
echo "Additional normal output"

(Note that ${var^^} is bash-only.)

This creates a shell function which may uses your system's syslog functions (with logger command) to send things to system logs. Thelogme()` function can be used either with options that generate single lines of log data, or with multiple lines of input that are processed on stdin. Play with it if it seems appealing.

Note that this is an example and probably should not be copied verbatim unless you understand it and know that it does precisely what you need. A better idea is to take the concepts here and implement them yourself in your own scripts.

  • Thanks very much for your detailed answer! I've actually been using a function log () { cat >> $logfile }, which is basically a simpler version of your logme.
    – wchargin
    Oct 27, 2014 at 19:34
  • Also, one may be interested in ts(1); usage: { printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2.
    – wchargin
    Dec 8, 2016 at 20:09
  • @wchargin - ts(1) isn't installed on the systems I use -- FreeBSD, OSX and an old Ubuntu box. Can you tell us what provides it?
    – ghoti
    Dec 8, 2016 at 22:59
  • It's part of moreutils, which also includes some nice tools like sponge(1) (write to file only after stdin is closed, so you can do something < foo | sponge foo without clobbering foo by redirection), and vipe(1) (insert a text editor into a pipe).
    – wchargin
    Dec 9, 2016 at 2:25
  • I've been using the bracketed grouping piped to tee many times. However, and I don't understand why, it seems that the context is not the same anymore.. i.e. myvar=hello ; { myvar=goodbye; echo $myvar; } | tee testoutput.txt ; echo $myvar myvar wont change to "goodbye" outside the output grouping
    – vegatripy
    Jan 25, 2023 at 11:10

More appropriate way of doing this is with { command; } rather than (command). The reason is that when commands are grouped with () a subshell is opened to execute those commands and thus the variables that are initialized during that block will not be available to other sections of the script.

Instead when we use {} for command grouping, the commands are executed within the same shell and thus the variables will be available to other sections of the script.

echo "Some normal commands"

    echo "Error: something happened"
    echo "Warning: this incident will be logged"
} >> logfile

echo "The value of var is: $var"
echo "More normal commands"

Here, when this section is executed the $var variable retains its value, where as in the other case it will not.

  • 1
    Also when using on one line, remember to have spaces in between, and terminate the command with semicolon. Example: { command; }. Feb 4, 2016 at 11:11

You must log in to answer this question.

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