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?
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
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
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).
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
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
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