Проверка параметров сценария Bash

96

Я придумал базовую, которая поможет автоматизировать процесс удаления ряда папок, когда они становятся ненужными.

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

Это вызывается так:

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

Проблема в том, что если вы забудете ввести id-number (как я только что сделал) , то это может потенциально удалить много вещей, которые вы действительно не хотите удалять.

Есть ли способ добавить любую форму проверки к параметрам командной строки? В моем случае было бы хорошо проверить, что: а) есть один параметр, б) он числовой и в) эта папка существует; прежде чем продолжить сценарий.

Nickf
источник

Ответы:

160
#!/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 : Я пропустил часть о проверке наличия каталогов вначале, поэтому я добавил это, завершив сценарий. Также рассмотрены вопросы, поднятые в комментариях; исправлено регулярное выражение, переключено с ==на eq.

Насколько я могу судить, это должен быть переносимый POSIX-совместимый сценарий; он не использует никаких башизмов, что на самом деле важно, потому что /bin/shв настоящее время в Ubuntu фактически dashнет bash.

Брайан Кэмпбелл
источник
не забудьте установить + e и использовать '-eq' вместо '==' для целочисленных сравнений
guns
Изменил его на -eq; что вам здесь дает set + e?
Брайан Кэмпбелл,
В своем ответе я обнаружил две вещи, которые вы, возможно, захотите исправить и в своем: во-первых, SO hilighter сходит с ума из-за $ # (рассматривая это как комментарий). я сделал "$ #", чтобы исправить это. во-вторых, регулярное выражение также соответствует «foo123bar». Я исправил это, выполнив ^ [0-9] + $. вы также можете исправить это, используя параметр grep -x
Johannes Schaub - litb
1
@ojblass Мне не хватало одного из тестов, о которых он спрашивал. Добавление этого означало также добавление его каталогов для тестирования, что значительно увеличило размер ответа, поскольку они не помещаются в одной строке. Можете ли вы предложить более компактный способ проверки существования каждого каталога?
Брайан Кэмпбелл,
1
согласно ответу-комментарию @Morten Nielsen ниже, grep '$ [0-9] + ^' действительно выглядит странно. Разве это не должно быть «^ [0-9] + $»?
Мартин Якубик 01
21

shРешение по Brian Campbell, в то время как благородный и хорошо выполнены, есть несколько проблем, поэтому я думал , что обеспечить свое собственное bashрешение.

Проблемы с shодним:

  • Тильда ~/fooне распространяется на ваш домашний каталог внутри heredocs. И ни когда это читается в readзаявлении или цитируется в rmзаявлении. Значит, вы получите No such file or directoryошибки.
  • Разветвление grepи тому подобное для основных операций - это глупо. Особенно, когда вы используете дрянную оболочку, чтобы избежать "тяжелого" веса bash.
  • Я также заметил несколько проблем с цитированием, например, вокруг расширения параметра в его echo.
  • Хотя это бывает редко, решение не справляется с именами файлов, содержащими символы новой строки. (Почти ни одно решение не shможет с ними справиться - вот почему я почти всегда предпочитаю bash, он гораздо более пуленепробиваемый и труднее использовать при правильном использовании).

Хотя да, использование /bin/shдля вашего хэшбэга означает, что вы должны избегать bashизмов любой ценой, вы можете использовать все bashизмы, которые вам нравятся, даже на Ubuntu или еще где-то еще, когда вы честны и ставите #!/bin/bashна первое место.

Итак, вот bashрешение, которое меньше, чище, прозрачнее, возможно, «быстрее» и более пуленепробиваемое.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Обратите внимание на цитаты $1в rmзаявлении!
  2. -dПроверка также потерпит неудачу , если $1пусто, так что две проверки в одном.
  3. Я не зря избегал регулярных выражений. Если вы должны использовать =~bash, вы должны поместить регулярное выражение в переменную. В любом случае глобусы вроде моей всегда предпочтительнее и поддерживаются в гораздо большем количестве версий bash.
лхунатх
источник
1
Так является ли $1 != *[^0-9]*тогда специфическим bash?
grinch
15

Я бы использовал bash [[:

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

Я нашел этот часто задаваемый вопрос хорошим источником информации.

Йоханнес Шауб - litb
источник
13

Не такой пуленепробиваемый, как приведенный выше ответ, но все же эффективный:

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

# rm commands go here
Счет за котел
источник
11

Использование, set -uкоторое приведет к тому, что любая неустановленная ссылка на аргумент немедленно приведет к сбою сценария.

См. Статью: Написание надежных сценариев оболочки Bash - David Pashley.com .

шмайкл
источник
Это отличное простое решение во многих случаях, которое также работает для функций. Благодарность!
Манфред Мозер
9

На странице руководства для test ( man test) представлены все доступные операторы, которые вы можете использовать как логические операторы в bash. Используйте эти флаги в начале вашего скрипта (или функций) для проверки ввода, как и на любом другом языке программирования. Например:

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

if [ -z $2 ] ; then
  echo "Second parameter needed!" && exit 2;
fi
кит
источник
8

Используйте '-z' для проверки наличия пустых строк и '-d' для проверки каталогов.

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
пушки
источник
2
зачем вам двойной [[]]?
vehomzzz
5

Вы можете компактно проверить точки a и b, выполнив следующие действия:

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

Что дает нам ...

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

$ ./testparams.sh 1234
1234

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

Этот метод должен нормально работать в sh.

MattK
источник
2

однострочная проверка аргумента Bash с проверкой каталога и без нее

Вот несколько методов, которые у меня сработали. Вы можете использовать их либо в глобальном пространстве имен скрипта (если в глобальном пространстве имен, вы не можете ссылаться на встроенные переменные функции)

быстрый и грязный лайнер

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

выход:

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

Fancier - имя и использование функции поставки

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

выход:

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

Добавьте сложную логику проверки, не загромождая вашу текущую функцию

Добавьте следующую строку в функцию или сценарий, который получает аргумент.

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

Затем вы можете создать функцию проверки, которая делает что-то вроде

validate() {

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

и функция die, которая прерывает скрипт при ошибке

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

Для дополнительных аргументов просто добавьте дополнительную строку, воспроизводящую формат.

: ${1?' You forgot to supply the first argument'}
: ${2?' You forgot to supply the second argument'}
AndrewD
источник
1

Старый пост, но я решил, что все равно могу внести свой вклад.

Сценарий, вероятно, не нужен и с некоторой терпимостью к подстановочным знакам может быть выполнен из командной строки.

  1. дикий где угодно совпадение. Давайте удалим все вхождения подпапки

    $ rm -rf ~/*/folder/*
    
  2. Оболочка повторилась. Удалим определенные папки pre и post одной строкой

    $ rm -rf ~/foo{1,2,3}/folder/{ab,cd,ef}
    
  3. Shell итерация + var (проверено BASH).

    $ var=bar rm -rf ~/foo{1,2,3}/${var}/{ab,cd,ef}
    
Xarses
источник