Überprüfen von Parametern für ein Bash-Skript

95

Ich habe mir eine grundlegende Idee ausgedacht, um das Entfernen einer Reihe von Ordnern zu automatisieren, wenn diese nicht mehr benötigt werden.

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

Dies wird wie folgt hervorgerufen:

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

Das Problem ist, dass wenn Sie vergessen, das einzugeben id-number (wie ich es gerade getan habe) , möglicherweise viele Dinge gelöscht werden, die Sie wirklich nicht löschen möchten.

Gibt es eine Möglichkeit, den Befehlszeilenparametern irgendeine Form der Validierung hinzuzufügen? In meinem Fall wäre es gut zu überprüfen, ob a) es einen Parameter gibt, b) er numerisch ist und c) dieser Ordner existiert; bevor Sie mit dem Skript fortfahren.

nickf
quelle

Antworten:

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

Bearbeiten : Ich habe den Teil über die Überprüfung, ob die Verzeichnisse zuerst vorhanden sind, verpasst, also habe ich diesen hinzugefügt, um das Skript zu vervollständigen. Haben auch Probleme angesprochen, die in Kommentaren angesprochen wurden; korrigierte den regulären Ausdruck, wechselte von ==zu eq.

Soweit ich das beurteilen kann, sollte dies ein portables, POSIX-kompatibles Skript sein. es verwendet keine bashisms, die da eigentlich wichtig ist , /bin/shauf Ubuntu ist eigentlich in dashdiesen Tagen, nicht bash.

Brian Campbell
quelle
Denken Sie daran, + e zu setzen und '-eq' anstelle von '==' für ganzzahlige Vergleiche zu verwenden
Waffen
Es wurde in -eq geändert. was kauft set + e dir hier?
Brian Campbell
Ich habe in meiner Antwort zwei Dinge gefunden, die Sie vielleicht auch in Ihrer beheben möchten: Erstens ist der SO-Hilighter verrückt nach $ # (behandelt ihn als Kommentar). Ich habe "$ #" gemacht, um das Problem zu beheben. Zweitens stimmt der Regex auch mit "foo123bar" überein. Ich habe es behoben, indem ich ^ [0-9] + $ gemacht habe. Sie können das
Problem
1
@ojblass Ich habe einen der Tests verpasst, nach denen er gefragt hat. Das Hinzufügen in bedeutete auch das Hinzufügen seiner Verzeichnisse zum Testen, wodurch die Größe der Antwort erheblich erweitert wurde, da sie nicht in eine Zeile passen. Können Sie eine kompaktere Methode zum Testen der Existenz jedes Verzeichnisses vorschlagen?
Brian Campbell
1
Laut @Morten Nielsens Antwortkommentar unten sieht das grep '$ [0-9] + ^' in der Tat seltsam aus. Sollte es nicht '^ [0-9] + $' sein?
Martin Jakubik
21

Die shLösung von Brian Campbell, obwohl edel und gut ausgeführt, hat ein paar Probleme, also dachte ich, ich würde meine eigene bashLösung anbieten.

Die Probleme mit dem sheinen:

  • Die Tilde in erweitert ~/foosich nicht auf Ihr Heimatverzeichnis innerhalb von Heredocs. Und weder, wenn es von der readAussage gelesen noch in der Aussage zitiert wird rm. Was bedeutet, dass Sie No such file or directoryFehler bekommen .
  • Das Abzweigen grepund dergleichen für Grundoperationen ist dumm. Besonders wenn Sie eine beschissene Schale verwenden, um das "schwere" Gewicht von Bash zu vermeiden.
  • Ich bemerkte auch einige Probleme beim Zitieren, zum Beispiel im Zusammenhang mit einer Parametererweiterung in seinem echo.
  • Obwohl selten, kann die Lösung keine Dateinamen verarbeiten, die Zeilenumbrüche enthalten. (Fast keine Lösung shkann damit umgehen - weshalb ich es fast immer vorziehe bash, es ist weitaus kugelsicherer und schwerer auszunutzen, wenn es gut verwendet wird).

