Перевод статьи «Handling Arguments in Bash Scripts».
Создание Bash-скриптов для автоматизации набора команд — первый шаг на пути к созданию инструментов, облегчающих вашу жизнь. Даже простые скрипты, читающиеся сверху вниз и запускающиеся по установленному графику, способны сэкономить вам массу времени. Но рано или поздно наступит момент, когда вы захотите настраивать поведение вашего скрипта на лету: создавать директории с нужными вам именами, загружать файлы из определенных git-репозиториев, указывать IP-адреса или порты и т. п. Вот здесь вам и пригодятся аргументы скриптов.
Позиционные и специальные параметры
Bash предоставляет нам переменные, которые присутствуют в любом написанном нами скрипте. Вот несколько самых полезных:
$1, $2, $3, …: позиционные параметры
Позиционные параметры содержат значения, которые вы указываете при запуске своего скрипта (в командной строке) в качестве аргументов. Рассмотрим пример. Допустим, у вас есть скрипт, запускающийся следующим образом:
$ ./my_script 200 goats
Переменная $1
будет содержать значение «200», а переменная $2
— значение «goats».
Я использую позиционные параметры в одном из своих простейших скриптов. Этот скрипт я запускаю на работе практически ежедневно (здесь показываю упрощенный вариант):
!/usr/bin/env bash project_name="$1" mkdir -p "${project_name}/{CAD,drawings,mold,resources}" echo "New project '$project_name' created!"
Как видите, я беру позиционную переменную $1
и сохраняю ее значение в настоящей именованной переменной. Но делать так не обязательно. Я мог бы написать
mkdir -p "$1/{CAD,drawings,mold,resources}"
и все равно все бы прекрасно работало.
Тем не менее, я предпочитаю сохранять позиционные параметры в именованные переменные вверху своего скрипта. Таким образом любой читатель моего скрипта сможет быстро понять, что к чему. Разумеется, это не заменяет хорошую документацию и надежную обработку ошибок, но это приятный маленький бонус в плане читаемости. Подобные вещи хоть немножко, но помогают, так что это хорошая практика.
Когда я запускаю скрипт вот так:
$ new_project "catheter-01"
он генерирует структуру директорий:
catheter-01 |- CAD |- drawings |- mold |- resources
$0: имя скрипта
В позиционной переменной $0
при вызове скрипта сохраняется его имя. Это особенно полезно для вывода сообщений о том, как нужно использовать этот скрипт.
#!/usr/bin/env bash function usage() { echo "Usage: $0 <name> [options]" } # Error handling omitted (for now) if [[ "$1" == -h ]]; then usage exit 0 fi name="$1" echo "Hello, ${name}!"
Вот что получится при запуске:
$ ./greeting -h Usage: ./greeting [options] $ bash greeting -h Usage: greeting [options] $ ./greeting "Ryan" Hello, Ryan!
$#: число аргументов
Кроме позиционных параметров в Bash есть еще и специальные. Переменная $#
хранит количество аргументов, переданных через командную строку. Это очень пригождается в обработке ошибок. Что произойдет, если наш скрипт не получит нужных ему аргументов? Давайте обновим скрипт из предыдущего примера и добавим обработку ошибок.
#!/usr/bin/env bash function usage() { echo "Usage: $0 <name> [options]" } ### Hot new error handling! # We expect one argument. Otherwise tell the user how to # call your script. if [[ "$#" -ne 1 ]]; then usage exit 1 fi if [[ "$1" == -h ]]; then usage exit 0 fi name="$1" echo "Hello, ${name}!"
$?: последний код возврата
Лично я нечасто пользуюсь этим специальным параметром в скриптах, зато интенсивно использую его в командной строке. Многие команды, когда их выполнение проваливается, не выводят никаких сообщений. Они просто ничего не делают. Как же вам узнать, успешно ли отработала команда? Можно вывести значение переменной $?
, в которой сохраняется код возврата последней запускавшейся команды.
$ ls test.txt code strudel.py $ echo $? 0 $ ls lamedir ls: cannot access 'lamedir': No such file or directory $ echo $? 2
Вот пример использования в скрипте:
#!/usr/bin/env bash dirname="$1" mkdir "$dirname" # This will fail if the directory exists already if [[ "$?" -ne 0 ]]; then # If the directory is already created, that's fine # just print out a message to alert the user echo "Directory '$dirname' already exists. Not making a new one." fi
$@ and $*: все аргументы
Кажется, именно эти переменные вызывают больше всего сложностей у новичков в Bash — и это понятно! Они работают практически одинаково, но разница в их действии может быть очень существенной в каждой отдельной ситуации.
Если вы НЕ берете эти переменные в кавычки, они делают одно и то же: вставляют в указанное место все переданные в скрипт аргументы.
#!/usr/bin/env bash echo "====================" echo "This is dollar star." echo "====================" for arg in $*; do echo "$arg" done echo "====================" echo "This is dollar at." echo "====================" for arg in $@; do echo "$arg" done
При запуске получим следующее:
$ ./arg_printer abba dabba "dooby doo" ==================== This is dollar star. ==================== abba dabba dooby doo ==================== This is dollar at. ==================== abba dabba dooby doo
Обратите внимание на аргумент «dooby doo»
. Он был взят в кавычки при передаче, но в результате разбился по пробелу на два разных аргумента. Порой это именно то, что нужно, но очень часто — нет.
Переходим к самому интересному: возьмем переменные в кавычки.
Если взять в кавычки $*
, в выводе вы получите все аргументы в одной строке. Аргументы будут разделены пробелами и фактически станут одним аргументом. Причем это не зависит от того, были ли они заключены в кавычки при вводе.
Примечание. На самом деле аргументы разделяются $IFS («внутренним разделителем полей»). Обычно это пробел, но стоит знать, что так бывает не всегда.
#!/usr/bin/env bash echo "====================" echo "This is quoted dollar star." echo "====================" for arg in "$*"; do echo "$arg" done
Запуск:
$ ./arg_printer abba dabba "dooby doo" ==================== This is quoted dollar star. ==================== abba dabba dooby doo
Видите? Один аргумент! Хотите самостоятельно реализовать echo?
#!/usr/bin/env bash printf '%s\n' "$*"
$ ./my_echo hello my name is Ryan hello my name is Ryan
Ловко, да?
А вот когда вы берете в кавычки $@
, Bash выводит все аргументы так, как они были переданы изначально. Это, на мой взгляд, самый полезный функционал, потому что позволяет передавать все аргументы подкомандам, сохраняя при этом пробелы и кавычки и не позволяя автоматическому разделению строк Bash все испортить.
#!/usr/bin/env bash echo "====================" echo "This is quoted dollar at." echo "====================" for arg in "$@"; do echo "$arg" done
$ ./arg_printer abba dabba "dooby doo" ==================== This is quoted dollar at. ==================== abba dabba dooby doo
Вы часто увидите это в скриптах, содержащих множество функций. Традиционно, если у вас много функций, вы делаете последнюю функцию в скрипте функцией main
. Она будет обрабатывать все аргументы и содержать организационную логику скрипта. А для запуска функции main обычно последней строчкой скрипта идет main "$@"
. Вот так:
#!/usr/bin/env bash function usage() { echo "Usage: $0 <first> <second> [options]" } function bigger() { local first="$1" local second="$2" if [[ "$first" -gt "$second" ]]; then echo "$first" else echo "$second" fi } function main() { if [[ "$#" -ne 2 ]]; then usage exit 1 fi local first="$1" local second="$2" bigger "$first" "$second" } main "$@"
Итоги
Надеюсь, теперь вы начинаете понимать силу кастомизации. Используя скрипты Bash, вы можете автоматизировать выполнение многих задач. А благодаря возможности передачи аргументов при вызове скрипта вы можете автоматически выполнять даже те задачи, логика которых зависит от ситуации!
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]