Полезные советы Linux: Параметры bash и расширения параметров

Передача и анализ параметров в скриптах

Вы иногда задаетесь вопросом, как использовать в скриптах параметры и как передать их внутренним функциям или другим скриптам? Вам необходимо создать простые тесты для проверки корректности параметров или опций или выполнить простое извлечение и замену выражений в строках параметров. Эти полезные советы помогут вам в использовании параметров и различных расширений параметров, доступных в bash shell.

Ян (Ian) Шилдс (Shields), Senior Programmer, EMC

Ян Шилдс (Ian Shields) работает над множеством Linux-проектов для Linux-раздела developerWorks. Он является Старшим программистом в IBM Research Triangle Park, NC. Начав работать в IBM в Канберре, Австралия, в качестве Системного Инженера в 1973, в настоящее время он занимается системами связи и распределенными вычислениями в Монреале, Канада и RTP, NC. Он имеет несколько патентов и несколько опубликованных работ. Он закончил Австралийский Национальный Университет (Australian National University) по курсу чистой математики и философии. Он имеет степень магистра и доктора в области компьютерных наук, которую получил в Университете Штата Северная Каролина (North Carolina State University). Вы можете связаться с Яном по адресу ishields@us.ibm.com.


developerWorks Contributing author
        level

17.09.2007

В настоящее время командная оболочка bash (bash shell) входит во многие системы Linux® и UNIX® и является наиболее распространенной оболочкой в Linux. Из этих советов вы узнаете, как управлять параметрами и опциями в скриптах bash и как использовать механизмы расширения параметров shell для проверки или изменения параметров. Эта статья сфокусирована на bash, и все примеры выполнялись на системе Linux с командной оболочкой bash. Однако такие же расширения доступны во многих других оболочках, таких как ksh, ash или dash, и вы можете использовать их в этих оболочках на других системах UNIX или даже в других средах, например, в Cygwin. Эти советы базируются на инструментах, о которых говорилось в предыдущих советах Полезные советы Linux: Test и функции сравнения в bash (Linux tip: Bash test and comparison functions). Некоторые части текста этой статьи являются цитатами из учебного пособия от developerWorks Подготовка к экзамену LPI 102: Командные оболочки, написание скриптов, программирование и компиляция (LPI exam 102 prep: Shells, scripting, programming, and compiling), охватывающего многие основные методы создания скриптов.

Передача параметров

Одно из достоинств функций и скриптов shell состоит в возможности изменить поведение единственной функции или скрипта путем передачи этой функции или скрипту параметров. Изучив этот раздел, вы научитесь распознавать и использовать передаваемые параметры.

Внутри функции или скрипта вы можете передать параметры, используя специальные переменные bash из Таблицы 1. Вы ставите перед ними символ $, чтобы ссылаться на них, как на другие переменные shell.

Таблица 1. Параметры shell для функций
ПараметрНазначение
0, 1, 2, ...Позиционные параметры, начинающиеся с параметра 0. Параметр 0 обращается к имени программы, которая запускает bash, или к имени скрипта shell, если функция запущена внутри скрипта shell. Информацию о других возможностях bash, например, о том, когда bash стартует с параметром -c, вы узнаете из руководства man. Строка, заключенная в одинарные или двойные кавычки, будет передана как одиночный параметр и кавычки будут убраны. В случае двойных кавычек произойдет подстановка всех переменных shell, например, $HOME, прежде чем функция будет вызвана. Вы должны использовать одинарные или двойные кавычки, чтобы передать параметры, содержащие пробелы или символы, которые могут иметь в shell специальное значение.
*Позиционные параметры, начинающиеся с параметра 1. Если подстановка сделана внутри двойных кавычек, подставляется одно слово с первым знаком разделителя полей из специальной переменной IFS (interfield separator), разделяющим параметры, или без разделительного пробела, если IFS отсутствует. По умолчанию значение IFS -- пробел, табуляция и перевод строки. Если IFS не определен, разделитель использует пробел, поскольку он является умолчанием для IFS.
@Позиционные параметры, начинающиеся с параметра 1. Если подстановка сделана внутри двойных кавычек, каждый параметр начинается с одного слова, так что "$@" эквивалентно "$1" "$2" .... Если есть вероятность, что ваши параметры будут содержать внутри себя пробелы, вы захотите использовать эту форму.
#Число параметров, за исключением параметра 0.

