Memvalidasi parameter ke skrip Bash

95

Saya datang dengan yang dasar untuk membantu mengotomatiskan proses menghapus sejumlah folder karena tidak diperlukan.

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

Ini dibangkitkan seperti ini:

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

Masalahnya adalah jika Anda lupa mengetik id-number (seperti yang saya lakukan tadi) , maka itu berpotensi menghapus banyak hal yang sebenarnya tidak ingin Anda hapus.

Adakah cara untuk menambahkan bentuk validasi apa pun ke parameter baris perintah? Dalam kasus saya, alangkah baiknya untuk memeriksa bahwa a) ada satu parameter, b) numerik, dan c) folder itu ada; sebelum melanjutkan dengan skrip.

nickf
sumber

Jawaban:

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 : Saya melewatkan bagian tentang memeriksa apakah direktori ada pada awalnya, jadi saya menambahkannya, menyelesaikan skrip. Juga, telah membahas masalah yang diangkat dalam komentar; memperbaiki ekspresi reguler, dialihkan dari ==menjadi eq.

Ini harus portabel, skrip yang sesuai dengan POSIX sejauh yang saya tahu; itu tidak menggunakan bashisme apa pun, yang sebenarnya penting karena /bin/shdi Ubuntu sebenarnya saat dashini, tidak bash.

Brian Campbell
sumber
ingat untuk mengatur + e dan gunakan '-eq' daripada '==' untuk perbandingan integer
senjata
Mengubahnya menjadi -eq; apa yang set + e beli di sini?
Brian Campbell
saya menemukan dua hal dalam jawaban saya yang mungkin juga ingin Anda perbaiki pada jawaban Anda: pertama SO hilighter menjadi gila karena $ # (memperlakukannya sebagai komentar). saya melakukan "$ #" untuk memperbaikinya. kedua, regex juga cocok dengan "foo123bar". saya memperbaikinya dengan melakukan ^ [0-9] + $. Anda juga dapat memperbaikinya dengan menggunakan opsi -x grep
Johannes Schaub - litb
1
@ojblass Saya melewatkan salah satu tes yang dia tanyakan. Menambahkan itu berarti juga menambahkan direktori untuk diuji, yang secara signifikan memperluas ukuran jawaban karena tidak dapat muat dalam satu baris. Dapatkah Anda menyarankan cara pengujian yang lebih ringkas untuk keberadaan setiap direktori?
Brian Campbell
1
Sesuai jawaban-komentar @Morten Nielsen di bawah ini, grep '$ [0-9] + ^' memang terlihat aneh. Bukankah seharusnya '^ [0-9] + $'?
martin jakubik
21

The shsolusi dengan Brian Campbell, sementara yang mulia dan baik dieksekusi, memiliki beberapa masalah, jadi saya pikir saya akan memberikan saya sendiri bashsolusi.

Masalah dengan yang shsatu:

  • Tilde in ~/footidak meluas ke direktori beranda Anda di dalam heredocs. Dan tidak juga ketika dibacakan oleh readpernyataan atau dikutip dalam rmpernyataan itu. Artinya Anda akan mendapatkan No such file or directoryerror.
  • Forking off grepdan semacamnya untuk operasi dasar adalah daft. Terutama bila Anda menggunakan cangkang jelek untuk menghindari beban bash yang "berat".
  • Saya juga memperhatikan beberapa masalah kutipan, misalnya di sekitar perluasan parameter di miliknya echo.
  • Meskipun jarang, solusinya tidak dapat menangani nama file yang berisi baris baru. (Hampir tidak ada solusi dalam yang shdapat mengatasinya - itulah sebabnya saya hampir selalu lebih suka bash, ini jauh lebih antipeluru & lebih sulit untuk dieksploitasi bila digunakan dengan baik).

