Table of Contents
bash Позиционные параметры
Во всех предыдущих программах отсутствовала одна особенность – возможность принимать и обрабатывать параметры и аргументы командной строки. В этой статье мы исследуем эту возможность и позволим нашим программам обращаться к содержимому командной строки.
Доступ к командной строке
Командная оболочка поддерживает множество переменных, которые называются позиционными параметрами и содержат отдельные слова из командной строки. Эти переменные имеют имена от 0 до 9. Продемонстрируем их:
#!/bin/bash # posit-param: сценарий для просмотра параметров командной строки echo " \$0 = $0 \$1 = $1 \$2 = $2 \$3 = $3 \$4 = $4 \$5 = $5 \$6 = $6 \$7 = $7 \$8 = $8 \$9 = $9 "
Этот очень простой сценарий выводит значения переменных с именами от $0 до $9. Запустим его без аргументов командной строки:
$0 = /home/nevvad/bin/posic_param $1 = $2 = $3 = $4 = $5 = $6 = $7 = $8 = $9 =
Даже в отсутствие аргументов переменная $0 всегда содержит первый элемент командной строки – путь к файлу выполняемой программы. Давайте передадим сценарию несколько аргументов:
posic_param a b c d $0 = /home/nevvad/bin/posic_param $1 = a $2 = b $3 = c $4 = d $5 = $6 = $7 = $8 = $9 =
<callout type=“primary” icon=“true” title=“ПРИМЕЧАНИЕ”>В действительности, если использовать механизм подстановки параметров, можно получить доступ более чем к девяти параметрам. Чтобы указать число больше девяти, следует заключить его в фигурные скобки; например, <html>${10}</html>, <html>${55}</html>, <html>${211}</html> и т. д.</callout>
Определение числа аргументов
Командная оболочка поддерживает также переменную <html>$#</html>, хранящую число аргументов командной строки
#!/bin/bash # posit-param: сценарий для просмотра параметров командной строки echo " Number of arguments: $# \$0 = $0 \$1 = $1 \$2 = $2 \$3 = $3 \$4 = $4 \$5 = $5 \$6 = $6 \$7 = $7 \$8 = $8 \$9 = $9 "
Результат:
posic_param a b c d Number of arguments: 4 $0 = /home/nevvad/bin/posic_param_5 $1 = a $2 = b $3 = c $4 = d $5 = $6 = $7 = $8 = $9 =
shift — доступ к множеству аргументов
Но как быть, если программе передается большое число аргументов, как в следующем примере:
posic_param * Number of arguments: 76 $0 = /home/nevvad/bin/posic_param_5 $1 = --absolute-names $2 = addr.list $3 = bin $4 = blackhole $5 = bootstrap-5.0.0-beta3 $6 = depends.txt $7 = destination $8 = dirlist-bin.txt $9 = dirlist-sbin.txt
В системе, где выполнялся этот пример, механизм подстановки развернул символ <html>*</html> в 76 аргументов. Как обработать такое количество? Командная оболочка предусматривает решение и для подобных случаев, правда, следует отметить, что изяществом оно не отличается. Команда shift выполняет «сдвиг» параметров к началу списка. Фактически, используя shift, можно обойтись единственной переменной-параметром (помимо $0, которая никогда не изменяется)
#!/bin/bash # posit-param2: сценарий вывода всех аргументов count=1 while [[ $# -gt 0 ]]; do echo "Argument $count = $1" count=$((count + 1)) shift done
Каждый раз, когда выполняется команда shift, значение $2 перемещается в $1, значение $3 перемещается в $2 и т. д. Значение $# при этом уменьшается на 1.
В программе posit-param2 мы создали цикл, проверяющий число оставшихся аргументов и продолжающийся до тех пор, пока оно не уменьшится до нуля. Цикл выводит текущий аргумент, в каждой итерации увеличивает счетчик обработанных аргументов count и, наконец, выполняет shift, чтобы загрузить в $1 следующий аргумент. Вот как работает эта программа:
posit-param2 a b c d Argument 1 = a Argument 2 = b Argument 3 = c Argument 4 = d
Простые приложения
Даже без команды shift можно писать полезные приложения, использующие позиционные параметры. Например, ниже приводится простая программа получения информации о файле
#!/bin/bash # file_info: простая программа получения информации о файле PROGNAME="$(basename "$0")" if [[ -e "$1" ]]; then echo -e "\nFile Type:" file "$1" echo -e "\nFile Status:" stat "$1" else echo "$PROGNAME: usage: $PROGNAME file" >&2 exit 1 fi
Эта программа выводит тип указанного файла (определяется с помощью команды file) и его атрибуты (командой stat). Интересной особенностью программы является переменная PROGNAME. Ей присваивается результат выполнения команды basename “$0”. Команда basename удаляет начальную часть из пути к файлу, оставляя только базовое имя. В данном примере basename удалит начальную часть из параметра $0, хранящего полный путь к данной программе. Такой результат удобно использовать для конструирования сообщений, например, о правилах использования программы. При подобном подходе можно переименовать сценарий, и при выводе сообщений новое имя программы будет использоваться автоматически.
Использование позиционных параметров в функциях
Позиционные параметры используются для передачи аргументов не только в сценарии, но и в функции командной оболочки. Для демонстрации преобразуем сценарий file_info в функцию
file_info () { # file_info: функция для вывода информации о файле if [[ -e "$1" ]]; then echo -e "\nFile Type:" file "$1" echo -e "\nFile Status:" stat "$1" else echo "$FUNCNAME: usage: $FUNCNAME file" >&2 return 1 fi }
Теперь, если сценарий, включающий функцию file_info, вызовет ее с именем файла в аргументе, аргумент будет передан в функцию.
Благодаря этому мы получаем возможность написать множество полезных функций для использования не только в наших сценариях, но и в файле <html>.bashrc</html>. Обратите внимание, что в этом примере вместо переменной PROGNAME используется переменная командной оболочки FUNCNAME. Оболочка автоматически присваивает значение этой переменной в момент вызова функции. Отметьте также, что $0 всегда содержит полный путь к первому элементу командной строки (то есть имя программы), а не имя функции, как можно было бы ожидать.
Обработка позиционных параметров скопом
Иногда бывает необходимо выполнить операцию сразу со всеми позиционными параметрами. Например, может понадобиться написать «обертку» для некоторой программы, то есть сценарий или функцию, упрощающие запуск этой программы. Обертка принимает список непонятных для нее параметров командной строки и просто передает его обертываемой программе.
Для этой цели командная оболочка предоставляет два специальных параметра. Они оба замещаются полным списком позиционных параметров, но имеют некоторые тонкие отличия. Описание этих параметров приводится в таблице ниже:
Параметр | Описание |
---|---|
<html>$*</html> | Замещается списком позиционных параметров, начиная с $1. Если имя параметра <html>$*</html> заключить в двойные кавычки, позиционные параметры будут перечислены в списке через первый символ в переменной IFS (по умолчанию пробел), а сам список будет размещен в одной строке и заключен в кавычки |
<html>$@</html> | Замещается списком позиционных параметров, начиная с $1. Если имя параметра <html>$@</html> заключить в двойные кавычки, механизм подстановки заменит его списком позиционных параметров, заключенных в кавычки по отдельности |
Следующий сценарий демонстрирует, как действуют эти специальные параметры:
#!/bin/bash # posit-params3 : сценарий для демонстрации $* и $@ print_params () { echo "\$1 = $1" echo "\$2 = $2" echo "\$3 = $3" echo "\$4 = $4" } pass_params () { echo -e "\n" '$* :'; print_params $* echo -e "\n" '"$*" :'; print_params "$*" echo -e "\n" '$@ :'; print_params $@ echo -e "\n" '"$@" :'; print_params "$@" } pass_params "word" "words with spaces"
В этой довольно замысловатой программе мы создали два аргумента, word и words with spaces, и передали их функции pass_params. Эта функция, в свою очередь, передает их функции print_params, с применением каждого из четырех методов, доступных для специальных параметров <html>$*</html> и <html>$@</html>. Вывод сценария показывает разницу между ними:
posit-param3 $* : $1 = word $2 = words $3 = with $4 = spaces "$*" : $1 = word words with spaces $2 = $3 = $4 = $@ : $1 = word $2 = words $3 = with $4 = spaces "$@" : $1 = word $2 = words with spaces $3 = $4 =
В данном примере оба параметра, <html>$*</html> и <html>$@</html>, возвращают результат из четырех слов:
word words with spaces
<html>“$*”</html> возвращает результат в виде одного слова, содержащего пробелы:
"word words with spaces"
<html>“$@”</html> возвращает результат в виде двух слов, второе из которых включает пробелы:
"word" "words with spaces"
Это соответствует нашим фактическим намерениям. Этот пример показывает, что, несмотря на наличие четырех разных способов получения списка позиционных параметров, в большинстве ситуаций предпочтительнее использовать прием с <html>“$@”</html>, потому что он сохраняет целостность каждого позиционного параметра.
Более сложное приложение
Продолжим работу над программой sys_info_page. Теперь мы добавим в нее поддержку нескольких параметров командной строки:
- Выходной файл. Мы добавим параметр, который позволит указать имя файла для вывода результатов работы программы. Сделать это можно будет с помощью <html>-f файл</html> или <html>–file файл</html>.
- Интерактивный режим. При передаче этого параметра программа будет предлагать пользователю ввести имя выходного файла и определять, существует ли этот файл. Если файл существует, пользователю будет предложено подтвердить свое решение, прежде чем затереть существующий файл. Этот параметр можно будет передать как <html>-i</html> или <html>–interactive</html>.
- Справка. Передав параметр <html>-h</html> или <html>–help</html>, можно потребовать от программы вывести сообщение с информацией о правилах пользования программой.
Далее приводится код, реализующий обработку командной строки:
usage () { echo "$PROGNAME: usage: $PROGNAME [-f file | -i]" return } # обработка параметров командной строки interactive= filename= while [[ -n "$1" ]]; do case "$1" in -f | --file) shift filename="$1" ;; -i | --interactive) interactive=1 ;; -h | --help) usage exit ;; *) usage >&2 exit 1 ;; esac shift done
Сначала мы добавили функцию usage для вывода сообщения, если программа вызывается с параметром <html>–help</html> или с неизвестным параметром.
Затем следует цикл обработки параметров. Цикл продолжается, пока позиционный параметр $1 не получит пустое значение. В конце цикла вызывается команда shift, чтобы сдвинуть позиционные параметры и, в конечном итоге, гарантировать завершение цикла.
Внутри цикла инструкция case проверяет текущий позиционный параметр на соответствие поддерживаемым вариантам. Если данный параметр поддерживается, выполняется соответствующая операция, если нет – выводится сообщение с информацией о правилах пользования программой и сценарий завершается с признаком ошибки.
Обратите внимание, как обрабатывается параметр <html>-f</html>. Обнаружив этот параметр, программа выполняет команду shift, которая сдвинет аргумент параметра <html>-f</html> с именем файла в позиционный параметр $1.
Далее следует код, реализующий интерактивный режим:
# интерактивный режим if [[ -n "$interactive" ]]; then while true; do read -p "Enter name of output file: " filename if [[ -e "$filename" ]]; then read -p "'$filename' exists. Overwrite? [y/n/q] > " case "$REPLY" in Y|y) break ;; Q|q) echo "Program terminated." exit ;; *) continue ;; esac elif [[ -z "$filename" ]]; then continue else break fi done fi
Если переменная interactive содержит непустое значение, начинается бесконечный цикл, который предлагает ввести имя файла и затем обрабатывает ситуацию, если введенное имя соответствует существующему файлу. Если указанный файл уже существует, пользователю на выбор предлагается три варианта: затереть существующий файл, выбрать другое имя или завершить программу. Если пользователь предпочтет затереть существующий файл, выполняется команда break и цикл прерывается. Обратите внимание, что инструкция case различает только вариант перезаписи существующего файла и завершения программы. Любой другой ответ пользователя будет приводить к переходу в начало цикла с повторным предложением ввести имя файла.
Для поддержки вывода в файл сначала необходимо имеющийся код вывода страницы преобразовать в функцию. Необходимость такого решения станет понятна чуть позже:
write_html_page () { cat <<- _EOF_ <html> <head> <title>$TITLE</title> </head> <body> <h1>$TITLE</h1> <p>$TIMESTAMP</p> $(report_uptime) $(report_disk_space) $(report_home_space) </body> </html> _EOF_ return } # вывод страницы html if [[ -n "$filename" ]]; then if touch "$filename" && [[ -f "$filename" ]]; then write_html_page > "$filename" else echo "$PROGNAME: Cannot write file '$filename'" >&2 exit 1 fi else write_html_page fi
Код, обслуживающий логику параметра <html>-f</html>, находится в конце листинга, приведенного выше. Он проверяет, определено ли имя файла и затем – доступность для записи файла с указанным именем. Для этого выполняется команда touch с последующей проверкой, что файл является обычным файлом. Эти две проверки позволяют обработать ситуацию неправильно указанного пути (в этом случае touch потерпит неудачу) и убедиться, что существующий файл является обычным файлом.
Как видите, функция write_html_page вызывается, чтобы сгенерировать фактическое содержимое страницы, которое затем либо выводится в стандартный вывод (если переменная filename содержит пустое значение), либо перенаправляется в указанный файл. Поскольку код HTML может выводиться в файл или в стандартный вывод, есть смысл преобразовать процедуру write_html_page в функцию, чтобы избежать повторения кода.
Полный листинг программы
Программа sys_info_page выросла и усложнилась. Ниже приводится полный листинг программы с выделенными последними изменениями:
#!/bin/bash # sys_info_page: программа вывода страницы с информацией о системе PROGNAME="$(basename $0)" TITLE="System Information Report For $HOSTNAME" CURRENT_TIME=$(date +"%x %r %Z") TIME_STAMP="Generated $CURRENT_TIME, by $USER" report_uptime () { cat <<- _EOF_ <h2>SYSTEM UPTIME</h2> <pre>$(uptime)</pre> _EOF_ return } report_disk_space () { cat <<- _EOF_ <h2>DISK SPASE UTILIZATION</h2> <pre>$(df -h)</pre> _EOF_ return } report_home_space () { if [[ $(id -u) -eq 0 ]]; then cat <<- _EOF_ <h2>HOME SPACE UTILIZATION</h2> <pre>$(du -sh /home/*)</pre> _EOF_ else cat <<- _EOF_ <h2>HOME SPACE UTILIZATION</h2> <pre>$(du -sh $HOME)</pre> _EOF_ fi return } # begin new string usage () { echo "$PROGNAME: usage: $PROGNAME [-f file | -i]" return } write_html_page () { cat <<- _EOF_ <html> <head> <title>$TITLE</title> </head> <body> <h1>$TITLE</h1> <p>$TIMESTAMP</p> $(report_uptime) $(report_disk_space) $(report_home_space) </body> </html> _EOF_ return } # обработка параметров командной строки interactive= filename= while [[ -n "$1" ]]; do case "$1" in -f | --file) shift filename="$1" ;; -i | --interactive) interactive=1 ;; -h | --help) usage exit ;; *) usage >&2 exit 1 esac done # интерактивный режим if [[ -n "$interactive" ]]; then while true; do read -p "Enter name of output file: " filename if [[ -e "$filename" ]]; then read -p "'$filename' exists. Overwrite? [y/n/q] > " case "$REPLY" in Y|y) break ;; Q|q) echo "Program terminated" exit ;; *) continue ;; esac fi done fi # вывод страницы html if [[ -n "$filename" ]]; then if touch "$filename" && [[ -f "$filename" ]]; then write_html_page > "$filename" else echo "$PROGNAME: Cannote write file '$filename'" >&2 exit 1 fi else write_html_page fi
<html></html>