In Bash, how to find the lowest-numbered unused file descriptor?

后端 未结 6 1760
梦毁少年i
梦毁少年i 2021-01-30 16:45

In a Bash-script, is it possible to open a file on \"the lowest-numbered file descriptor not yet in use\"?

I have looked around for how to do this, but it seems that Bas

6条回答
  •  迷失自我
    2021-01-30 17:18

    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.h
    readonly EINVAL=22   # Invalid argument
    readonly 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 data
    echo "input file contains data \"$data\"" >&$log
    

提交回复
热议问题