Sementara, ya, menggunakan /bin/shuntuk hashbang Anda berarti Anda harus menghindari bashisme dengan cara apa pun, Anda dapat menggunakan semua bashisme yang Anda suka, bahkan di Ubuntu atau yang lainnya ketika Anda jujur ​​dan meletakkannya #!/bin/bashdi atas.

Jadi, inilah bashsolusi yang lebih kecil, lebih bersih, lebih transparan, mungkin "lebih cepat", dan lebih tahan peluru.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Perhatikan kutipan $1di sekitar rmpernyataan itu!
  2. The -dcek juga akan gagal jika $1kosong, jadi itu dua cek dalam satu.
  3. Saya menghindari ekspresi reguler karena suatu alasan. Jika Anda harus menggunakan =~dalam bash, Anda harus meletakkan ekspresi reguler dalam variabel. Bagaimanapun, gumpalan seperti milik saya selalu lebih disukai dan didukung di lebih banyak versi bash.
lhunath
sumber
1
Jadi, apakah $1 != *[^0-9]*pesta potongan globbing itu spesifik?
grinch
15

Saya akan menggunakan bash [[:

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

Saya menemukan faq ini sebagai sumber informasi yang baik.

Johannes Schaub - litb
sumber
13

Tidak antipeluru seperti jawaban di atas, namun tetap efektif:

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

# rm commands go here
Tagihan Boiler
sumber
11

Gunakan set -uyang akan menyebabkan referensi argumen yang tidak disetel segera gagal dalam skrip.

Silakan, lihat artikel: Menulis Skrip Shell Bash yang Kuat - David Pashley.com .

shmichael
sumber
Ini adalah perbaikan mudah yang hebat dalam banyak kasus dan berfungsi juga untuk fungsi. Terima kasih!
Manfred Moser
9

Halaman manual untuk test ( man test) menyediakan semua operator yang tersedia yang dapat Anda gunakan sebagai operator boolean di bash. Gunakan tanda tersebut di awal skrip (atau fungsi) Anda untuk validasi input seperti yang Anda lakukan dalam bahasa pemrograman lainnya. Sebagai contoh:

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

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
ikan paus
sumber
8

Gunakan '-z' untuk menguji string kosong dan '-d untuk memeriksa direktori.

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
senjata
sumber
2
mengapa Anda membutuhkan [[]] ganda?
kendaraan
5

Anda dapat memvalidasi poin a dan b secara kompak dengan melakukan sesuatu seperti berikut:

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

Yang memberi kita ...

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

$ ./testparams.sh 1234
1234

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

Metode ini akan bekerja dengan baik di sh.

MattK
sumber
2

validasi argumen Bash satu liner, dengan dan tanpa validasi direktori

Berikut beberapa metode yang berhasil untuk saya. Anda dapat menggunakannya di namespace skrip global (jika di namespace global, Anda tidak dapat mereferensikan variabel builtin fungsi)

cepat dan kotor satu liner

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

keluaran:

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

Fancier - nama fungsi suplai dan penggunaan

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

keluaran:

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

Tambahkan logika validasi kompleks tanpa mengacaukan fungsi Anda saat ini

Tambahkan baris berikut di dalam fungsi atau skrip yang menerima argumen.

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

Anda kemudian dapat membuat fungsi validasi yang melakukan sesuatu seperti

validate() {

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

dan fungsi die yang membatalkan skrip jika gagal

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

Untuk argumen tambahan, cukup tambahkan baris tambahan, yang mereplikasi format.

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

Posting lama tapi saya pikir saya bisa berkontribusi.

Sebuah skrip bisa dibilang tidak diperlukan dan dengan beberapa toleransi terhadap kartu liar dapat dilakukan dari baris perintah.

  1. liar di mana saja yang cocok. Mari kita hapus kemunculan sub "folder"

    $ rm -rf ~/*/folder/*
  2. Shell melakukan iterasi. Mari hapus folder pra dan posting tertentu dengan satu baris

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

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