Validation des paramètres dans un script Bash

95

J'en ai proposé un de base pour aider à automatiser le processus de suppression d'un certain nombre de dossiers lorsqu'ils deviennent inutiles.

#!/bin/bash
rm -rf ~/myfolder1/$1/anotherfolder
rm -rf ~/myfolder2/$1/yetanotherfolder
rm -rf ~/myfolder3/$1/thisisafolder

Ceci est évoqué comme ceci:

./myscript.sh <{id-number}>

Le problème est que si vous oubliez de taper le id-number (comme je l'ai fait juste à ce moment-là) , cela pourrait potentiellement supprimer beaucoup de choses que vous ne voulez vraiment pas supprimer.

Existe-t-il un moyen d'ajouter une forme de validation aux paramètres de ligne de commande? Dans mon cas, il serait bon de vérifier que a) il y a un paramètre, b) c'est numérique, et c) ce dossier existe; avant de continuer avec le script.

nickf
la source

Réponses:

158
#!/bin/sh
die () {
    echo >&2 "$@"
    exit 1
}

[ "$#" -eq 1 ] || die "1 argument required, $# provided"
echo $1 | grep -E -q '^[0-9]+$' || die "Numeric argument required, $1 provided"

while read dir 
do
    [ -d "$dir" ] || die "Directory $dir does not exist"
    rm -rf "$dir"
done <<EOF
~/myfolder1/$1/anotherfolder 
~/myfolder2/$1/yetanotherfolder 
~/myfolder3/$1/thisisafolder
EOF

edit : J'ai manqué la partie sur la vérification si les répertoires existent au début, alors j'ai ajouté cela, en complétant le script. Ont également abordé les questions soulevées dans les commentaires; correction de l'expression régulière, passée de ==à eq.

Cela devrait être un script portable compatible POSIX pour autant que je sache; il n'utilise aucun bashisme, ce qui est en fait important car /bin/shsur Ubuntu, c'est en fait de dashnos jours, non bash.

Brian Campbell
la source
n'oubliez pas de définir + e et d'utiliser '-eq' au lieu de '==' pour les comparaisons d'entiers
pistolets
Changé en -eq; qu'est-ce que set + e vous achète ici?
Brian Campbell
J'ai trouvé deux choses dans ma réponse que vous voudrez peut-être aussi corriger dans la vôtre: d'abord, le SO hilighter devient fou pour $ # (le traitant comme un commentaire). j'ai fait "$ #" pour le réparer. deuxièmement, l'expression régulière correspond également à «foo123bar». Je l'ai corrigé en faisant ^ [0-9] + $. vous pouvez également le corriger en utilisant l'option -x de grep
Johannes Schaub - litb
1
@ojblass Je manquais l'un des tests sur lesquels il parlait. Ajouter cela signifiait également ajouter ses répertoires à tester, ce qui a considérablement élargi la taille de la réponse car ils ne peuvent pas tenir sur une seule ligne. Pouvez-vous suggérer une manière plus compacte de tester l'existence de chaque répertoire?
Brian Campbell
1
selon la réponse-commentaire de @Morten Nielsen ci-dessous, le grep '$ [0-9] + ^' semble vraiment étrange. Ne devrait-il pas être «^ [0-9] + $»?
martin jakubik
21

La shsolution par Brian Campbell, bien que noble et bien exécutée, a quelques problèmes, alors j'ai pensé fournir ma propre bashsolution.

Les problèmes avec celui- shci:

  • Le tilde dans ~/foone s'étend pas à votre répertoire d'origine dans heredocs. Et ni quand il est lu par la readdéclaration ou cité dans la rmdéclaration. Ce qui signifie que vous obtiendrez des No such file or directoryerreurs.
  • Bifurquer grepet autres pour les opérations de base est stupide. Surtout lorsque vous utilisez une coquille de merde pour éviter le poids «lourd» de bash.
  • J'ai également remarqué quelques problèmes de citations, par exemple autour d'une expansion de paramètre dans le sien echo.
  • Bien que rare, la solution ne peut pas gérer les noms de fichiers contenant des retours à la ligne. (Presque aucune solution ne shpeut y faire face - c'est pourquoi je préfère presque toujours bash, c'est beaucoup plus à l'épreuve des balles et plus difficile à exploiter lorsqu'il est bien utilisé).

Bien que, oui, utiliser /bin/shpour votre hashbang signifie que vous devez éviter les bashismes à tout prix, vous pouvez utiliser tous les bashismes que vous aimez, même sur Ubuntu ou quoi que ce soit lorsque vous êtes honnête et placé #!/bin/bashau sommet.

Voici donc une bashsolution plus petite, plus propre, plus transparente, probablement "plus rapide" et plus à l'épreuve des balles.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Remarquez les citations autour $1de la rmdéclaration!
  2. La -dvérification échouera également si elle $1est vide, c'est donc deux vérifications en une.
  3. J'ai évité les expressions régulières pour une raison. Si vous devez utiliser =~dans bash, vous devriez mettre l'expression régulière dans une variable. Dans tous les cas, les globs comme le mien sont toujours préférables et pris en charge dans des versions beaucoup plus bash.
