Understand redirection

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the bash category.

Last Updated: 2023-03-28

Simple redirection

echo hi > file - changes file descriptor 1 (STDOUT) to file. Same as 1>file

Therefore, if you do a command that also has errors as well as normal output, it won't appear in the file. Here's proof:

# Running ls a real dir (tmp) and a non-existent one
$ ls /tmp/ doesnotexist > file
# In the terminal, we get an error about the non-existent directory, but nothing
# about /tmp
=> ls doesnotexist: No such file or directory

# But when we look inside the file, it has the normal non-error output (but no mention of the error)
cat file
filea filb

Next up, cat < file - changes file descriptor 0 to file. I.e. takes input from there

And lastly, pipe wires STDOUT of one command to STDIN of others echo foo | cat

Non-duplicating redirection

e.g. ls /tmp doesnotexist 2> errfile. This puts the STDERR in errfile (and prevents it being printed on the terminal)

# Send errors to one file and normal output to another
$ ls /tmp/ doesnotexist 2> errfile 1> file
# Prove it worked
$ cat errfile
ls: doesnotexist: No such file or directory

$ cat file
realfile.docx anotherrealfile.pdf

Duplicating file descriptors

The following code combines STDERR into STDOUT. Now the pipe can read from STDERR as well (probably not what you want though!) However watch out - pipes can therefore swallow errors silently...

ls /tmp/ doesnotexist 2>&1 | cat

Meaning of 2>&1 - something written on STDERR will go to whatever 1 references (i.e. STDOUT)

A gotcha: Order matters

command 2>&1 > file is not the same as command >file 2>&1

Why the difference? The key is that the redirection (kinda like C pointers - reminder: & deferencing) just gives a reference to its current value. So in the first command, stderr is redirected to whatever fd1 is at the time, which, is /dev/ttys006 as shown by lsof when you ask it what is connected to the current process of the shell ($$)

$ lsof -p $$

zsh     54371 jack    0u   CHR   16,6 0t1686916       5705 /dev/ttys006
zsh     54371 jack    1u   CHR   16,6 0t1686916       5705 /dev/ttys006
zsh     54371 jack    2u   CHR   16,6 0t1686916       5705 /dev/ttys006
zsh     54371 jack   10u   CHR   16,6  0t115249       5705 /dev/ttys006

# CHR – Character special file

General thinking

Shell commands have many "outputs" - aside from what's sent to fd1 & fd2, there is a return code which can be used in logic, and a process id (say if it was backgrounded).