Weryfikacja parametrów w skrypcie Bash

96

Wymyśliłem podstawowy, który pomaga zautomatyzować proces usuwania wielu folderów, gdy stają się niepotrzebne.

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

Jest to wywołane w ten sposób:

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

Problem polega na tym, że jeśli zapomnisz wpisać id-number (tak jak wtedy zrobiłem) , może to potencjalnie usunąć wiele rzeczy, których naprawdę nie chcesz usunąć.

Czy istnieje sposób, aby dodać dowolną formę walidacji do parametrów wiersza poleceń? W moim przypadku dobrze byłoby sprawdzić, czy a) jest jeden parametr, b) jest numeryczny ic) czy folder istnieje; przed kontynuowaniem skryptu.

nickf
źródło

Odpowiedzi:

159
#!/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

edycja : przegapiłem część dotyczącą sprawdzania, czy katalogi istnieją na początku, więc dodałem to w, kończąc skrypt. Odnieśli się również do kwestii poruszonych w komentarzach; naprawiono wyrażenie regularne, przełączono z ==na eq.

O ile wiem, powinien to być przenośny skrypt zgodny z POSIX; nie używa żadnych bashizmów, co jest właściwie ważne, ponieważ /bin/shw Ubuntu tak naprawdę jest w dashdzisiejszych czasach bash.

Brian Campbell
źródło
pamiętaj, aby ustawić + e i użyć '-eq' zamiast '==' do porównań całkowitych
guns
Zmieniono to na -eq; co tu set + e kupuje?
Brian Campbell
W mojej odpowiedzi znalazłem dwie rzeczy, które możesz również chcieć naprawić w swojej: po pierwsze, podpora SO wariuje za $ # (traktując to jako komentarz). zrobiłem "$ #", aby to naprawić. po drugie, wyrażenie regularne również dopasowuje „foo123bar”. naprawiłem to robiąc ^ [0-9] + $. możesz to również naprawić, używając opcji grepa -x
Johannes Schaub - litb
1
@ojblass Brakowało mi jednego z testów, o który pytał. Dodanie tego oznaczało również dodanie jego katalogów do testowania, co znacznie zwiększyło rozmiar odpowiedzi, ponieważ nie mieszczą się one w jednej linii. Czy możesz zaproponować bardziej zwarty sposób testowania istnienia każdego katalogu?
Brian Campbell
1
zgodnie z komentarzem @Morten Nielsen poniżej, grep '$ [0-9] + ^' wygląda naprawdę dziwnie. Czy nie powinno to być „^ [0-9] + $”?
martin Jakubik
21

shRozwiązanie przez Brian Campbell, a szlachetny i dobrze wykonany, ma kilka problemów, więc pomyślałem, że zapewniają własne bashrozwiązanie.

Problemy z shjednym:

  • Tylda w ~/foonie rozwija się do katalogu głównego w heredocach. Ani wtedy, gdy jest czytane przez readoświadczenie lub cytowane w rmoświadczeniu. Co oznacza, że ​​dostanieszNo such file or directory błędy.
  • Rozwidlenie grep i takie podstawowe operacje są głupie. Zwłaszcza, gdy używasz kiepskiej muszli, aby uniknąć „ciężkiego” uderzenia.
  • Zauważyłem też kilka problemów z cytowaniem, na przykład związanych z rozszerzaniem parametrów w jego echo .
  • Chociaż jest to rzadkie, rozwiązanie nie radzi sobie z nazwami plików, które zawierają znaki nowej linii. (Prawie żadne rozwiązanie nie shmoże sobie z nimi poradzić - dlatego prawie zawsze wolę bash, jest znacznie bardziej kuloodporny i trudniejszy do wykorzystania, gdy jest dobrze używany).

Chociaż tak, używanie /bin/shdo haszowania oznacza, że ​​musisz unikać bashizmów za wszelką cenę, możesz używać wszystkich bashizmów, które lubisz, nawet na Ubuntu lub czegokolwiek, jeśli jesteś uczciwy i szczery#!/bin/bash na szczycie.

