Test multiple file conditions in one swoop (BASH)?

前端 未结 6 1490
滥情空心
滥情空心 2020-12-13 00:22

Often when writing for the bash shell, one needs to test if a file (or Directory) exists (or doesn\'t exist) and take appropriate action. Most common amongst these test are

相关标签:
6条回答
  • 2020-12-13 00:41

    You can use logical operators to multiple conditions, e.g. -a for AND:

    MYFILE=/tmp/data.bin
    if [ -f "$MYFILE"  -a  -r "$MYFILE"  -a  -w "$MYFILE" ]; then
        #do stuff
    fi
    unset MYFILE
    
    0 讨论(0)
  • 2020-12-13 00:42
    check-file(){
        while [[ ${#} -gt 0 ]]; do
            case $1 in
               fxrsw) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" && -w "$2" ]] || return 1 ;;
                fxrs) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" ]] || return 1 ;;
                 fxr) [[ -f "$2" && -x "$2" && -r "$2" ]] || return 1 ;;
                  fr) [[ -f "$2" && -r "$2" ]] || return 1 ;;
                  fx) [[ -f "$2" && -x "$2" ]] || return 1 ;;
                  fe) [[ -f "$2" && -e "$2" ]] || return 1 ;;
                  hf) [[ -h "$2" && -f "$2" ]] || return 1 ;;
                   *) [[ -e "$1" ]] || return 1 ;;
            esac
            shift
        done
    }
    
    check-file fxr "/path/file" && echo "is valid"
    
    check-file hf "/path/folder/symlink" || { echo "Fatal error cant validate symlink"; exit 1; }
    
    check-file fe "file.txt" || touch "file.txt" && ln -s "${HOME}/file.txt" "/docs/file.txt" && check-file hf "/docs/file.txt" || exit 1
    
    if check-file fxrsw "${HOME}"; then
        echo "Your home is your home from the looks of it."
    else
        echo "You infected your own home."
    fi
    
    0 讨论(0)
  • 2020-12-13 00:44

    Of course, you need to use AND somehow as Kerrek(+1) and Ben(+1) pointed it out. You can do in in few different ways. Here is an ala-microbenchmark results for few methods:

    Most portable and readable way:

    $ time for i in $(seq 100000); do [ 1 = 1 ] && [ 2 = 2 ] && [ 3 = 3 ]; done
    real    0m2.583s
    

    still portable, less readable, faster:

    $ time for i in $(seq 100000); do [ 1 = 1 -a 2 = 2 -a 3 = 3 ]; done
    real    0m1.681s
    

    bashism, but readable and faster

    $ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
    real    0m1.285s
    

    bashism, but quite readable, and fastest.

    $ time for i in $(seq 100000); do [[ 1 = 1 && 2 = 2 && 3 = 3 ]]; done
    real    0m0.934s
    

    Note, that in bash, "[" is a builtin, so bash is using internal command not a symlink to /usr/bin/test exacutable. The "[[" is a bash keyword. So the slowest possible way will be:

    time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
    real    14m8.678s
    
    0 讨论(0)
  • 2020-12-13 00:46

    Why not write a function to do it?

    check_file () {
        local FLAGS=$1
        local PATH=$2
        if [ -z "$PATH" ] ; then
            if [ -z "$FLAGS" ] ; then
                echo "check_file: must specify at least a path" >&2
                exit 1
            fi
            PATH=$FLAGS
            FLAGS=-e
        fi
        FLAGS=${FLAGS#-}
        while [ -n "$FLAGS" ] ; do
            local FLAG=`printf "%c" "$FLAGS"`
            if [ ! -$FLAG $PATH ] ; then false; return; fi
            FLAGS=${FLAGS#?}
        done
        true
    }
    

    Then just use it like:

    for path in / /etc /etc/passwd /bin/bash
    {
        if check_file -dx $path ; then
            echo "$path is a directory and executable"
        else
            echo "$path is not a directory or not executable"
        fi
    }
    

    And you should get:

    / is a directory and executable
    /etc is a directory and executable
    /etc/passwd is not a directory or not executable
    /bin/bash is not a directory or not executable
    
    0 讨论(0)
  • 2020-12-13 00:55

    This seems to work (notice the double brackets):

    #!/bin/bash
    
    if [[ -fwd "/Library/Application Support" ]]
    then
        echo 'YES SIR -f -w -d are fine'
    else
        echo 'no -f or -w or -d for you'
    fi
    
    0 讨论(0)
  • 2020-12-13 01:04

    You want -a as in -f foo -a -d foo (actually that test would be false, but you get the idea).

    You were close with & you just needed && as in [ -f foo ] && [ -d foo ] although that runs multiple commands rather than one.

    Here is a manual page for test which is the command that [ is a link to. Modern implementations of test have a lot more features (along with the shell-builtin version [[ which is documented in your shell's manpage).

    0 讨论(0)
提交回复
热议问题