Обратите внимание: Если вы используете более 9 параметров, вы не можете использовать $10 для обращения к десятому параметру. Вы должны сначала или обработать или сохранить первый параметр ($1), а затем использовать команду shift, чтобы зафиксировать параметр 1 и переместить все остальные параметры под 1, так что $10 станет $9 и так далее. Значение $# будет обновлено, чтобы показать остающееся количество параметров. На практике вы чаще всего захотите осуществлять процесс итерации по параметрам функции или скрипта shell, списка, созданного подстановкой команд при помощи выражения for, поэтому это ограничение редко создает проблему.

Теперь вы можете определить простую функцию, чтобы сказать, сколько параметров она содержит, и отобразить их, как показано в Листинге 1.

Листинг 1. Параметры функции
[ian@pinguino ~]$ testfunc () { echo "$# parameters"; echo "$@"; }
[ian@pinguino ~]$ testfunc
0 parameters

[ian@pinguino ~]$ testfunc a b c
3 parameters
a b c
[ian@pinguino ~]$ testfunc a "b c"
2 parameters
a b c

Скрипты shell обращаются с параметрами таким же образом, как и функции. Действительно, вы часто будете находить себя в сборке скриптов для решения множества небольших задач. В Листинге 2 показан скрипт shell, testfunc.sh, для той же простой задачи и результат его выполнения с одним из приведенных выше вводов. Не забудьте сделать ваш скрипт исполняемым, воспользовавшись командой chmod +x.

Листинг 2. Параметры скрипта shell
[ian@pinguino ~]$ cat testfunc.sh
#!/bin/bash
echo "$# parameters"
echo "$@";
[ian@pinguino ~]$ ./testfunc.sh a "b c"
2 parameters
a b c

Из Таблицы 1 вы узнали, что shell может ссылаться на список передаваемых параметров типа $* или $@ и что от того, заключены эти выражения в кавычки или нет, зависит, как они интерпретируются. Используете ли вы $*, "$*", $@ или "$@", вы не увидите больших различий в выводе приведенной выше функции, но будьте уверены, что в более сложном варианте различия будут значительны, поскольку вы хотите проанализировать параметры или, возможно, передать какие-то из них другой функции или скрипту. В Листинге 3 показана функция, которая выводит количество параметров и затем параметры, соответствующие этим четырем вариантам. В Листинге 4 показана функция в действии. По умолчанию в качестве первого символа для переменной IFS используется пробел, поэтому в Листинге 4 в качестве первого символа переменной IFS добавлена вертикальная черта, чтобы более явно показать, где используется этот символ для расширения "$*".

Листинг 3. Функция для исследования различий в обработке параметра
[ian@pinguino ~]$ type testfunc2
# testfunc2 -- это функция
testfunc2 ()
{
    echo "$# parameters";
    echo Using '$*';
    for p in $*;
    do
        echo "[$p]";
    done;
    echo Using '"$*"';
    for p in "$*";
    do
        echo "[$p]";
    done;
    echo Using '$@';
    for p in $@;
    do
        echo "[$p]";
    done;
    echo Using '"$@"';
    for p in "$@";
    do
        echo "[$p]";
    done
}
Листинг 4. Вывод информации о параметрах с помощью testfunc2
[ian@pinguino ~]$ IFS="|${IFS}" testfunc2 abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]

Внимательно исследуйте различия, особенно вид используемых кавычек и параметры, которые содержат промежутки -- пробелы или символы начала новой строки. Заметьте, что начиная с одиночной пары символов [] расширение "$*" -- на самом деле одно слово.


Опции и getopts

Традиционные команды UNIX и Linux предполагают, что некоторые из переданных аргументов будут опциями. Исторически сложилось так, что это были переключатели в виде одиночных символов, отличающиеся от других параметров стоящей перед ними черточкой или знаком минус. Для удобства некоторые опции могут быть скомбинированы, как в команде ls -lrt, которая выдает подробный (опция -l, от английского long) листинг содержимого каталога, расположенного в обратном порядке (опция -r, от английского reverse), отсортированного по времени изменения (опция -t, от английского time).

