In Bash, how to find the lowest-numbered unused file descriptor?
I know this thread is old, but believe that the best answer is missing, and would be useful to others like me who come here searching for a solution.
Bash and Zsh have built in ways to find unused file descriptors, without having to write scripts. (I found no such thing for dash, so the above answers may still be useful.)
Note: this finds the lowest unused file descriptor > 10, not the lowest overall.
$ man bash /^REDIRECTION (paragraph 2)$ man zshmisc /^OPENING FILE DESCRIPTORS
Example works with bsh and zsh.
Open an unused file descriptor, and assign the number to $FD:
$ exec {FD}>test.txt$ echo line 1 >&$FD$ echo line 2 >&$FD$ cat test.txtline 1line 2$ echo $FD10 # this number will vary
Close the file descriptor when done:
$ exec {FD}>&-
The following shows that the file descriptor is now closed:
$ echo line 3 >&$FDbash: $FD: Bad file descriptorzsh: 10: bad file descriptor
I revised my original answer and now have a one line solution for the original post.
The following function could live in a global file or sourced script (e.g. ~/.bashrc):
# Some error code mappings from errno.hreadonly EINVAL=22 # Invalid argumentreadonly EMFILE=24 # Too many open files# Finds the lowest available file descriptor, opens the specified file with the descriptor# and sets the specified variable's value to the file descriptor. If no file descriptors# are available the variable will receive the value -1 and the function will return EMFILE.## Arguments:# The file to open (must exist for read operations)# The mode to use for opening the file (i.e. 'read', 'overwrite', 'append', 'rw'; default: 'read')# The global variable to set with the file descriptor (must be a valid variable name)function openNextFd { if [ $# -lt 1 ]; then echo "${FUNCNAME[0]} requires a path to the file you wish to open" >&2 return $EINVAL fi local file="$1" local mode="$2" local var="$3" # Validate the file path and accessibility if [[ "${mode:='read'}" == 'read' ]]; then if ! [ -r "$file" ]; then echo "\"$file\" does not exist; cannot open it for read access" >&2 return $EINVAL fi elif [[ !(-w "$file") && ((-e "$file") || !(-d $(dirname "$file"))) ]]; then echo "Either \"$file\" is not writable (and exists) or the path is invalid" >&2 return $EINVAL fi # Translate mode into its redirector (this layer of indirection prevents executing arbitrary code in the eval below) case "$mode" in 'read') mode='<' ;; 'overwrite') mode='>' ;; 'append') mode='>>' ;; 'rw') mode='<>' ;; *) echo "${FUNCNAME[0]} does not support the specified file access mode \"$mode\"" >&2 return $EINVAL ;; esac # Validate the variable name if ! [[ "$var" =~ [a-zA-Z_][a-zA-Z0-9_]* ]]; then echo "Invalid variable name \"$var\" passed to ${FUNCNAME[0]}" >&2 return $EINVAL fi # we'll start with 3 since 0..2 are mapped to standard in, out, and error respectively local fd=3 # we'll get the upperbound from bash's ulimit local fd_MAX=$(ulimit -n) while [[ $fd -le $fd_MAX && -e /proc/$$/fd/$fd ]]; do ((++fd)) done if [ $fd -gt $fd_MAX ]; then echo "Could not find available file descriptor" >&2 $fd=-1 success=$EMFILE else eval "exec ${fd}${mode} \"$file\"" local success=$? if ! [ $success ]; then echo "Could not open \"$file\" in \"$mode\" mode; error: $success" >&2 fd=-1 fi fi eval "$var=$fd" return $success;}
One would use the foregoing function as follows to open files for input and output:
openNextFd "path/to/some/file" "read" "inputfile"# opens 'path/to/some/file' for read access and stores# the descriptor in 'inputfile'openNextFd "path/to/other/file" "overwrite" "log"# truncates 'path/to/other/file', opens it in write mode, and# stores the descriptor in 'log'
And one would then use the preceding descriptors as usual for reading and writing data:
read -u $inputFile dataecho "input file contains data \"$data\"" >&$log