How to redirect and append both standard output and standard error to a file with Bash
BashAppendStdoutIo RedirectionStderrBash Problem Overview
To redirect standard output to a truncated file in Bash, I know to use:
cmd > file.txt
To redirect standard output in Bash, appending to a file, I know to use:
cmd >> file.txt
To redirect both standard output and standard error to a truncated file, I know to use:
cmd &> file.txt
How do I redirect both standard output and standard error appending to a file? cmd &>> file.txt
did not work for me.
Bash Solutions
Solution 1 - Bash
cmd >>file.txt 2>&1
Bash executes the redirects from left to right as follows:
>>file.txt
: Openfile.txt
in append mode and redirectstdout
there.2>&1
: Redirectstderr
to "wherestdout
is currently going". In this case, that is a file opened in append mode. In other words, the&1
reuses the file descriptor whichstdout
currently uses.
Solution 2 - Bash
There are two ways to do this, depending on your Bash version.
The classic and portable (Bash pre-4) way is:
cmd >> outfile 2>&1
A nonportable way, starting with Bash 4 is
cmd &>> outfile
(analog to &> outfile
)
For good coding style, you should
- decide if portability is a concern (then use the classic way)
- decide if portability even to Bash pre-4 is a concern (then use the classic way)
- no matter which syntax you use, don't change it within the same script (confusion!)
If your script already starts with #!/bin/sh
(no matter if intended or not), then the Bash 4 solution, and in general any Bash-specific code, is not the way to go.
Also remember that Bash 4 &>>
is just shorter syntax — it does not introduce any new functionality or anything like that.
The syntax is (beside other redirection syntax) described in the Bash hackers wiki.
Solution 3 - Bash
In Bash you can also explicitly specify your redirects to different files:
cmd >log.out 2>log_error.out
Appending would be:
cmd >>log.out 2>>log_error.out
Solution 4 - Bash
This should work fine:
your_command 2>&1 | tee -a file.txt
It will store all logs in file.txt as well as dump them in the terminal.
Solution 5 - Bash
Solution 6 - Bash
Try this:
You_command 1> output.log 2>&1
Your usage of &> x.file
does work in Bash 4. Sorry for that: (
Here comes some additional tips.
0, 1, 2, ..., 9 are file descriptors in bash.
0 stands for standard input, 1 stands for standard output, 2 stands for standard error. 3~9 is spare for any other temporary usage.
Any file descriptor can be redirected to other file descriptor or file by using operator >
or >>
(append).
Usage: <file_descriptor> > <filename | &file_descriptor>
Please see the reference in Chapter 20. I/O Redirection.
Solution 7 - Bash
Another approach:
If using older versions of Bash where &>>
isn't available, you also can do:
(cmd 2>&1) >> file.txt
This spawns a subshell, so it's less efficient than the traditional approach of cmd >> file.txt 2>&1
, and it consequently won't work for commands that need to modify the current shell (e.g. cd
, pushd
), but this approach feels more natural and understandable to me:
- Redirect standard error to standard output.
- Redirect the new standard output by appending to a file.
Also, the parentheses remove any ambiguity of order, especially if you want to pipe standard output and standard error to another command instead.
To avoid starting a subshell, you instead could use curly braces instead of parentheses to create a group command:
{ cmd 2>&1; } >> file.txt
(Note that a semicolon (or newline) is required to terminate the group command.)
Solution 8 - Bash
Redirections from script himself
You could plan redirections from the script itself:
#!/bin/bash
exec 1>>logfile.txt
exec 2>&1
/bin/ls -ld /tmp /tnt
Running this will create/append logfile.txt
, containing:
/bin/ls: cannot access '/tnt': No such file or directory
drwxrwxrwt 2 root root 4096 Apr 5 11:20 /tmp
Log to many different files
You could create two different logfiles, appending to one overall log and recreating another last log:
#!/bin/bash
if [ -e last.log ] ;then
mv -f last.log last.old
fi
exec 1> >(tee -a overall.log /dev/tty >last.log)
exec 2>&1
ls -ld /tnt /tmp
Running this script will
- if
last.log
already exist, rename them tolast.old
(overwritinglast.old
if they exist). - create a new
last.log
. - append everything to
overall.log
- output everything to the terminal.
Simple and combined logs
#!/bin/bash
[ -e last.err ] && mv -f last.err lasterr.old
[ -e last.log ] && mv -f last.log lastlog.old
exec 2> >(tee -a overall.err combined.log /dev/tty >last.err)
exec 1> >(tee -a overall.log combined.log /dev/tty >last.log)
ls -ld /tnt /tmp
So you have
last.log
last run log filelast.err
last run error filelastlog.old
previous run log filelasterr.old
previous run error fileoverall.log
appended overall log fileoverall.err
appended overall error filecombined.log
appended overall error and log combined file.- still output to the terminal
stdbuf
:
And for interactive session, use If you plan to use this in interactive shell, you must tell tee
to not buffering his input/output:
# Source this to multi-log your session
[ -e last.err ] && mv -f last.err lasterr.old
[ -e last.log ] && mv -f last.log lastlog.old
exec 2> >(exec stdbuf -i0 -o0 tee -a overall.err combined.log /dev/tty >last.err)
exec 1> >(exec stdbuf -i0 -o0 tee -a overall.log combined.log /dev/tty >last.log)
Once sourced this, you could try:
ls -ld /tnt /tmp
Solution 9 - Bash
If you care about the ordering of the content of the two streams, see @ed-morton 's answer to a similar question, here.