1. Overview
File descriptors are essential components in Bash scripting, as they provide a way to interact with files and other input/output streams. However, it’s important to properly close file descriptors after their use to prevent resource leaks and ensure efficiency. In this tutorial, we’ll explore various techniques to close file descriptors in Bash. We’ll learn what close file descriptors are and how we can use them in different ways. Then, we’ll discuss how to check whether a file descriptor is open or closed. Finally, we’ll look at a few practical applications where close file descriptors can come in handy.
2. Understanding File Descriptors
In Bash, file descriptors are numerical identifiers used to access files, pipes, and other input/output streams. There are three standard file descriptors that we use in Bash. The stdin file descriptor (0) is the default source of input for a command or a script. Similarly, the stdout file descriptor (1) is the default destination for a command’s output. In addition, the stderr (2) file descriptor is used for displaying error messages or debugging information. Let’s write a shell script to better understand them:
#!/bin/bash
while read line; do
if grep -iq "error" <<< "$line"; then
echo "Error: $line" >&2
continue
fi
echo "Read: $line"
done < input.txt
In this script, we read the contents of input.txt using the stdin file descriptor (<*). Then, we iterate through each line and check for the occurrence of “*error*“. If it contains “*error*“, we send the output of *echo* to the *stderr* stream using *>&2. In the same way, we can write the output of the script to a file using the stdout file descriptor (>):
$ script.sh > Log.txt
Moreover, file descriptors beyond 0, 1, and 2 can be opened for additional input/output streams or redirected to different files or devices. In the next sections, we’ll learn about close file descriptors.
3. Close File Descriptors
Close file descriptors release resources and terminates the connections between a process and stdin/stdout streams. They’re essential to ensure proper resource management and avoid unnecessary resource consumption. Let’s dive into a few techniques that we can use to close file descriptors in Bash.
3.1. Explicitly Closing File Descriptors
Explicitly closing file descriptors refers to deliberately closing a specific file descriptor to release the associated system resources. We can achieve this by using exec:
$ exec 3>&-
Let’s break this down:
- exec is a Bash built-in command that we can use to execute a command in the current shell or manipulate file descriptors
- 3 is the file descriptor number that we want to manipulate
- >&- is the syntax to close the specified file descriptor
So, if we have a script that contains the stdin file descriptor, we can close it using the above command:
#!/bin/bash
# Open file descriptor 3 for reading Log.txt
exec 3< Log.txt
while IFS= read -r line <&3; do
echo "Processing: $line"
done
# Close file descriptor 3
exec 3<&-
In this example, the exec 3<&-* command effectively terminates the connection to the log file. **Notably, we used *3<* for the *stdin* as opposed to *3>. The latter is used for closing a stdout file descriptor.**
3.2. Redirecting File Descriptors to /dev/null
Redirecting a stream to /dev/null is another way to suppress a file descriptor. /dev/null is a special virtual device file that discards all data written to it:
$ exec 3>/dev/null
We can redirect a stream to /dev/null when we want to suppress or discard certain output or error messages rather than terminate the connection entirely.
3.3. Close File Descriptors Within a Subshell
File descriptors can also be closed within a subshell. Any file descriptor closed within a subshell remains closed only within that subshell:
$ (exec 3>&-)
This is useful if we want to isolate and control the scope of file descriptors within a specific section in a shell script. An example for this is if we have a Bash script that performs various operations, and we want to redirect the output of a specific section to a file while ensuring that the file descriptor is closed once that section completes. As an example, let’s take this shell script:
#!/bin/bash
# Parent: Open file descriptor 3 for writing
exec 3>Log.txt
dmesg >&3
(
# Child: Output errors to another file from &3
grep -i "error" <&3 >Errors.txt
# Child: Close file descriptor 3
exec 3>&-
)
# Parent shell: Close file descriptor 3
exec 3>&-
Here’s what happens:
- exec 3>Log.txt opens file descriptor 3 for writing and associates it with Log.txt in the parent shell
- dmesg >&3 redirects the output of the command to file descriptor 3
- () parenthesis wraps the code inside them and executes it in a subshell
- exec 3>&- closes the connection to Errors.txt in the subshell
- exec 3>&- closes the connection to Log.txt in the parent shell
It’s worth noting that we didn’t open the file descriptor in the subshell because the subshell inherits the file descriptors from the parent shell. This is also true for child processes. In addition, the exec 3>&- in the subshell doesn’t affect the connection to Log.txt.
4. Checking if a File Descriptor Is Closed or Open
It’s often useful to verify in a shell script whether a file descriptor is closed or still open. We can use a couple of methods for this.
4.1. /proc/self/fd
The /proc/self/fd directory contains symbolic links to all open file descriptors. Therefore, by listing its contents, we can determine which file descriptors are open:
$ ls /proc/self/fd
0 1 2 3
Let’s open a new file descriptor 4:
$ exec 4>Log.txt
Now, let’s list fd again:
$ ls /proc/self/fd
0 1 2 3 4
4.2. Redirecting to command
Alternatively, we can redirect the output of a file descriptor to command:
$ command <&3 echo "3 is open"
3 is open
Conversely, it prints an error if a file descriptor isn’t open:
$ command <&5 echo "5 is open"
bash: 5: Bad file descriptor
In this case, command returns 1. With this knowledge, we can check for opened file descriptors in a shell script:
#!/bin/bash
if { command &<3 echo;} 2>/dev/null; then
echo "fd 3 is open"
else
echo "fd 3 is closed"
fi
5. Close File Descriptors in Practice
There are a few scenarios where we need to close file descriptors. First, it’s essential to close any unused descriptors to avoid unnecessary resource consumption. In addition to this, if a script repeatedly opens file descriptors in a loop or as part of a function, failing to close them can lead to file descriptor leaks. Finally, when we close or redirect file descriptors that we’re not using, it helps ensure that our input and output operations work smoothly. By doing this, we can make sure that the output goes exactly where we want it to go without any unnecessary information or error messages causing confusion.
6. Conclusion
In this article, we learned what file descriptors and close file descriptors are and how we can use them. Then, we explored how we can check for opened file descriptors. Finally, we covered the uses of close file descriptors.