Während die Verwendung /bin/shfür Ihren Hashbang bedeutet, dass Sie bashIsmen um jeden Preis vermeiden müssen , können Sie alle bashIsmen verwenden, die Sie mögen, sogar unter Ubuntu oder so weiter, wenn Sie ehrlich sind und ganz #!/bin/bashoben stehen.

Hier ist eine bashLösung, die kleiner, sauberer, transparenter, wahrscheinlich "schneller" und kugelsicherer ist.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Beachten Sie die Anführungszeichen $1in der rmAnweisung!
  2. Die -dPrüfung schlägt auch fehl, wenn sie $1leer ist. Das sind also zwei Prüfungen in einer.
  3. Ich habe reguläre Ausdrücke aus einem bestimmten Grund vermieden. Wenn Sie =~in bash verwenden müssen, sollten Sie den regulären Ausdruck in eine Variable einfügen. In jedem Fall sind Globs wie meine immer vorzuziehen und werden in weitaus mehr Bash-Versionen unterstützt.
lhunath
quelle
1
Ist das Globbing Piece $1 != *[^0-9]*Bash dann spezifisch?
grinch
15

Ich würde Bashs verwenden [[:

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

Ich fand diese FAQ eine gute Informationsquelle.

Johannes Schaub - litb
quelle
13

Nicht so kugelsicher wie die obige Antwort, aber dennoch effektiv:

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

# rm commands go here
Kesselrechnung
quelle
9

Die Manpage für test ( man test) enthält alle verfügbaren Operatoren, die Sie als boolesche Operatoren in bash verwenden können. Verwenden Sie diese Flags am Anfang Ihres Skripts (oder Ihrer Funktionen) für die Eingabevalidierung wie in jeder anderen Programmiersprache. Beispielsweise:

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

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
Whaley
quelle
8

Verwenden Sie '-z', um nach leeren Zeichenfolgen zu suchen, und '-d, um nach Verzeichnissen zu suchen.

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
Waffen
quelle
2
warum brauchst du double [[]]?
vehomzzz
5

Sie können die Punkte a und b kompakt validieren, indem Sie Folgendes tun:

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

Welches gibt uns ...

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

$ ./testparams.sh 1234
1234

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

Diese Methode sollte in sh gut funktionieren.

MattK
quelle
2

Einzeiler-Bash-Argumentvalidierung mit und ohne Verzeichnisvalidierung

Hier sind einige Methoden, die für mich funktioniert haben. Sie können sie entweder im globalen Skript-Namespace verwenden (wenn Sie im globalen Namespace nicht auf die in die Funktion integrierten Variablen verweisen können)

schnell und schmutzig ein Liner

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

Ausgabe:

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

Züchter - Name und Verwendung der Lieferfunktion

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

Ausgabe:

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

Fügen Sie eine komplexe Validierungslogik hinzu, ohne Ihre aktuelle Funktion zu überladen

Fügen Sie die folgende Zeile in die Funktion oder das Skript ein, die bzw. das das Argument empfängt.

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

Sie können dann eine Validierungsfunktion erstellen, die ungefähr so ​​funktioniert

validate() {

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

und eine Die-Funktion, die das Skript bei einem Fehler abbricht

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

Fügen Sie für zusätzliche Argumente einfach eine zusätzliche Zeile hinzu und replizieren Sie das Format.

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

Alter Beitrag, aber ich dachte, ich könnte trotzdem etwas beitragen.

Ein Skript ist wohl nicht erforderlich und könnte mit einer gewissen Toleranz gegenüber Platzhaltern über die Befehlszeile ausgeführt werden.

  1. wild überall passend. Entfernen Sie alle Vorkommen von Unterordnern.

    $ rm -rf ~/*/folder/*
  2. Shell iteriert. Entfernen Sie die spezifischen Vor- und Nachordner mit einer Zeile

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

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