Вы можете использовать те же методы в скриптах shell, и встроенная команда getopts облегчает вашу задачу. Чтобы увидеть, как она работает, рассмотрим пример скрипта, testopt.sh, показанный в Листинге 5.

Листинг 5. Скрипт testopt.sht
#!/bin/bash
echo "OPTIND starts at $OPTIND"
while getopts ":pq:" optname
  do
    case "$optname" in
      "p")
        echo "Option $optname is specified"
        ;;
      "q")
        echo "Option $optname has value $OPTARG"
        ;;
      "?")
        echo "Unknown option $OPTARG"
        ;;
      ":")
        echo "No argument value for option $OPTARG"
        ;;
      *)
      # Соответствий не найдено
        echo "Unknown error while processing options"
        ;;
    esac
    echo "OPTIND is now $OPTIND"
  done

Команда getopts использует две предустановленные переменные. Переменная OPTIND изначально установлена в 1. Затем она содержит индекс следующего подлежащего обработке параметра. Команда getopts возвращает true, если опция найдена, поэтому обычная парадигма обработки опций использует в этом примере цикл while с выражением case. Первый аргумент, переданный команде getopts, -- список букв, обозначающих опции, которые будут распознаваться, в данном случае p и r. Двоеточие (:) после буквы, обозначающей опцию, показывает, что опция требует значения; например, опция -f может использоваться для указания имени файла, как в команде tar. В этом примере первое двоеточие дает указание команде getopts быть молчаливой и не показывать обычные сообщения об ошибках, поскольку этот скрипт обеспечит его собственную обработку ошибок.

Второй параметр, в этом примере optname, -- имя переменной, которая получит имя найденной опции. Если ожидается, что опция будет иметь значение, это значение, если оно есть, будет помещено в переменной OPTARG. В молчаливом режиме ошибка может происходить в двух следующих случаях.

  1. Если найдена неопознанная опция, optname будет содержать ? и OPTARG будет содержать эту неизвестную опцию.
  2. Если найдена опция, которая требует значения, но значения нет, optname будет содержать : и OPTARG будет содержать имя опции, чей аргумент отсутствует.

В режиме отображения всей информации эти ошибки порождают диагностические сообщения об ошибке и OPTARG отменяется. Скрипт может использовать значения ? или : в optname для выявления и возможной обработки ошибки.

В Листинге 6 показаны два примера выполнения этого простого скрипта.

Листинг 6. Выполнение скрипта testopt.sh
[ian@pinguino ~]$ ./testopt.sh -p -q
OPTIND starts at 1
Option p is specified
OPTIND is now 2
No argument value for option q
OPTIND is now 3
[ian@pinguino ~]$ ./testopt.sh -p -q -r -s tuv
OPTIND starts at 1
Option p is specified
OPTIND is now 2
Option q has value -r
OPTIND is now 4
Unknown option s
OPTIND is now 5

Если необходимо, вы можете передать набор аргументов команде getopts для обработки. Вы должны будете самостоятельно вновь установить OPTIND в 1, если вы вызываете getopts для нового набора аргументов в том скрипте, в котором вы уже использовали его с другими аргументами. Дополнительные подробности вы найдете в страницах man или info для bash.


Расширения параметра

Итак, вы увидели, как параметры передаются функции или скрипту и как распознавать опции. Теперь начнем обработку опций и параметров. Неплохо было бы знать, что оставляют аргументы после обработки опций. Тогда, возможно, вы должны будете подтвердить значения параметров или присвоить значение по умолчанию для недостающих параметров. Этот раздел вводит некоторые значения параметров, которые доступны в bash. Конечно, в вашем распоряжении есть также мощные команды Linux или UNIX, такие как sed или awk, для решения более сложных задач, но вам также следует знать, как используются расширения shell.

Давайте начнем создание скрипта с анализа опций и параметров функций, которые вы видели раньше. Наш скрипт testargs.sh показан в Листинге 7.

Листинг 7. Скрипт testargs.sh
#!/bin/bash