Oto bashrozwiązanie, które jest mniejsze, czystsze, bardziej przejrzyste, prawdopodobnie „szybsze” i bardziej kuloodporne.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Zwróć uwagę na cytaty $1wrm oświadczeniu!
  2. -dSprawdzić, czy nie będzie również$1 jest pusta, więc to dwie kontrole w jednym.
  3. Nie bez powodu unikałem wyrażeń regularnych. Jeśli musisz użyć =~w bash, powinieneś umieścić wyrażenie regularne w zmiennej. W każdym razie, globy takie jak moja są zawsze preferowane i obsługiwane w znacznie większej liczbie wersji basha.
lhunath
źródło
1
Czy w takim razie $1 != *[^0-9]*bash globbing piece jest specyficzny?
grinch
15

Użyłbym basha [[:

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

Uznałem, że ten FAQ jest dobrym źródłem informacji.

Johannes Schaub - litb
źródło
13

Nie tak kuloodporny jak powyższa odpowiedź, ale nadal skuteczny:

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

# rm commands go here
Bill Boiler
źródło
11

Użycie, set -uktóre spowoduje, że każde nieustawione odwołanie do argumentu natychmiast zakończy działanie skryptu.

Zapoznaj się z artykułem: Writing Robust Bash Shell Scripts - David Pashley.com .

shmichael
źródło
Jest to bardzo łatwa poprawka w wielu przypadkach i działa również w przypadku funkcji. Dzięki!
Manfred Moser
9

Strona man dla test ( man test) zawiera wszystkie dostępne operatory, których możesz użyć jako operatorów logicznych w bash. Użyj tych flag na początku skryptu (lub funkcji) do sprawdzania poprawności danych wejściowych, tak jak w każdym innym języku programowania. Na przykład:

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

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
wieloryb
źródło
8

Użyj „-z”, aby sprawdzić, czy nie ma pustych łańcuchów, i „-d”, aby sprawdzić katalogi.

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
pistolety
źródło
2
dlaczego potrzebujesz podwójnego [[]]?
Vehomzzz
5

Możesz zwięźle sprawdzić poprawność punktów a i b, wykonując coś takiego:

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

Co daje nam ...

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

$ ./testparams.sh 1234
1234

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

Ta metoda powinna działać dobrze w sh.

MattK
źródło
2

walidacja argumentów Bash z jedną linijką, zi bez walidacji katalogu

Oto kilka metod, które zadziałały w moim przypadku. Możesz ich używać w globalnej przestrzeni nazw skryptu (jeśli w globalnej przestrzeni nazw nie możesz odwoływać się do zmiennych wbudowanych funkcji)

szybki i brudny jeden wkład

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

wynik:

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

Hodowca - nazwa i zastosowanie funkcji dostaw

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

wynik:

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

Dodaj złożoną logikę walidacji bez zaśmiecania bieżącej funkcji

Dodaj następujący wiersz w funkcji lub skrypcie, który otrzymuje argument.

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

Następnie możesz utworzyć funkcję walidacji, która robi coś podobnego

validate() {

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

oraz funkcja matrycy, która przerywa skrypt w przypadku niepowodzenia

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

Aby uzyskać dodatkowe argumenty, po prostu dodaj dodatkową linię, replikując format.

: ${1?' You forgot to supply the first argument'}
: ${2?' You forgot to supply the second argument'}
AndrewD
źródło
1

Stary post, ale pomyślałem, że i tak mógłbym wnieść swój wkład.

Skrypt prawdopodobnie nie jest konieczny iz pewną tolerancją dla symboli wieloznacznych mógłby zostać wykonany z wiersza poleceń.

  1. dzikie dopasowanie wszędzie. Pozwala usunąć wszelkie wystąpienia pod „folderu”

    $ rm -rf ~/*/folder/*
    
  2. Powłoka iterowana. Pozwala usunąć określone foldery pre i post za pomocą jednej linii

    $ rm -rf ~/foo{1,2,3}/folder/{ab,cd,ef}
    
  3. Powłoka iterowana + var (przetestowana przez BASH).

    $ var=bar rm -rf ~/foo{1,2,3}/${var}/{ab,cd,ef}
    
Xarses
źródło