lhunath
la source
1
Alors, est-ce que la pièce de globbing est $1 != *[^0-9]*spécifique?
grinch du
15

J'utiliserais des bash [[:

if [[ ! ("$#" == 1 && $1 =~ ^[0-9]+$ && -d $1) ]]; then 
    echo 'Please pass a number that corresponds to a directory'
    exit 1
fi

J'ai trouvé que cette FAQ était une bonne source d'informations.

Johannes Schaub - litb
la source
13

Pas aussi à l'épreuve des balles que la réponse ci-dessus, mais toujours efficace:

#!/bin/bash
if [ "$1" = "" ]
then
  echo "Usage: $0 <id number to be cleaned up>"
  exit
fi

# rm commands go here
Bill de la chaudière
la source
11

Utilisez set -uce qui entraînera l'échec immédiat du script de toute référence d'argument non définie.

Veuillez consulter l'article: Écriture de scripts robustes Bash Shell - David Pashley.com .

Shmichael
la source
C'est une solution très facile dans de nombreux cas et fonctionne également pour les fonctions. Merci!
Manfred Moser
9

La page de manuel de test ( man test) fournit tous les opérateurs disponibles que vous pouvez utiliser comme opérateurs booléens dans bash. Utilisez ces indicateurs au début de votre script (ou de vos fonctions) pour la validation d'entrée, comme vous le feriez dans n'importe quel autre langage de programmation. Par exemple:

if [ -z $1 ] ; then
  echo "First parameter needed!" && exit 1;
fi

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
whaley
la source
8

Utilisez '-z' pour tester les chaînes vides et '-d pour rechercher les répertoires.

if [[ -z "$@" ]]; then
    echo >&2 "You must supply an argument!"
    exit 1
elif [[ ! -d "$@" ]]; then
    echo >&2 "$@ is not a valid directory!"
    exit 1
fi
armes à feu
la source
2
pourquoi avez-vous besoin du double [[]]?
vehomzzz
5

Vous pouvez valider les points a et b de manière compacte en procédant comme suit:

#!/bin/sh
MYVAL=$(echo ${1} | awk '/^[0-9]+$/')
MYVAL=${MYVAL:?"Usage - testparms <number>"}
echo ${MYVAL}

Ce qui nous donne ...

$ ./testparams.sh 
Usage - testparms <number>

$ ./testparams.sh 1234
1234

$ ./testparams.sh abcd
Usage - testparms <number>

Cette méthode devrait fonctionner correctement dans sh.

MattK
la source
2

validation d'argument One Liner Bash, avec et sans validation de répertoire

Voici quelques méthodes qui ont fonctionné pour moi. Vous pouvez les utiliser dans l'espace de noms du script global (si dans l'espace de noms global, vous ne pouvez pas référencer les variables intégrées de la fonction)

une doublure rapide et sale

: ${1?' You forgot to supply a directory name'}

production:

./my_script: line 279: 1: You forgot to supply a directory name

Fancier - nom de la fonction et utilisation

${1? ERROR Function: ${FUNCNAME[0]}() Usage: " ${FUNCNAME[0]} directory_name"}

production:

./my_script: line 288: 1:  ERROR Function: deleteFolders() Usage:  deleteFolders directory_name

Ajoutez une logique de validation complexe sans encombrer votre fonction actuelle

Ajoutez la ligne suivante dans la fonction ou le script qui reçoit l'argument.

: ${1?'forgot to supply a directory name'} && validate $1 || die 'Please supply a valid directory'

Vous pouvez ensuite créer une fonction de validation qui fait quelque chose comme

validate() {

    #validate input and  & return 1 if failed, 0 if succeed
    if [[ ! -d "$1" ]]; then
        return 1
    fi
}

et une fonction die qui abandonne le script en cas d'échec

die() { echo "$*" 1>&2 ; exit 1; }

Pour des arguments supplémentaires, ajoutez simplement une ligne supplémentaire, répliquant le format.

: ${1?' You forgot to supply the first argument'}
: ${2?' You forgot to supply the second argument'}
AndrewD
la source
1

Ancien message mais j'ai pensé que je pourrais contribuer de toute façon.

Un script n'est sans doute pas nécessaire et avec une certaine tolérance aux caractères génériques pourrait être exécuté à partir de la ligne de commande.

  1. wild n'importe où correspondant. Permet de supprimer toute occurrence de sous "dossier"

    $ rm -rf ~/*/folder/*
  2. Shell a répété. Permet de supprimer les dossiers spécifiques pré et post avec une seule ligne

    $ rm -rf ~/foo{1,2,3}/folder/{ab,cd,ef}
  3. Shell itéré + var (testé BASH).

    $ var=bar rm -rf ~/foo{1,2,3}/${var}/{ab,cd,ef}
Xarses
la source