showopts () {
  while getopts ":pq:" optname
    do
      case "$optname" in
        "p")
          echo "Option $optname is specified"
          ;;
        "q")
          echo "Option $optname has value $OPTARG"
          ;;
        "?")
          echo "Unknown option $OPTARG"
          ;;
        ":")
          echo "No argument value for option $OPTARG"
          ;;
        *)
        # Соответствий не найдено
          echo "Unknown error while processing options"
          ;;
      esac
    done
  return $OPTIND
}

showargs () {
  for p in "$@"
    do
      echo "[$p]"
    done
}

optinfo=$(showopts "$@")
argstart=$?
arginfo=$(showargs "${@:$argstart}")
echo "Arguments are:"
echo "$arginfo"
echo "Options are:"
echo "$optinfo"

Попробуйте запустить этот скрипт несколько раз, чтобы понять, как он работает, затем мы исследуем его более подробно. В Листинге 8 показано несколько примеров вывода.

Листинг 8. Выполнение скрипта testargs.sh
[ian@pinguino ~]$ ./testargs.sh -p -q qoptval abc "def ghi"
Arguments are:
[abc]
[def ghi]
Options are:
Option p is specified
Option q has value qoptval
[ian@pinguino ~]$ ./testargs.sh -q qoptval -p -r abc "def ghi"
Arguments are:
[abc]
[def ghi]
Options are:
Option q has value qoptval
Option p is specified
Unknown option r
[ian@pinguino ~]$ ./testargs.sh "def ghi"
Arguments are:
[def ghi]
Options are:

Заметьте, что аргументы отделены от опций. Функция showopts анализирует опции, как и раньше, но использует выражение return для возврата значения переменной OPTIND вызывающему выражению. Процесс вызова присваивает это значение переменной argstart. Это значение затем используется для выбора подмножества исходных параметров, состоящего из тех параметров, которые не были обработаны как опции. Это сделано при помощи расширения
${@:$argstart}
Не забудьте заключить это выражение в кавычки, чтобы сохранить параметры вместе с входящими в них пробелами, как вы видели это ранее в Листинге 2.

Если вы плохо знакомы со скриптами и функциями, обратите внимание на следующие моменты:

  1. Выражение return возвращает выходное значение функции showopts, которая доступна из того места, откуда ее вызвали, при помощи $?. Вы также можете использовать возвращаемое функцией значение с такими командами как test или while для управления ветвлением и циклами.
  2. Функции bash могут содержать необязательное слово "function", например:
    function showopts ()
    Использование этого слова не является частью стандарта POSIX и не поддерживается такими shell'ами как dash, поэтому если вы его используете, не указывайте в качестве shebang-строки
    #!/bin/sh
    так как это может привести к тому, что ваш системный shell не будет работать так, как вы ожидаете.
  3. Вывод функции, например, вывод, созданный выражениями echo в этих двух функциях, не напечатан, но будет доступен из того места, откуда он был вызваны. Если этот вывод не присвоен переменной или не предусмотрено другого использования в качестве части вызывающего выражения, shell попытается выполнить это, вместо того чтобы показать.

Подмножества и части строк

Обычно расширение имеет такую форму: ${PARAMETER:OFFSET:LENGTH}, где аргумент LENGTH необязателен. Итак, если вы хотите выбрать только определенное подмножество аргументов скрипта, вы можете использовать полную версию, чтобы показать, сколько аргументов следует выбрать. Например, ${@:4:3} обращается к трем аргументам, начиная с аргумента 4, а именно, к аргументам 4, 5 и 6. Вы можете использовать это расширение для выбора конкретных параметров помимо тех, которые доступны сразу, используя от $1 до $9 включительно. ${@:15:1} -- способ вызова сразу 15 параметра.

Вы можете использовать расширение с конкретными параметрами, а также весь набор параметров, представленный при помощи $* или $@. В этом случае параметр обрабатывается как строка и число, представляющее собой сдвиг или длину. Например, если переменная x имеет значение "some value", то
${x:3:5}
будет иметь значение "e val", как показано в Листинге 9.

