web-dev-qa-db-fra.com

Tester plusieurs conditions de fichier en un seul coup (BASH)?

Souvent, lors de l'écriture pour le shell bash, il faut tester si un fichier (ou répertoire) existe (ou n'existe pas) et prendre les mesures appropriées. Les plus courants parmi ces tests sont ...

-e - le fichier existe, -f - le fichier est un fichier standard (pas un répertoire ou un fichier de périphérique), -s - le fichier n'est pas de taille nulle, -d - le fichier est un répertoire, -r - le fichier dispose d'une autorisation de lecture, -w - le fichier a été écrit, ou -x autorisation d'exécution (pour l'utilisateur exécutant le test)

Ceci est facilement confirmé comme démontré sur ce répertoire accessible en écriture par l'utilisateur ....

#/bin/bash

if [ -f "/Library/Application Support" ]; then
echo 'YES SIR -f is fine'
else echo 'no -f for you'
fi

if [ -w "/Library/Application Support" ]; then
echo 'YES SIR -w is fine'
else echo 'no -w for you'
fi

if [ -d "/Library/Application Support" ]; then
echo 'YES SIR -d is fine'
else echo 'no -d for you'
fi

➝ pas de -f pour vous ✓
➝ OUI SIR -w est très bien ✓
➝ OUI SIR -d est bien ✓

Ma question, bien qu'apparemment évidente et peu susceptible d'être impossible - est de savoir comment simplement combiner ces tests, sans avoir à les effectuer séparément pour chaque condition .. Malheureusement ...

if [ -wd "/Library/Application Support" ]  
  ▶  -wd: unary operator expected

if [ -w | -d "/Library/Application Support" ]   
  ▶  [: missing `]'
  ▶  -d: command not found

if [ -w [ -d "/Library.... ]]   &  if [ -w && -d "/Library.... ] 
  ▶  [: missing `]'

➝ pas de mot pour vous ✖
➝ no -w | -d pour toi ✖
➝ pas de [-w [-d ..]] pour vous ✖
➝ pas de -w && -d pour vous ✖

Qu'est-ce que j'oublie ici?

34
Alex Gray

Vous pouvez utiliser des opérateurs logiques pour plusieurs conditions, par exemple -a pour AND:

MYFILE=/tmp/data.bin
if [ -f "$MYFILE"  -a  -r "$MYFILE"  -a  -w "$MYFILE" ]; then
    #do stuff
fi
unset MYFILE
35
Kerrek SB

Bien sûr, vous devez utiliser AND d'une manière ou d'une autre, comme Kerrek (+1) et Ben (+1) l'ont souligné. Vous pouvez le faire de différentes manières. Voici un résultat de micro-benchmark pour quelques méthodes:

Manière la plus portable et lisible:

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

toujours portable, moins lisible, plus rapide:

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

bashisme, mais lisible et plus rapide

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

bashisme, mais assez lisible et plus rapide.

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

Notez que dans bash, "[" est une fonction intégrée, donc bash utilise une commande interne et non un lien symbolique vers/usr/bin/test exacutable. Le "[[" est un mot-clé bash. Le moyen le plus lent sera donc:

time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
real    14m8.678s
22
Michał Šrajer

Tu veux -a un péché -f foo -a -d foo (en fait, ce test serait faux, mais vous avez l'idée).

Vous étiez proche de & vous aviez juste besoin && un péché [ -f foo ] && [ -d foo ] bien que cela exécute plusieurs commandes plutôt qu'une.

Voici ne page de manuel de test qui est la commande qui [ est un lien vers. Les implémentations modernes de test ont beaucoup plus de fonctionnalités (avec la version intégrée de Shell [[ qui est documenté dans la page de manuel de votre Shell).

8
Ben Jackson
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
3
jonretting

Pourquoi ne pas écrire une fonction pour le faire?

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
}

Ensuite, utilisez-le simplement comme:

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
}

Et vous devriez obtenir:

/ 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
2
chad

Cela semble fonctionner (notez les doubles crochets):

#!/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
2
Gao