Shell Mastery — File Descriptors
The Three Default File Descriptors
Every process gets three at birth:
0 = stdin (keyboard input) 1 = stdout (normal output) 2 = stderr (error output)
# See them live
ls -la /proc/$$/fd
# 0 -> /dev/pts/0 (your terminal)
# 1 -> /dev/pts/0
# 2 -> /dev/pts/0
Redirection
Basics
cmd > file # stdout to file (same as 1>file)
cmd 2> file # stderr to file
cmd &> file # both stdout + stderr to file
cmd > file 2>&1 # same thing — stdout to file, stderr follows stdout
cmd >> file # append stdout
cmd 2>> file # append stderr
Discard output
cmd > /dev/null # discard stdout
cmd 2> /dev/null # discard stderr
cmd &> /dev/null # discard everything
cmd > /dev/null 2>&1 # same — portable POSIX form
Separate stdout and stderr into different files
cmd > stdout.log 2> stderr.log
Swap stdout and stderr
cmd 3>&1 1>&2 2>&3 3>&-
# 3=copy of stdout, 1→stderr, 2→old stdout, close 3
Custom File Descriptors (3-9)
Open fd 3 for writing
exec 3> /tmp/custom.log
echo "line 1" >&3
echo "line 2" >&3
exec 3>&- # close fd 3
cat /tmp/custom.log
Open fd 4 for reading
exec 4< /etc/passwd
while IFS= read -r line <&4; do
echo "$line"
done
exec 4<&- # close fd 4
Read and write on same fd
exec 5<> /tmp/data.txt # open for read+write
echo "hello" >&5 # write
cat <&5 # read
exec 5>&- # close
Log to file AND terminal simultaneously
exec 3>&1 # save stdout to fd 3
exec 1> >(tee /tmp/script.log) 2>&1 # tee stdout+stderr
echo "This goes to terminal AND log"
exec 1>&3 3>&- # restore stdout
Inspect File Descriptors
See what fds a process has open
ls -la /proc/$$/fd # current shell
ls -la /proc/self/fd # same thing
ls -la /proc/<PID>/fd # any process
Count open fds
ls /proc/$$/fd | wc -l
fd limits
ulimit -n # soft limit (per process)
ulimit -Hn # hard limit
cat /proc/sys/fs/file-max # system-wide max
cat /proc/sys/fs/file-nr # allocated free max
Detailed info about a specific fd
cat /proc/$$/fdinfo/0 # flags, position for fd 0 (stdin)
Competition Patterns
Capture exit code while piping stderr
{ cmd 2>&1; echo $? > /tmp/exit_code; } | tee output.log
rc=$(cat /tmp/exit_code)
Process substitution uses fds under the hood
diff <(cmd1) <(cmd2)
# <() creates /dev/fd/N pipes — check:
ls -la /dev/fd/
Named pipe (fifo) — manual fd control
mkfifo /tmp/mypipe
cmd1 > /tmp/mypipe & # writer runs in background
cmd2 < /tmp/mypipe # reader blocks until data arrives
rm /tmp/mypipe
Here string uses fd 0 (stdin)
read -r var <<< "hello world"
echo "$var" # hello world
Redirect inside a loop (common gotcha)
# WRONG — pipe creates subshell, variable lost
cat file.txt | while read line; do count=$((count+1)); done
echo $count # empty!
# RIGHT — redirect, stays in current shell
while read line; do count=$((count+1)); done < file.txt
echo $count # correct
Read from multiple files simultaneously
exec 3< file1.txt
exec 4< file2.txt
while IFS= read -r line1 <&3 && IFS= read -r line2 <&4; do
printf "%-30s %s\n" "$line1" "$line2"
done
exec 3<&- 4<&-
Where to Find This in System Docs
man pages — the authoritative reference
man bash # search: /REDIRECTION (section 3.6)
# search: /DUPLICATING (fd duplication)
man zshmisc # search: /REDIRECTION
man zshall # everything zsh in one page
Kernel-level understanding (how it actually works)
man 2 open # open() syscall — creates a new fd
man 2 close # close() syscall — releases a fd
man 2 dup2 # dup2() — how 2>&1 works internally
man 2 pipe # pipe() — how | creates a pair of fds
man 2 read # read() — how data flows through a fd
man 2 write # write() — how data is sent to a fd
man 7 pipe # pipe semantics, buffer sizes (64K default)
bash builtin help
help exec # exec builtin — fd manipulation
help read # read from specific fd
help mapfile # read fd into array
System files
ls /proc/$$/fd # live view of your shell's fds
ls -la /dev/fd/ # symlinks to /proc/self/fd/
cat /proc/$$/fdinfo/0 # detailed info on fd 0
ls /usr/share/doc/bash*/ # bash documentation on disk
The key man page search patterns
# In man bash, search for these sections:
/REDIRECTION # all redirection syntax
/Here Documents # heredocs
/Here Strings # <<< syntax
/Duplicating # 2>&1 syntax
/Moving # fd moving with -
/Opening # exec N<>file