Листинг 9. Подстроки значений параметров в shell
[ian@pinguino ~]$ x="some value"
[ian@pinguino ~]$ echo "${x:3:5}"
e val

Размеры

Вы уже видели, что $# указывает число параметров и что расширение ${PARAMETER:OFFSET:LENGTH} применяется и к конкретным параметрам, и к $* и $@, поэтому вас не должно удивить, что аналогичная конструкция, ${#PARAMETER}, может использоваться для определения размера конкретного параметра. Простая функция testlength, показанная в Листинге 10, иллюстрирует это. Попробуйте сделать это сами.

Листинг 10. Параметр lengths
[ian@pinguino ~]$ testlength () { for p in "$@"; do echo ${#p};done }
[ian@pinguino ~]$ testlength 1 abc "def ghi"
1
3
7

Работа с шаблонами

Расширение параметра также включает возможности поиска по шаблону при помощи тех же знаков подстановки, которые используются в расширениях имен файлов или подстановках. Обратите внимание, что это не соответствует регулярным выражениям, используемым grep.

Таблица 2. Расширение шаблонов в shell
РасширениеНазначение
${ПАРАМЕТР#СЛОВО}Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с начала расширенного значения ПАРАМЕТРА. Использование '@' или '$' приводит к удалению по образцу для каждого параметра в списке.
${ПАРАМЕТР##СЛОВО}Приводит к удалению самого длинного соответствия шаблону с начала вместо самого короткого.
${ПАРАМЕТР%СЛОВО}Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с конца расширенного значения ПАРАМЕТРА. Использование '@' или '$' приводит к удалению по образцу для каждого параметра в списке.
${ПАРАМЕТР%%СЛОВО}Приводит к удалению самого длинного соответствия шаблону с конца вместо самого короткого.
${ПАРАМЕТР/ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ}Shell расширяет ОБРАЗЕЦ как расширение имени файла и заменяет самое длинное соответствие шаблону, если оно имеется, расширенным значением ПАРАМЕТРА. Для соответствия образцам в начале расширенного значения ПАРАМЕТРА поставьте в начале ОБРАЗЦА # или %, если соответствие должно проверяться до конца. Если ПОСЛЕДОВАТЕЛЬНОСТЬ пуста, перемещение / может быть опущено и соответствия удаляются. Использование '@' или '$' приводит к замене образца для каждого параметра в списке.
${ПАРАМЕТР//ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ}Выполняет замену для всех подходящих, а не только для первого.

В Листинге 11 показаны некоторые основные приемы использования расширений.

Листинг 11. Примеры шаблонов
[ian@pinguino ~]$ x="a1 b1 c2 d2"
[ian@pinguino ~]$ echo ${x#*1}
b1 c2 d2
[ian@pinguino ~]$ echo ${x##*1}
c2 d2
[ian@pinguino ~]$ echo ${x%1*}
a1 b
[ian@pinguino ~]$ echo ${x%%1*}
a
[ian@pinguino ~]$ echo ${x/1/3}
a3 b1 c2 d2
[ian@pinguino ~]$ echo ${x//1/3}
a3 b3 c2 d2
[ian@pinguino ~]$ echo ${x//?1/z3}
z3 z3 c2 d2

Сложим все вместе

Прежде чем мы приступим к описанию нескольких оставшихся моментов, давайте посмотрим на реальный пример обработки параметра. Я создал авторский пакет developerWorks (см. раздел Ресурсы) в системе Linux при помощи скрипта bash. Мы храним различные необходимые мне файлы в подкаталогах библиотеки с названием developerworks/library. Самая последняя версия на момент написания статьи -- 5.7, так что файлы schema хранятся в developerworks/library/schema/5.7, файлы XSL -- в developerworks/library/xsl/5.7 и простые шаблоны -- в developerworks/library/schema/5.7/templates. Очевидно, единственного параметра, отображающего номер версии, в данном случае 5.7, было бы достаточно для скрипта, чтобы создать пути для всех этих файлов. Так что скрипт берет параметр -v, который должен иметь значение. Проверка правильности этого параметра выполняется позже путем построения пути и затем проверки, что он существует, при помощи [ -d "$pathname" ].

Это прекрасно работает при создании продуктов, но в ходе разработки файлы хранятся в различных каталогах:

  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430
  • developerworks/library/xsl/5.8/archive/test-5.8/merge-0430 and
  • developerworks/library/schema/5.8/archive/test-5.8/merge-0430/templates-0430

где сейчас используется версия 5.8, и 0430 обозначает месяц и день последней тестовой версии.

Для урегулирования я добавил параметр -p, который содержит дополнительную часть информации о пути -- archive/test-5.8/merge-0430. Далее, я или кто-то другой можем забыть начальный или конечный слеши и некоторые пользователи Windows могут использовать обратные слеши вместо прямых, поэтому я решил эту проблему при помощи скрипта. Также обратите внимание, что путь к каталогу с шаблонами дважды содержит дату, поэтому мне было необходимо как-то отсечь дату, в данном случае -0430.

В Листинге 12 показан код, который я использовал для обработки двух параметров и удаления части пути в соответствии с этими требованиями. Значение опции -v хранится в переменной ssversion, тогда как усеченная версия переменной -p хранится в pathsuffix и дата, включая начальный дефис, хранится в datesuffix. Комментарии объясняют, что происходит на каждом этапе. Вы узнаете несколько расширений параметров, включая длину, часть строки, соответствие образцу и замену образца даже в этом коротком фрагменте скрипта.

Листинг 12. Анализ параметров для создания авторского пакета developerWorks
while getopts ":v:p:" optval "$@"
  do
    case $optval in
      "v")
        ssversion="$OPTARG"
      ;;
      "p")
        # Превратить все обратные слеши а прямые
        pathsuffix="${OPTARG//\\//}"
        # Убедиться, что в начале стоит / и за ним ничего нет
        [ ${pathsuffix:0:1} != "/" ] && pathsuffix="/$pathsuffix"
        pathsuffix=${pathsuffix%/}
        # Отсечь последний дефис и все расположенное за ним
        dateprefix=${pathsuffix%-*}
        # Использовать длину того, что осталось, чтобы получить дефис 
	# и все расположенное за ним
        [ "$dateprefix" != "$pathsuffix" ] && datesuffix="${pathsuffix:${#dateprefix}}"
        ;;
      *)
        errormsg="Unknown parameter or option error with option - $OPTARG"
        ;;
    esac
  done

Как и в большинстве случаев в Linux и, возможно, в программировании вообще, это не единственное решение этой проблемы, но оно иллюстрирует более утилитарное использование расширений, о которых вы узнали.


Значения по умолчанию?

Из предыдущего раздела вы узнали, как присваивать опциям значения для таких переменных как ssversion или pathsuffix. В этом случае пустая версия будет найдена позже и пустой pathsuffix, который встретится в окончательном варианте, приемлем. Что если вам необходимо присвоить значение по умолчанию параметрам, которые не определены? Расширения shell, показанные а Таблице 3, помогут вам при решении этой задачи.

Таблица 3. Расширения shell по отношению к стандартным комбинациям
РасширениеНазначение
${ПАРАМЕТР:-СЛОВО}Если ПАРАМЕТР не установлен или нулевой, shell расширяет СЛОВО и заменяет результат. Значение ПАРАМЕТРА не меняется.
`${ПАРАМЕТР:=СЛОВО}Если ПАРАМЕТР не установлен или нулевой, shell расширяет СЛОВО и присваивает результат ПАРАМЕТРУ. Это значение затем заменяется. Вы не можете таким образом присваивать значения позиционным параметрам или специальным параметрам.
${ПАРАМЕТР:?СЛОВО}Если ПАРАМЕТР не установлен или нулевой, shell расширяет СЛОВО и записывает результат в стандартный поток вывода ошибок. Если СЛОВО не представлено, вместо этого будет выведено сообщение. Если вы используете shell не в интерактивном режиме, осуществляется выход.
${ПАРАМЕТР:+СЛОВО} Если ПАРАМЕТР не установлен или нулевой, замен не производится. В противном случае shell расширяет СЛОВО и заменяет результат.

Листинг 13 иллюстрирует эти расширения и различия между ними.

Листинг 13. Замены для нулевых и неустановленных переменных
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:-'XYZ'}/${y:-'XYZ'}/$x/$y/"
/'XYZ'/abc def//abc def/
[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:='XYZ'}/${y:='XYZ'}/$x/$y/"
/'XYZ'/abc def/'XYZ'/abc def/
[[ian@pinguino ~]$ ( unset x;y="abc def"; echo "/${x:?'XYZ'}/${y:?'XYZ'}/$x/$y/" )\
>  >so.txt 2>se.txt
[ian@pinguino ~]$ cat so.txt
[ian@pinguino ~]$ cat se.txt
-bash: x: XYZ
[[ian@pinguino ~]$ unset x;y="abc def"; echo "/${x:+'XYZ'}/${y:+'XYZ'}/$x/$y/"
//'XYZ'//abc def/

Передача параметров

При передаче параметров существуют некоторые тонкости, которые могут запутать вас, если вы будете невнимательны. Вы уже знаете о важности кавычек и о том, как кавычки влияют на использование $* и $@, но рассмотрим следующий случай. Предположим, вы хотите создать скрипт или функцию, которые воздействуют на все файлы или, возможно, каталоги в текущем рабочем каталоге. Для иллюстрации этого варианта рассмотрим скрипты ll-1.sh и ll-2.sh из Листинга 14.

Листинг 14. Два простых скрипта
#!/bin/bash
# ll-1.sh
for f in "$@"
  do
    ll-2.sh "$f"
  done

#!/bin/bash
ls -l "$@"

Скрипт ll-1.sh просто передает каждый из параметров по очереди скрипту ll-2.sh, и ll-2.sh создает развернутый листинг каталога с переданными параметрами. Мой тестовый каталог содержит пару пустых файлов, "file1" и "file 2". В Листинге 15 показан результат выполнения скриптов.

Листинг 15. Выполнение скриптов - 1
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2

Пока все хорошо. Но если вы забудете использовать параметр *, скрипт ничего не сделает. Он не работает автоматически с содержимым текущего каталога, как это делает, например, команда ls. Простое исправление могло бы добавить проверку для этого условия в ll-1.sh и использовать вывод команды ls, чтобы сгенерировать ввод для ll-2.sh, когда ll-1.sh ничего не порождает. Возможное решение показано в Листинге 16.

Листинг 16. Исправленная версия ll-1.sh
#!/bin/bash
# ll-1.sh - revision 1
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && for f in "$(ls)"
  do
    ll-2.sh "$f"
  done

Обратите внимание, что мы осмотрительно заключили вывод команды ls в кавычки для уверенности, что мы правильно обработали "file 2". В Листинге 17 показан результат выполнения нового скрипта ll-1.sh с * и без.

Листинг 17. Выполнение скриптов - 2
[ian@pinguino test]$ ll-1.sh *
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file1
-rw-rw-r-- 1 ian ian 0 May 16 15:15 file 2
[ian@pinguino test]$ ll-1.sh
ls: file1
file 2: No such file or directory

Вы удивлены? Когда вы передаете параметры, особенно если они являются выводом команды, это может выглядеть трюком. Здесь ключ в сообщении об ошибке, которое показывает, что имена файлов разделены символами переноса строки. Есть много способов решения этой проблемы, но один простой способ состоит в том, чтобы использовать встроенную команду read, как показано в Листинге 18.

Листинг 18. Выполнение скриптов - 2
#!/bin/bash
# ll-1.sh - revision 2
for f in "$@"
  do
    ll-2.sh "$f"
  done
[ $# -eq 0 ] && ls | while read f
  do
    ll-2.sh "$f"
  done

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


Узнайте больше

Если вы хотите больше узнать о скриптах bash в Linux, прочтите учебное пособие "Подготовка к экзамену 102: Командные оболочки, написание скриптов, программирование и компиляция (LPI exam 102 prep: Shells, scripting, programming, and compiling)". (EN) Ссылки на другие ресурсы вы найдете ниже в разделе Ресурсы. И наконец, не забывайте о рейтинге этой страницы.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, AIX и UNIX, Open source
ArticleID=255762
ArticleTitle=Полезные советы Linux: Параметры bash и расширения параметров
publish-date=09172007