Understanding the exec 3<&0 Mechanism in Linux

One day, I suddenly wanted to implement a Redis client using Bash, adhering to the principle of <span>not reinventing the wheel</span>, and discovered an open-source library redi.sh[1].

As a client, it is essential to establish a TCP connection with the server. Driven by curiosity, I studied its source code:

exec {FD}&lt;&gt; /dev/tcp/"$REDIS_HOST"/"$REDIS_PORT"

As shown above, I found a method to establish a TCP connection in Bash.

This introduces two key points:

  1. The usage of exec in Bash (<span>Content of this article</span>)
  2. How to establish a TCP connection in Bash[2]

Detailed Explanation of the exec Command

$ bash -c 'help exec'
exec: exec [-cl] [-a name] file [redirection ...]
    Exec FILE, replacing this shell with the specified program.
    If FILE is not specified, the redirections take effect in this
    shell.  If the first argument is `-l', then place a dash in the
    zeroth arg passed to FILE, as login does.  If the `-c' option
    is supplied, FILE is executed with a null environment.  The `-a'
    option means to make set argv[0] of the executed process to NAME.
    If the file cannot be executed and the shell is not interactive,
    then the shell exits, unless the shell option `execfail' is set.

This command has two usage modes.

1: Execute an external command and replace the current Shell process

This is the fundamental function of exec. It follows its original meaning at the operating system level: to execute.

Working Mechanism

  • Typically, when you run an external command (like ls, cat, vim, etc.), Bash creates a new child process to run that command. After the command ends, control returns to the parent process (which is the original Bash Shell).
  • When using exec to run a command, it does not create a child process; instead, it allows the current Shell process to “transform” into that new command. The new command’s process PID is the same as the original Shell.

<span>Syntax format:</span>

exec command [arguments...]

To emphasize:<span> </span>

  • <span>Process Replacement</span>: The new command completely replaces the current Shell.
  • <span>Shell Exit</span>: Once the new command finishes executing, the entire process exits. This means if your current Shell is a login session, executing exec ls will cause your terminal window or session to close directly after displaying the ls results, because the Shell process that hosted it has transformed into ls and ls has completed execution.

2: Manipulate the file descriptors of the current Shell

This is a more common and practical function of exec in Shell scripting. When exec is followed by no command but only file redirection operations, its role is to change the file descriptors of the current Shell process.

Working Mechanism

File descriptors are integers used by the operating system to track open files. Bash has three by default:

  • 0: Standard Input
  • 1: Standard Output
  • 2: Standard Error

Using exec can open, close, or copy these file descriptors, and this effect is permanent, affecting all subsequent commands run in that Shell or script.

<span>Syntax format:</span>

exec redirection operation

<span>Common Usage and Examples</span>

  1. Permanent redirection of standard output/error
# Redirect all standard output of the current Shell and its subcommands to the file log.txt
exec &gt; log.txt
echo "This line will be written to log.txt, not the screen"
ls # The result of this command will also be written to log.txt
# Redirect both standard output and standard error to a file
exec &gt; output.log 2&gt;&amp;1
echo "Standard output"
ls /nonexistent_directory # Error messages will also go to output.log
  1. Read input from a file as the “main input” of the script
# Change the standard input of the current Shell to come from the file input.txt
exec &lt; input.txt
while read line; do
    echo "Read line: $line"
done
# The read command will no longer read from the keyboard, but from input.txt
  1. Open a file as a custom file descriptor

This is key to implementing complex I/O operations.

# Open a file for reading, assigning file descriptor 3
exec 3&lt; /etc/passwd

# Open a file for writing (append), assigning file descriptor 4
exec 4&gt;&gt; /tmp/debug.log

# Use custom file descriptors
read -u 3 user_line # Read a line from file descriptor 3 (i.e., /etc/passwd)
echo "Debug info: $user_line" &gt;&amp;4 # Write info to file descriptor 4 (i.e., /tmp/debug.log)

# After operations, close the file descriptors
exec 3&lt;&amp;-
exec 4&gt;&amp;-
  1. Backup and restore standard file descriptors
# Backup standard input
exec 3&lt;&amp;0

# Temporarily redirect standard input to a file
exec &lt; input.txt

# ... some operations ...

# Restore standard input
exec 0&lt;&amp;3
exec 3&lt;&amp;- # Close backup

Useful Code Snippets

exec 3&lt;&amp;0 # First, copy file descriptor 0 to file descriptor 3, effectively backing up file descriptor 0

exec 0&lt;~/access.log # Read file into file descriptor 0

while read LINE # This variable reads data from stdin (i.e., descriptor 0)
do
    echo $LINE
done

exec 0&lt;&amp;3 # Copy file descriptor 3 back to file descriptor 0 (restore reading from keyboard)
exec 3&lt;&amp;- # Close backup
# exec redirect TCP input/output
# 8 is a user-specified file descriptor; &lt; indicates read, &gt; indicates write, i.e., open in RW mode
exec 8&lt;&gt;/dev/tcp/www.baidu.com/80

# Output redirection, write data, send request
echo 'GET / HTTP/1.1'&gt; &amp;8

# Input redirection, read data, receive response
cat &lt;&amp;8

# Close
exec 8&lt;&amp;-;
exec 8&gt;&amp;-;

References

[1]

redi.sh: https://github.com/crypt1d/redi.sh

[2]

How to establish a TCP connection in Bash: https://www.cnblogs.com/chengmo/archive/2010/10/22/1858302.html

Leave a Comment