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

         

Пример 9-4. Ограничение времени ожидания команды read

#!/bin/bash # t-out.sh

TIMELIMIT=4 # 4 секунды

read -t $TIMELIMIT variable <&1

echo

if [ -z "$variable" ] then echo "Время ожидания истекло." else echo "variable = $variable" fi

exit 0

$UID

user id number

UID (идентификатор) текущего пользователя, в соответствии с /etc/passwd

Это реальный UID текущего пользователя, даже если он временно приобрел права другого пользователя с помощью su. Переменная $UID доступна только для чтения.

Пример 9-5. Я -- root?

#!/bin/bash # am-i-root.sh: Root я, или не root?

ROOT_UID=0 # $UID root-а всегда равен 0.

if [ "$UID" -eq "$ROOT_UID" ] # Настоящий "root"? then echo "- root!" else echo "простой пользователь (но мамочка вас тоже любит)!" fi

exit 0

# ============================================================= # # Код, приведенный ниже, никогда не отработает, #+ поскольку работа сценария уже завершилась выше

# Еще один способ отличить root-а от не root-а:

ROOTUSER_NAME=root

username=`id -nu` # Или... username=`whoami` if [ "$username" = "$ROOTUSER_NAME" ] then echo "Рутти-тутти. - root!" else echo "Вы - лишь обычный юзер." fi

См. также Пример 2-2.



Переменные $ENV, $LOGNAME, $MAIL, $TERM, $USER и $USERNAME, не являются встроенными

переменными Bash. Тем не менее, они часто инициализируются как переменные окружения в одном из стартовых файлов Bash. Переменная $SHELL, командная оболочка пользователя, может задаваться в /etc/passwd или в сценарии "init" и она тоже не является встроенной переменной Bash.

tcsh% echo $LOGNAME

bozo

tcsh% echo $SHELL

/bin/tcsh

tcsh% echo $TERM

rxvt

bash$ echo $LOGNAME

bozo

bash$ echo $SHELL

/bin/tcsh

bash$ echo $TERM

rxvt

<


Позиционные параметры (аргументы)

$0, $1, $2 и т.д.

аргументы передаются... из командной строки в сценарий, функциям или команде set (см. Пример 4-5 и Пример 11-13)

$#

количество аргументов командной строки [20], или позиционных параметров (см. Пример 33-2)

$*

Все аргументы в виде одной строки (слова)

$@

То же самое, что и $*, но при этом каждый параметр представлен как отдельная строка (слово), т.е. параметры не подвергаются какой либо интерпретации.

Пример 9-6. arglist: Вывод списка аргументов с помощью переменных $* и $@

#!/bin/bash # Вызовите сценарий с несколькими аргументами, например: "один два три".

E_BADARGS=65

if [ ! -n "$1" ] then echo "Порядок использования: `basename $0` argument1 argument2 и т.д." exit $E_BADARGS fi

echo

index=1

echo "Список аргументов в переменной \"\$*\":" for arg in "$*" # Работает некорректно, если "$*" не ограничена кавычками. do echo "Аргумент #$index = $arg" let "index+=1" done # $* воспринимает все аргументы как одну строку. echo "Полный список аргументов выглядит как одна строка."

echo

index=1

echo "Список аргументов в переменной \"\$@\":" for arg in "$@" do echo "Аргумент #$index = $arg" let "index+=1" done # $@ воспринимает аргументы как отдельные строки (слова). echo "Список аргументов выглядит как набор различных строк (слов)."

echo

exit 0

После команды shift (сдвиг), первый аргумент, в переменной $@, теряется, а остальные сдвигаются на одну позицию "вниз" (или "влево", если хотите).

#!/bin/bash # Вызовите сценарий в таком виде: ./scriptname 1 2 3 4 5

echo "$@" # 1 2 3 4 5 shift echo "$@" # 2 3 4 5 shift echo "$@" # 3 4 5

# Каждая из команд "shift" приводит к потере аргумента $1, # но остальные аргументы остаются в "$@".

Специальная переменная $@ может быть использована для выбора типа ввода в сценария.


Команда cat "$@"

позволяет выполнять ввод как со стандартного устройства ввода stdin, так и из файла, имя которого передается сценарию из командной строки. См. Пример 12-17 и Пример 12-18.

Переменные $* и $@, в отдельных случаях, могут содержать противоречивую информацию! Это зависит от содержимого переменной $IFS.

Пример 9-7. Противоречия в переменных $* и $@

#!/bin/bash

# Демонстрация противоречивости содержимого внутренних переменных "$*" и "$@", #+ которая проявляется при изменении порядка заключения параметров в кавычки. # Демонстрация противоречивости, проявляющейся при изменении #+ содержимого переменной IFS.

set -- "Первый один" "второй" "третий:один" "" "Пятый: :один" # Установка аргументов $1, $2, и т.д.

echo

echo 'IFS по-умолчанию, переменная "$*"' c=0 for i in "$*" # в кавычках do echo "$((c+=1)): [$i]" # Эта строка остается без изменений во всех циклах. # Вывод аргументов. done echo ---

echo 'IFS по-умолчанию, переменная $*' c=0 for i in $* # без кавычек do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS по-умолчанию, переменная "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS по-умолчанию, переменная $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo ---

IFS=: echo 'IFS=":", переменная "$*"' c=0 for i in "$*" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная $*' c=0 for i in $* do echo "$((c+=1)): [$i]" done echo ---

var=$* echo 'IFS=":", переменная "$var" (var=$*)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная $var (var=$*)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo ---

var="$*" echo 'IFS=":", переменная $var (var="$*")' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo ---



echo 'IFS=":", переменная "$var" (var="$*")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo ---

var=$@ echo 'IFS=":", переменная $var (var=$@)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная "$var" (var=$@)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo ---

var="$@" echo 'IFS=":", переменная "$var" (var="$@")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo ---

echo 'IFS=":", переменная $var (var="$@")' c=0 for i in $var do echo "$((c+=1)): [$i]" done

echo

# Попробуйте запустить этот сценарий под ksh или zsh -y.

exit 0

# Это сценарий написан Stephane Chazelas, # Незначительные изменения внесены автором документа.

Различия между $@ и $* наблюдаются только тогда, когда они помещаются в двойные кавычки.

Пример 9-8. Содержимое $* и $@, когда переменная $IFS -- пуста

#!/bin/bash

# Если переменная $IFS инициализирована "пустым" значением, # то "$*" и "$@" содержат аргументы не в том виде, в каком ожидается.

mecho () # Вывод аргументов. { echo "$1,$2,$3"; }

IFS="" # Инициализация "пустым" значением. set a b c # Установка аргументов.

mecho "$*" # abc,, mecho $* # a,b,c

mecho $@ # a,b,c mecho "$@" # a,b,c

# Поведение переменных $* и $@, при "пустой" $IFS, зависит # от версии командной оболочки, Bash или sh. # Поэтому, было бы неразумным пользоваться этой "фичей" в своих сценариях.

# Спасибо S.C.

exit 0

Прочие специальные переменные

$-

Список флагов, переданных сценарию (командой set).


См. Пример 11-13.

Эта конструкция изначально была введена в ksh, откуда перекочевала в Bash и, похоже, работает в Bash не совсем надежно. Единственное возможное применение -- проверка - запущен ли сценарий в интерактивном режиме.

$!

PID последнего, запущенного в фоне, процесса

LOG=$0.log

COMMAND1="sleep 100"

echo "Запись в лог всех PID фоновых процессов, запущенных из сценария: $0" >> "$LOG" # Таким образом возможен мониторинг и удаление процессов по мере необходимости. echo >> "$LOG"

# Команды записи в лог.

echo -n "PID of \"$COMMAND1\": " >> "$LOG" ${COMMAND1} & echo $! >> "$LOG" # PID процесса "sleep 100": 1506

# Спасибо Jacques Lederer за предложенный пример.

$_

Специальная переменная, содержит последний аргумент предыдущей команды.

Пример 9-9. Переменная "подчеркивание"

#!/bin/bash

echo $_ # /bin/bash # Для запуска сценария был вызван /bin/bash.

du >/dev/null # Подавление вывода. echo $_ # du

ls -al >/dev/null # Подавление вывода. echo $_ # -al (последний аргумент)

: echo $_ # :

$?

Код возврата команды, функции или скрипта (см. Пример 22-3)

$$

PID самого процесса-сценария. Переменная $$ часто используется при генерации "уникальных" имен для временных файлов (см. Пример A-14, Пример 29-6, Пример 12-23 и Пример 11-23). Обычно это проще чем вызов mktemp.

9.2. Работа со строками

Bash поддерживает на удивление большое количество операций над строками. К сожалению, этот раздел Bash испытывает недостаток унификации. Одни операции являются подмножеством операций подстановки параметров, а другие -- совпадают с функциональностью команды UNIX -- expr. Это приводит к противоречиям в синтаксисе команд и перекрытию функциональных возможностей, не говоря уже о возникающей путанице.

Длина строки

${#string}

expr length $string

expr "$string" : '.*'

stringZ=abcABC123ABCabc

echo ${#stringZ} # 15 echo `expr length $stringZ` # 15 echo `expr "$stringZ" : '.*'` # 15



Пример 9-10. Вставка пустых строк между параграфами в текстовом файле

#!/bin/bash # paragraph-space.sh

# Вставка пустых строк между параграфами в текстовом файле. # Порядок использования: $0 <FILENAME

MINLEN=45 # Возможно потребуется изменить это значение. # Строки, содержащие количество символов меньшее, чем $MINLEN #+ принимаются за последнюю строку параграфа.

while read line # Построчное чтение файла от начала до конца... do echo "$line" # Вывод строки.

len=${#line} if [ "$len" -lt "$MINLEN" ] then echo # Добавление пустой строки после последней строки параграфа. fi done

exit 0

Длина подстроки в строке (подсчет совпадающих символов ведется с начала строки)

expr match "$string" '$substring'

где $substring -- регулярное выражение.

expr "$string" : '$substring'

где $substring -- регулярное выражение.

stringZ=abcABC123ABCabc # |------|

echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8 echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8

Index

expr index $string $substring

Номер позиции первого совпадения в $string c первым символом в $substring.

stringZ=abcABC123ABCabc echo `expr index "$stringZ" C12` # 6 # позиция символа C.

echo `expr index "$stringZ" 1c` # 3 # символ 'c' (в #3 позиции) совпал раньше, чем '1'.

Эта функция довольно близка к функции strchr() в языке C.

Извлечение подстроки

${string:position}

Извлекает подстроку из $string, начиная с позиции $position.

Если строка $string -- "*" или "@", то извлекается позиционный параметр

(аргумент), [21] с номером $position.

${string:position:length}

Извлекает $length символов из $string, начиная с позиции $position.

stringZ=abcABC123ABCabc # 0123456789..... # Индексация начинается с 0.

echo ${stringZ:0} # abcABC123ABCabc echo ${stringZ:1} # bcABC123ABCabc echo ${stringZ:7} # 23ABCabc

echo ${stringZ:7:3} # 23A # Извлекает 3 символа.

# Возможна ли индексация с "правой" стороны строки?



echo ${stringZ:-4} # abcABC123ABCabc # По-умолчанию выводится полная строка. # Однако . . .

echo ${stringZ:(-4)} # Cabc echo ${stringZ: -4} # Cabc # Теперь выводится правильно. # Круглые скобки или дополнительный пробел "экранируют" параметр позиции.

# Спасибо Dan Jacobson, за разъяснения.

Если $string -- "*" или "@", то извлекается до $length позиционных параметров (аргументов), начиная с $position.

echo ${*:2} # Вывод 2-го и последующих аргументов. echo ${@:2} # То же самое.

echo ${*:2:3} # Вывод 3-х аргументов, начиная со 2-го.

expr substr $string $position $length

Извлекает $length символов из $string, начиная с позиции $position.

stringZ=abcABC123ABCabc # 123456789...... # Индексация начинается с 1.

echo `expr substr $stringZ 1 2` # ab echo `expr substr $stringZ 4 3` # ABC

expr match "$string" '\($substring\)'

Находит и извлекает первое совпадение $substring в $string, где $substring -- это регулярное выражение.

expr "$string" : '\($substring\)'

Находит и извлекает первое совпадение $substring в $string, где $substring -- это регулярное выражение.

stringZ=abcABC123ABCabc # =======

echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1 echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1 echo `expr "$stringZ" : '\(.......\)'` # abcABC1 # Все вышеприведенные операции дают один и тот же результат.

expr match "$string" '.*\($substring\)'

Находит и извлекает первое совпадение $substring в $string, где $substring -- это регулярное выражение. Поиск начинается с конца $string.

expr "$string" : '.*\($substring\)'

Находит и извлекает первое совпадение $substring в $string, где $substring -- это регулярное выражение. Поиск начинается с конца $string.

stringZ=abcABC123ABCabc # ======

echo `expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'` # ABCabc echo `expr "$stringZ" : '.*\(......\)'` # ABCabc



Удаление части строки

${string#substring}

Удаление самой короткой, из найденных, подстроки $substring в строке $string. Поиск ведется с начала строки

${string##substring}

Удаление самой длинной, из найденных, подстроки $substring в строке $string. Поиск ведется с начала строки

stringZ=abcABC123ABCabc # |----| # |----------|

echo ${stringZ#a*C} # 123ABCabc # Удаление самой короткой подстроки.

echo ${stringZ##a*C} # abc # Удаление самой длинной подстроки.

${string%substring}

Удаление самой короткой, из найденных, подстроки $substring в строке $string. Поиск ведется с конца строки

${string%%substring}

Удаление самой длинной, из найденных, подстроки $substring в строке $string. Поиск ведется с конца строки

stringZ=abcABC123ABCabc # || # |------------|

echo ${stringZ%b*c} # abcABC123ABCa # Удаляется самое короткое совпадение. Поиск ведется с конца $stringZ.

echo ${stringZ%%b*c} # a # Удаляется самое длинное совпадение. Поиск ведется с конца $stringZ.

Пример 9-11. Преобразование графических файлов из одного формата в другой, с изменением имени файла

#!/bin/bash # cvt.sh: # Преобразование всех файлов в заданном каталоге, #+ из графического формата MacPaint, в формат "pbm".

# Используется утилита "macptopbm", входящая в состав пакета "netpbm", #+ который сопровождается Brian Henderson (bryanh@giraffe-data.com). # Netpbm -- стандартный пакет для большинства дистрибутивов Linux.

OPERATION=macptopbm SUFFIX=pbm # Новое расширение файла.

if [ -n "$1" ] then directory=$1 # Если каталог задан в командной строке при вызове сценария else directory=$PWD # Иначе просматривается текущий каталог. fi

# Все файлы в каталоге, имеющие расширение ".mac", считаются файлами #+ формата MacPaint.

for file in $directory/* # Подстановка имен файлов. do filename=${file%.*c} # Удалить расширение ".mac" из имени файла #+ ( с шаблоном '.*c' совпадают все подстроки #+ начинающиеся с '.' и заканчивающиеся 'c', $OPERATION $file > "$filename.$SUFFIX" # Преобразование с перенаправлением в файл с новым именем rm -f $file # Удаление оригинального файла после преобразования.


echo "$filename.$SUFFIX" # Вывод на stdout. done

exit 0

# Упражнение: # -------- # Сейчас этот сценарий конвертирует *все* файлы в каталоге # Измените его так, чтобы он конвертировал *только* те файлы, #+ которые имеют расширение ".mac".

Замена подстроки

${string/substring/replacement}

Замещает первое вхождение $substring строкой $replacement.

${string//substring/replacement}

Замещает все вхождения $substring строкой $replacement.

stringZ=abcABC123ABCabc

echo ${stringZ/abc/xyz} # xyzABC123ABCabc # Замена первой подстроки 'abc' строкой 'xyz'.

echo ${stringZ//abc/xyz} # xyzABC123ABCxyz # Замена всех подстрок 'abc' строкой 'xyz'.

${string/#substring/replacement}

Подстановка строки $replacement

вместо $substring. Поиск ведется с начала строки $string.

${string/%substring/replacement}

Подстановка строки $replacement

вместо $substring. Поиск ведется с конца строки $string.

stringZ=abcABC123ABCabc

echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc # Поиск ведется с начала строки

echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ # Поиск ведется с конца строки

9.2.1. Использование awk при работе со строками

В качестве альтернативы, Bash-скрипты могут использовать средства awk при работе со строками.

Пример 9-12. Альтернативный способ извлечения подстрок

#!/bin/bash # substring-extraction.sh

String=23skidoo1 # 012345678 Bash # 123456789 awk # Обратите внимание на различия в индексации: # Bash начинает индексацию с '0'. # Awk начинает индексацию с '1'.

echo ${String:2:4} # с 3 позиции (0-1-2), 4 символа # skid

# В эквивалент в awk: substr(string,pos,length). echo | awk ' { print substr("'"${String}"'",3,4) # skid } ' # Передача пустого "echo" по каналу в awk, означает фиктивный ввод, #+ делая, тем самым, ненужным предоставление имени файла.

exit 0

9.2.2. Дальнейшее обсуждение

Дополнительную информацию, по работе со строками, вы найдете в разделе Section 9.3 и в секции, посвященной команде expr. Примеры сценариев:



Пример 12-6

Пример 9-15

Пример 9-16

Пример 9-17

Пример 9-19

9.3. Подстановка параметров

Работа с переменными и/или подстановка их значений

${parameter}

То же самое, что и $parameter, т.е. значение переменной parameter. В отдельных случаях, при возникновении неоднозначности интерпретации, корректно будет работать только такая форма записи: ${parameter}.

Может использоваться для конкатенации (слияния) строковых переменных.

your_id=${USER}-on-${HOSTNAME} echo "$your_id" # echo "Старый \$PATH = $PATH" PATH=${PATH}:/opt/bin #Добавление /opt/bin в $PATH. echo "Новый \$PATH = $PATH"

${parameter-default}, ${parameter:-default}

Если параметр отсутствует, то используется значение по-умолчанию.

echo ${username-`whoami`} # Вывод результата работы команды `whoami`, если переменная $username не установлена.

Формы записи ${parameter-default}

и ${parameter:-default}

в большинстве случаев можно считать эквивалентными. Дополнительный символ : имеет значение только тогда, когда parameter

определен, но имеет "пустое" (null) значение.

#!/bin/bash

username0= # переменная username0 объявлена, но инициализирована "пустым" значением. echo "username0 = ${username0-`whoami`}" # Вывод после символа "=" отсутствует.

echo "username1 = ${username1-`whoami`}" # Переменная username1 не была объявлена. # Выводится имя пользователя, выданное командой `whoami`.

username2= # переменная username2 объявлена, но инициализирована "пустым" значением. echo "username2 = ${username2:-`whoami`}" # Выводится имя пользователя, выданное командой `whoami`, поскольку #+здесь употребляется конструкция ":-" , а не "-".

exit 0

Параметры по-умолчанию

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

DEFAULT_FILENAME=generic.data filename=${1:-$DEFAULT_FILENAME} # Если имя файла не задано явно, то последующие операторы будут работать #+ с файлом "generic.data". #



см. так же Пример 3-4, Пример 28-2 и Пример A-7.

Сравните этот подход с методом списков and list, для задания параметров командной строки по-умолчанию .

${parameter=default}, ${parameter:=default}

Если значения параметров не задананы явно, то они принимают значения по-умолчанию.

Оба метода задания значений по-умолчанию до определенной степени идентичны. Символ : имеет значение только когда $parameter был инициализирован "пустым" (null) значением, [22] как показано выше.

echo ${username=`whoami`} # Переменная "username" принимает значение, возвращаемое командой `whoami`.

${parameter+alt_value}, ${parameter:+alt_value}

Если параметр имеет какое либо значение, то используется alt_value, иначе -- null ("пустая" строка).

Оба варианта до определенной степени идентичны. Символ : имеет значение только если parameter объявлен и "пустой", см. ниже.

echo "###### \${parameter+alt_value} ########" echo

a=${param1+xyz} echo "a = $a" # a =

param2= a=${param2+xyz} echo "a = $a" # a = xyz

param3=123 a=${param3+xyz} echo "a = $a" # a = xyz

echo echo "###### \${parameter:+alt_value} ########" echo

a=${param4:+xyz} echo "a = $a" # a =

param5= a=${param5:+xyz} echo "a = $a" # a = # Вывод отличается от a=${param5+xyz}

param6=123 a=${param6+xyz} echo "a = $a" # a = xyz

${parameter?err_msg}, ${parameter:?err_msg}

Если parameter инициализирован, то используется его значение, в противном случае -- выводится err_msg.

Обе формы записи можно, до определенной степени, считать идентичными. Символ : имеет значение только когда parameter

инициализирован "пустым" значением, см. ниже.

Пример 9-13. Подстановка параметров и сообщения об ошибках

#!/bin/bash

# Проверка отдельных переменных окружения. # Если переменная, к примеру $USER, не установлена, #+ то выводится сообщение об ошибке.

: ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?} echo echo "Имя машины: $HOSTNAME." echo "Ваше имя: $USER." echo "Ваш домашний каталог: $HOME." echo "Ваш почтовый ящик: $MAIL." echo echo "Если перед Вами появилось это сообщение," echo "то это значит, что все критические переменные окружения установлены." echo echo



# ------------------------------------------------------

# Конструкция ${variablename?} так же выполняет проверку #+ наличия переменной в сценарии.

ThisVariable=Value-of-ThisVariable # Обратите внимание, в строковые переменные могут быть записаны #+ символы, которые запрещено использовать в именах переменных. : ${ThisVariable?} echo "Value of ThisVariable is $ThisVariable". echo echo

: ${ZZXy23AB?"Переменная ZZXy23AB не инициализирована."} # Если ZZXy23AB не инициализирована, #+ то сценарий завершается с сообщением об ошибке.

# Текст сообщения об ошибке можно задать свой. # : ${ZZXy23AB?"Переменная ZZXy23AB не инициализирована."}

# То же самое: dummy_variable=${ZZXy23AB?} # dummy_variable=${ZZXy23AB?"Переменная ZXy23AB не инициализирована."} # # echo ${ZZXy23AB?} >/dev/null

echo "Это сообщение не будет напечатано, поскольку сценарий завершится раньше."

HERE=0 exit $HERE # Сценарий завершит работу не здесь.

Пример 9-14. Подстановка параметров и сообщение о "порядке использования"

#!/bin/bash # usage-message.sh

: ${1?"Порядок использования: $0 ARGUMENT"} # Сценарий завершит свою работу здесь, если входные аргументы отсутствуют, #+ со следующим сообщением. # usage-message.sh: 1: Порядок использования: usage-message.sh ARGUMENT

echo "Эти две строки появятся, только когда задан аргумент в командной строке." echo "Входной аргумент командной строки = \"$1\""

exit 0 # Точка выхода находится здесь, только когда задан аргумент командной строки.

# Проверьте код возврата в обеих случаях, с и без аргумента командной строки. # Если аргумент задан, то код возврата будет равен 0. # Иначе -- 1.

Подстановка параметров и/или экспансия. Следующие выражения могут служить дополнениями оператора match команды expr, применяемой к строкам (см. Пример 12-6). Как правило, они используются при разборе имен файлов и каталогов.

Длина переменной / Удаление подстроки

${#var}

String length (число символов в переменной $var).


В случае массивов, команда ${#array} возвращает длину первого элемента массива.

Исключения:

${#*} и ${#@}

возвращает количество аргументов (позиционных параметров).

Для массивов, ${#array[*]} и ${#array[@]}

возвращает количество элементов в массиве.

Пример 9-15. Длина переменной

#!/bin/bash # length.sh

E_NO_ARGS=65

if [ $# -eq 0 ] # Для работы скрипта необходим хотя бы один входной параметр. then echo "Вызовите сценарий с одним или более параметром командной строки." exit $E_NO_ARGS fi

var01=abcdEFGH28ij

echo "var01 = ${var01}" echo "Length of var01 = ${#var01}"

echo "Количество входных параметров = ${#@}" echo "Количество входных параметров = ${#*}"

exit 0

${var#Pattern}, ${var##Pattern}

Удаляет из переменной $var наименьшую/наибольшую подстроку, совпадающую с шаблоном $Pattern. Поиск ведется с начала строки $var.

Пример использования из Пример A-8:

# Функцмя из сценария "days-between.sh". # Удаляет нули, стоящие в начале аргумента-строки.

strip_leading_zero () # Ведущие нули, которые согут находиться в номере дня/месяца, # лучше удалить val=${1#0} # В противном случае Bash будет интерпретировать числа return $val # как восьмеричные (POSIX.2, sect 2.9.2.1). }

Другой пример:

echo `basename $PWD` # Имя текущего рабочего каталога. echo "${PWD##*/}" # Имя текущего рабочего каталога. echo echo `basename $0` # Имя файла-сценария. echo $0 # Имя файла-сценария. echo "${0##*/}" # Имя файла-сценария. echo filename=test.data echo "${filename##*.}" # data # Расширение файла.

${var%Pattern}, ${var%%Pattern}

Удаляет из переменной $var наименьшую/наибольшую подстроку, совпадающую с шаблоном $Pattern. Поиск ведется с конца строки $var.

Bash версии 2 имеет ряд дополнительных возможностей.

Пример 9-16. Поиск по шаблону в подстановке параметров

#!/bin/bash # Поиск по шаблону в операциях подстановки параметров # ## % %%.

var1=abcd12345abc6789 pattern1=a*c # * (символ шаблона), означает любые символы между a и c.



echo echo "var1 = $var1" # abcd12345abc6789 echo "var1 = ${var1}" # abcd12345abc6789 (альтернативный вариант) echo "Число символов в ${var1} = ${#var1}" echo "pattern1 = $pattern1" # a*c (между 'a' и 'c' могут быть любые символы) echo

echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789 # Наименьшая подстрока, удаляются первые 3 символа abcd12345abc6789 ^^^^^^ |-| echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789 # Наибольшая подстрока, удаляются первые 12 символов abcd12345abc6789 # ^^^^^^ |----------|

echo; echo

pattern2=b*9 # все, что между 'b' и '9' echo "var1 = $var1" # abcd12345abc6789 echo "pattern2 = $pattern2" echo

echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a # Наименьшая подстрока, удаляются последние 6 символов abcd12345abc6789 # ^^^^^^^^^ |----| echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a # Наибольшая подстрока, удаляются последние 12 символов abcd12345abc6789 # ^^^^^^^^^ |-------------|

# Запомните, # и ## используются для поиска с начала строки, # % и %% используются для поиска с конца строки.

echo

exit 0

Пример 9-17. Изменение расширений в именах файлов:

#!/bin/bash

# rfe # ---

# Изменение расширений в именах файлов. # # rfe old_extension new_extension # # Пример: # Изменить все расширения *.gif в именах файлов на *.jpg, в текущем каталоге # rfe gif jpg

ARGS=2 E_BADARGS=65

if [ $# -ne "$ARGS" ] then echo "Порядок использования: `basename $0` old_file_suffix new_file_suffix" exit $E_BADARGS fi

for filename in *.$1 # Цикл прохода по списку имен файлов, имеющих расширение равное первому аргументу. do mv $filename ${filename%$1}$2 # Удалить первое расширение и добавить второе, done

exit 0

Подстановка значений переменных / Замена подстроки

Эти конструкции перекочевали в Bash из ksh.

${var:pos}

Подстанавливается значение переменной var, начиная с позиции pos.

${var:pos:len}

Подстанавливается значение переменной var, начиная с позиции pos, не более len символов.


См. Пример A-16.

${var/Pattern/Replacement}

Первое совпадение с шаблоном Pattern, в переменной var замещается подстрокой Replacement.

Если подстрока Replacement

отсутствует, то найденное совпадение будет удалено.

${var//Pattern/Replacement}

Глобальная замена. Все найденные совпадения с шаблоном Pattern, в переменной var, будут замещены подстрокой Replacement.

Как и в первом случае, если подстрока Replacement

отсутствует, то все найденные совпадения будут удалены.

Пример 9-18. Поиск по шаблону при анализе произвольных строк

#!/bin/bash

var1=abcd-1234-defg echo "var1 = $var1"

t=${var1#*-*} echo "var1 (все, от начала строки по первый символ \"-\", включительно, удаляется) = $t" # t=${var1#*-} то же самое, #+ поскольку оператор # ищет кратчайшее совпадение, #+ а * соответствует любым предшествующим символам, включая пустую строку. # (Спасибо S. C. за разъяснения.)

t=${var1##*-*} echo "Если var1 содержит \"-\", то возвращается пустая строка... var1 = $t"

t=${var1%*-*} echo "var1 (все, начиная с последнего \"-\" удаляется) = $t"

echo

# ------------------------------------------- path_name=/home/bozo/ideas/thoughts.for.today # ------------------------------------------- echo "path_name = $path_name" t=${path_name##/*/} echo "Из path_name удален путь к файлу = $t" # В данном случае, тот эе эффект можно получить так: t=`basename $path_name` # t=${path_name%/}; t=${t##*/} более общее решение, #+ но имеет некоторые ограничения. # Если $path_name заканчивается символом перевода строки, то `basename $path_name` не будет работать, #+ но для данного случая вполне применимо. # (Спасибо S.C.)

t=${path_name%/*.*} # Тот же эффект дает t=`dirname $path_name` echo "Из path_name удалено имя файла = $t" # Этот вариант будет терпеть неудачу в случаях: "../", "/foo////", # "foo/", "/". # Удаление имени файла, особенно когда его нет, #+ использование dirname имеет свои особенности. # (Спасибо S.C.)



echo

t=${path_name:11} echo "Из $ path_name удалены первые 11 символов = $t" t=${path_name:11:5} echo "Из $path_name удалены первые 11 символов, выводится 5 символов = $t"

echo

t=${path_name/bozo/clown} echo "В $path_name подстрока \"bozo\" заменена на \"clown\" = $t" t=${path_name/today/} echo "В $path_name подстрока \"today\" удалена = $t" t=${path_name//o/O} echo "В $path_name все символы \"o\" переведены в верхний регистр, = $t" t=${path_name//o/} echo "Из $path_name удалены все символы \"o\" = $t"

exit 0

${var/#Pattern/Replacement}

Если в переменной var найдено совпадение с Pattern, причем совпадающая подстрока расположена в начале строки (префикс), то оно заменяется на Replacement. Поиск ведется с начала строки

${var/%Pattern/Replacement}

Если в переменной var найдено совпадение с Pattern, причем совпадающая подстрока расположена в конце строки (суффикс), то оно заменяется на Replacement. Поиск ведется с конца строки

Пример 9-19. Поиск префиксов и суффиксов с заменой по шаблону

#!/bin/bash # Поиск с заменой по шаблону.

v0=abc1234zip1234abc # Начальное значение переменной. echo "v0 = $v0" # abc1234zip1234abc echo

# Поиск совпадения с начала строки. v1=${v0/#abc/ABCDEF} # abc1234zip1234abc # |-| echo "v1 = $v1" # ABCDE1234zip1234abc # |---|

# Поиск совпадения с конца строки. v2=${v0/%abc/ABCDEF} # abc1234zip123abc # |-| echo "v2 = $v2" # abc1234zip1234ABCDEF # |----|

echo

# ---------------------------------------------------- # Если совпадение находится не с начала/конца строки, #+ то замена не производится. # ---------------------------------------------------- v3=${v0/#123/000} # Совпадение есть, но не в начале строки. echo "v3 = $v3" # abc1234zip1234abc # ЗАМЕНА НЕ ПРОИЗВОДТСЯ! v4=${v0/%123/000} # Совпадение есть, но не в конце строки. echo "v4 = $v4" # abc1234zip1234abc # ЗАМЕНА НЕ ПРОИЗВОДТСЯ!



exit 0

${!varprefix*}, ${!varprefix@}

Поиск по шаблону всех, ранее объявленных переменных, имена которых начинаются с varprefix.

xyz23=whatever xyz24=

a=${!xyz*} # Подстановка имен объявленных переменных, которые начинаются с "xyz". echo "a = $a" # a = xyz23 xyz24 a=${!xyz@} # То же самое. echo "a = $a" # a = xyz23 xyz24

# Эта возможность была добавлена в Bash, в версии 2.04.

9.4. Объявление переменных: declare и typeset

Инструкции declare и typeset являются встроенными инструкциями (они абсолютно идентичны друг другу и являются синонимами) и предназначена для наложения ограничений на переменные. Это очень слабая попытка контроля над типами, которая имеется во многих языках программирования. Инструкция declare появилась в Bash, начиная с версии 2. Кроме того, инструкция typeset может использоваться и в ksh-сценариях.

ключи инструкций declare/typeset

-r readonly (только для чтения)

declare -r var1

(declare -r var1 аналогично объявлению readonly var1)

Это грубый эквивалент констант (const) в языке C. Попытка изменения таких переменных завершается сообщением об ошибке.

-i integer

declare -i number # Сценарий интерпретирует переменную "number" как целое число.

number=3 echo "number = $number" # number = 3

number=three echo "number = $number" # number = 0 # Строка "three" интерпретируется как целое число.

Примечательно, что допускается выполнение некоторых арифметических операций над переменными, объявленными как integer, не прибегая к инструкциям expr или let.

-a array

declare -a indices

Переменная indices объявляется массивом.

-f functions

declare -f

Инструкция declare -f, без аргументов, приводит к выводу списка ранее объявленных функций в сценарии.

declare -f function_name

Инструкция declare -f function_name выводит имя функции function_name, если она была объявлена ранее.

-x export

declare -x var3

Эта инструкция объявляет переменную, как доступную для экспорта.



var=$value

declare -x var3=373

Инструкция declare допускает совмещение объявления и присваивания значения переменной одновременно.

Пример 9-20. Объявление переменных с помощью инструкции declare

#!/bin/bash

func1 () { echo Это функция. }

declare -f # Список функций, объявленных выше.

echo

declare -i var1 # var1 -- целочисленная переменная. var1=2367 echo "переменная var1 объявлена как $var1" var1=var1+1 # Допустимая арифметическая операция над целочисленными переменными. echo "переменная var1 увеличена на 1 = $var1." # Допустимая операция для целочисленных переменных echo "Возможно ли записать дробное число 2367.1 в var1?" var1=2367.1 # Сообщение об ошибке, переменная не изменяется. echo "значение переменной var1 осталось прежним = $var1"

echo

declare -r var2=13.36 # инструкция 'declare' допускает установку свойств переменной #+ и одновременно присваивать значение. echo "var2 declared as $var2" # Допускается ли изменять значение readonly переменных? var2=13.37 # Сообщение об ошибке и завершение работы сценария.

echo "значение переменной var2 осталось прежним $var2" # Эта строка никогда не будет выполнена.

exit 0 # Сценарий завершит работу выше.

9.5. Косвенные ссылки на переменные

Предположим, что значение одной переменной -- есть имя второй переменной. Возможно ли получить значение второй переменной через обращение к первой? Например, Пусть a=letter_of_alphabet и letter_of_alphabet=z, тогда вопрос будет звучать так: "Возможно ли получить значение z, обратившись к переменной a?". В действительности это возможно и это называется косвенной ссылкой. Для этого необходимо прибегнуть к несколько необычной нотации eval var1=\$$var2.

Пример 9-21. Косвенные ссылки

#!/bin/bash # Косвенные ссылки на переменные.

a=letter_of_alphabet letter_of_alphabet=z

echo

# Прямое обращение к переменной. echo "a = $a"

# Косвенное обращение к переменной. eval a=\$$a echo "А теперь a = $a"



echo

# Теперь попробуем изменить переменную, на которую делается ссылка.

t=table_cell_3 table_cell_3=24 echo "\"table_cell_3\" = $table_cell_3" echo -n "разыменование (получение ссылки) \"t\" = "; eval echo \$$t # В данном, простом, случае, # eval t=\$$t; echo "\"t\" = $t" # дает тот же результат (почему?).

echo

t=table_cell_3 NEW_VAL=387 table_cell_3=$NEW_VAL echo "Значение переменной \"table_cell_3\" изменено на $NEW_VAL." echo "Теперь \"table_cell_3\" = $table_cell_3" echo -n "разыменование (получение ссылки) \"t\" = "; eval echo \$$t # инструкция "eval" принимает два аргумента "echo" и "\$$t" (назначает равным $table_cell_3) echo

# (Спасибо S.C. за разъяснения.)

# Еще один способ -- нотация ${!t}, будет обсуждаться в разделе "Bash, версия 2". # Так же, см. пример "ex78.sh".

exit 0

Пример 9-22. Передача косвенных ссылок в awk

#!/bin/bash

# Другая версия сценария "column totaler" # который суммирует заданную колонку (чисел) в заданном файле. # Здесь используются косвенные ссылки.

ARGS=2 E_WRONGARGS=65

if [ $# -ne "$ARGS" ] # Проверка количества входных аргументов. then echo "Порядок использования: `basename $0` filename column-number" exit $E_WRONGARGS fi

filename=$1 column_number=$2

#===== До этой строки идентично первоначальному варианту сценария =====#

# Мнгострочные скрипты awk вызываются конструкцией awk ' ..... '

# Начало awk-сценария. # ------------------------------------------------ awk "

{ total += \$${column_number} # косвенная ссылка } END { print total }

" "$filename" # ------------------------------------------------ # Конец awk-сценария.

# Косвенные ссылки делают возможным бесконфликтное # обращение к переменным shell внутри вложенных сценариев awk. # Спасибо Stephane Chazelas.

exit 0

Такой метод обращения к переменным имеет свои особенности. Если переменная, на которую делается ссылка, меняет свое значение, то переменная которая ссылается, должна быть должным образом разыменована, т.е. олжна быть выполнена операция получения ссылки, как это делается в примере выше. К счастью, нотация ${!variable}, введенная в Bash, начиная с версии 2 (см. Пример 34-2) позволяет выполнять косвенные ссылки более интуитивно понятным образом.

<


9.6. $RANDOM: генерация псевдослучайных целых чисел

$RANDOM -- внутренняя функция Bash (не константа), которая возвращает псевдослучайные целые числа в диапазоне 0 - 32767. Функция $RANDOM не должна использоваться для генераци ключей шифрования.

Пример 9-23. Генерация случайных чисел

#!/bin/bash

# $RANDOM возвращает различные случайные числа при каждом обращении к ней. # Диапазон изменения: 0 - 32767 (16-битовое целое со знаком).

MAXCOUNT=10 count=1

echo echo "$MAXCOUNT случайных чисел:" echo "-----------------" while [ "$count" -le $MAXCOUNT ] # Генерация 10 ($MAXCOUNT) случайных чисел. do number=$RANDOM echo $number let "count += 1" # Нарастить счетчик. done echo "-----------------"

# Если вам нужны случайные числа не превышающие определенного числа, # воспользуйтесь оператором деления по модулю (остаток от деления).

RANGE=500

echo

number=$RANDOM let "number %= $RANGE" echo "Случайное число меньше $RANGE --- $number"

echo

# Если вы желаете ограничить диапазон "снизу", # то просто производите генерацию псевдослучайных чисел в цикле до тех пор, # пока не получите число большее нижней границы.

FLOOR=200

number=0 # инициализация while [ "$number" -le $FLOOR ] do number=$RANDOM done echo "Случайное число, большее $FLOOR --- $number" echo

# Эти два способа могут быть скомбинированы. number=0 #initialize while [ "$number" -le $FLOOR ] do number=$RANDOM let "number %= $RANGE" # Ограничение "сверху" числом $RANGE. done echo "Случайное число в диапазоне от $FLOOR до $RANGE --- $number" echo

# Генерация случайных "true" и "false" значений. BINARY=2 number=$RANDOM T=1

let "number %= $BINARY" # let "number >>= 14" дает более равномерное распределение # (сдвиг вправо смещает старший бит на нулевую позицию, остальные биты обнуляются). if [ "$number" -eq $T ] then echo "TRUE" else echo "FALSE" fi



echo

# Можно имитировать бросание 2-х игровых кубиков. SPOTS=7 # остаток от деления на 7 дает диапазон 0 - 6. ZERO=0 die1=0 die2=0

# Кубики "выбрасываются" раздельно.

while [ "$die1" -eq $ZERO ] # Пока на "кубике" ноль. do let "die1 = $RANDOM % $SPOTS" # Имитировать бросок первого кубика. done

while [ "$die2" -eq $ZERO ] do let "die2 = $RANDOM % $SPOTS" # Имитировать бросок второго кубика. done

let "throw = $die1 + $die2" echo "Результат броска кубиков = $throw" echo

exit 0

Пример 9-24. Выбор случайной карты из колоды

#!/bin/bash # pick-card.sh

# Пример выбора случайного элемента массива.

# Выбор случайной карты из колоды.

Suites="Треф Бубей Червей Пик"

Denominations="2 3 4 5 6 7 8 9 10 Валет Дама Король Туз"

suite=($Suites) # Инициализация массивов. denomination=($Denominations)

num_suites=${#suite[*]} # Количество элементов массивов. num_denominations=${#denomination[*]}

echo -n "${denomination[$((RANDOM%num_denominations))]} " echo ${suite[$((RANDOM%num_suites))]}

# $bozo sh pick-cards.sh # Валет Треф

# Спасибо "jipe," за пояснения по работе с $RANDOM. exit 0

Jipe подсказал еще один способ генерации случайных чисел из заданного диапазона.

# Генерация случайных чисел в диапазоне 6 - 30. rnumber=$((RANDOM%25+6))

# Генерируется случайное число из диапазона 6 - 30, #+ но при этом число должно делиться на 3 без остатка. rnumber=$(((RANDOM%30/3+1)*3))

# Упражнение: Попробуйте разобраться с выражением самостоятельно.

Насколько случайны числа, возвращаемые функцией $RANDOM? Лучший способ оценить "случайность" генерируемых чисел -- это написать сценарий, который будет имитировать бросание игрального кубика достаточно большое число раз, а затем выведет количество выпадений каждой из граней...

Пример 9-25. Имитация бросания кубика с помощью RANDOM

#!/bin/bash # Случайные ли числа возвращает RANDOM?

RANDOM=$$ # Инициализация генератора случайных чисел числом PID процесса-сценария.



PIPS=6 # Кубик имеет 6 граней. MAXTHROWS=600 # Можете увеличить, если не знаете куда девать свое время. throw=0 # Счетчик бросков.

zeroes=0 # Обнулить счетчики выпадения отдельных граней. ones=0 # т.к. неинициализированные переменные - "пустые", и не равны нулю!. twos=0 threes=0 fours=0 fives=0 sixes=0

print_result () { echo echo "единиц = $ones" echo "двоек = $twos" echo "троек = $threes" echo "четверок = $fours" echo "пятерок = $fives" echo "шестерок = $sixes" echo }

update_count() { case "$1" in 0) let "ones += 1";; # 0 соответствует грани "1". 1) let "twos += 1";; # 1 соответствует грани "2", и так далее 2) let "threes += 1";; 3) let "fours += 1";; 4) let "fives += 1";; 5) let "sixes += 1";; esac }

echo

while [ "$throw" -lt "$MAXTHROWS" ] do let "die1 = RANDOM % $PIPS" update_count $die1 let "throw += 1" done

print_result

# Количество выпадений каждой из граней должно быть примерно одинаковым, если считать RANDOM достаточно случайным. # Для $MAXTHROWS = 600, каждая грань должна выпасть примерно 100 раз (плюс-минус 20). # # Имейте ввиду, что RANDOM - это генератор ПСЕВДОСЛУЧАЙНЫХ чисел,

# Упражнение: # --------------- # Перепишите этот сценарий так, чтобы он имитировал 1000 бросков монеты. # На каждом броске возможен один из двух вариантов выпадения - "ОРЕЛ" или "РЕШКА".

exit 0

Как видно из последнего примера, неплохо было бы производить переустановку начального числа генератора случайных чисел RANDOM перед тем, как начать работу с ним. Если используется одно и то же начальное число, то генератор RANDOM будет выдавать одну и ту же последовательность чисел. (Это совпадает с поведением функции random() в языке C.)

Пример 9-26. Переустановка RANDOM

#!/bin/bash # seeding-random.sh: Переустановка переменной RANDOM.

MAXCOUNT=25 # Длина генерируемой последовательности чисел.



random_numbers () { count=0 while [ "$count" -lt "$MAXCOUNT" ] do number=$RANDOM echo -n "$number " let "count += 1" done }

echo; echo

RANDOM=1 # Переустановка начального числа генератора случайных чисел RANDOM. random_numbers

echo; echo

RANDOM=1 # То же самое начальное число... random_numbers # ...в результате получается та же последовательность чисел. # # В каких случаях может оказаться полезной генерация совпадающих серий?

echo; echo

RANDOM=2 # Еще одна попытка, но с другим начальным числом... random_numbers # получим другую последовательность.

echo; echo

# RANDOM=$$ в качестве начального числа выбирается PID процесса-сценария. # Вполне допустимо взять в качестве начального числа результат работы команд 'time' или 'date'.

# Немного воображения... SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') # Псевдослучайное число забирается #+ из системного генератора псевдослучайных чисел /dev/urandom , #+ затем конвертируется в восьмеричное число командой "od", #+ и наконец "awk" возвращает единственное число для переменной SEED. RANDOM=$SEED random_numbers

echo; echo

exit 0

Системный генератор /dev/urandom дает последовательность псевдослучайных чисел с более равномерным распределением, чем $RANDOM. Команда dd if=/dev/urandom of=targetfile bs=1 count=XX создает файл, содержащий последовательность псевдослучайных чисел. Однако, эти числа требуют дополнительной обработки, например с помощью команды od (этот прием используется в примере выше) или dd (см. Пример 12-42).

Есть и другие способы генерации псевдослучайных последовательностей в сценариях. Awk имеет для этого достаточно удобные средства.

Пример 9-27. Получение псевдослучайных чисел с помощью awk

#!/bin/bash # random2.sh: Генерация псевдослучайных чисел в диапазоне 0 - 1. # Используется функция rand() из awk.

AWKSCRIPT=' { srand(); print rand() } ' # Команды/параметры, передаваемые awk # Обратите внимание, функция srand() переустанавливает начальное число генератора случайных чисел.

echo -n "Случайное число в диапазоне от 0 до 1 = " echo | awk "$AWKSCRIPT"

exit 0

# Упражнения: # ---------

# 1) С помощью оператора цикла выведите 10 различных случайных чисел. # (Подсказка: вам потребуется вызвать функцию "srand()" # в каждом цикле с разными начальными числами. # Что произойдет, если этого не сделать?)

# 2) Заставьте сценарий генерировать случайные числа в диапазоне 10 - 100 # используя целочисленный множитель, как коэффициент масштабирования

# 3) То же самое, что и во втором упражнении, # но на этот раз случайные числа должны быть целыми.

<


9.7. Двойные круглые скобки

Эта конструкция во многом похожа на инструкцию let, внутри ((...)) вычисляются арифметические выражения и возвращается их результат. В простейшем случае, конструкция a=$(( 5 + 3 ))

присвоит переменной "a" значение выражения "5 + 3", или 8. Но, кроме того, двойные круглые скобки позволяют работать с переменными в стиле языка C.

Пример 9-28. Работа с переменными в стиле языка C

#!/bin/bash # Работа с переменными в стиле языка C.

echo

(( a = 23 )) # Присвоение переменной в стиле C, с обоих строн от "=" стоят пробелы. echo "a (начальное значение) = $a"

(( a++ )) # Пост-инкремент 'a', в стиле C. echo "a (после a++) = $a"

(( a-- )) # Пост-декремент 'a', в стиле C. echo "a (после a--) = $a"

(( ++a )) # Пред-инкремент 'a', в стиле C. echo "a (после ++a) = $a"

(( --a )) # Пред-декремент 'a', в стиле C. echo "a (после --a) = $a"

echo

(( t = a<45?7:11 )) # Трехместный оператор в стиле языка C. echo "If a < 45, then t = 7, else t = 11." echo "t = $t " # Да!

echo

# См. так же описание ((...)) в циклах "for" и "while".

# Эта конструкция доступна в Bash, начиная с версии 2.04.

exit 0

См. так же Пример 10-12.

Глава 10. Циклы и ветвления

Управление ходом исполнения -- один из ключевых моментов структурной организации сценариев на языке командной оболочки. Циклы и преходы являются теми инструментальными средствами, которые обеспечивают управление порядком исполнения команд.

10.1. Циклы

Цикл -- это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.

циклы for

for (in)

Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C.

for arg in [list]

do

 команда(ы)...

done

На каждом проходе цикла, переменная-аргумент цикла arg

последовательно, одно за другим, принимает значения из списка list.

for arg in "$var1" "$var2" "$var3" ... "$varN" # На первом проходе, $arg = $var1 # На втором проходе, $arg = $var2 # На третьем проходе, $arg = $var3 # ... # На N-ном проходе, $arg = $varN



# Элементы списка заключены в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).

Элементы списка могут включать в себя шаблонные символы.

Есл ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.

for arg in [list] ; do

Пример 10-1. Простой цикл for

#!/bin/bash # Список планет.

for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон do echo $planet done

echo

# Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент . for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон" do echo $planet done

exit 0

Каждый из элементов [списка]

может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке, необходимо использовать инструкцию set (см. Пример 11-13).

Пример 10-2. Цикл for с двумя параметрами в каждом из элементов списка

#!/bin/bash # Список планет.

# Имя кажой планеты ассоциировано с расстоянием от планеты до Солнца (млн. миль).

for planet in "Меркурий 36" "Венера 67" "Земля 93" "Марс 142" "Юпитер 483" do set -- $planet # Разбиение переменной "planet" на множество аргументов (позиционных параметров). # Конструкция "--" предохраняет от неожиданностей, если $planet "пуста" или начинается с символа "-".

# Если каждый из аргументов потребуется сохранить, поскольку на следующем проходе они будут "забиты" новыми значениями, # То можно поместить их в массив, # original_params=("$@")

echo "$1 в $2,000,000 миль от Солнца" #----две табуляции---к параметру $2 добавлены нули done

# (Спасибо S.C., за разъяснения.)

exit 0

В качестве списка, в цикле for, можно использовать переменную.

Пример 10-3. Fileinfo: обработка списка файлов, находящегося в переменной



#!/bin/bash # fileinfo.sh

FILES="/usr/sbin/privatepw /usr/sbin/pwck /usr/sbin/go500gw /usr/bin/fakefile /sbin/mkreiserfs /sbin/ypbind" # Список интересующих нас файлов. # В список добавлен фиктивный файл /usr/bin/fakefile.

echo

for file in $FILES do

if [ ! -e "$file" ] # Проверка наличия файла. then echo "Файл $file не найден."; echo continue # Переход к следующей итерации. fi

ls -l $file | awk '{ print $8 " размер: " $5 }' # Печать 2 полей. whatis `basename $file` # Информация о файле. echo done

exit 0

В [списке]

цикла for могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны.

Пример 10-4. Обработка списка файлов в цикле for

#!/bin/bash # list-glob.sh: Создание список файлов в цикле for с использованием # операции подстановки имен файлов ("globbing").

echo

for file in * do ls -l "$file" # Список всех файлов в $PWD (текущем каталоге). # Напоминаю, что символу "*" соответствует любое имя файла, # однако, в операциях подстановки имен файлов ("globbing"), # имеются исключения -- имена файлов, начинающиеся с точки.

# Если в каталоге нет ни одного файла, соответствующего шаблону, # то за имя файла принимается сам шаблон. # Чтобы избежать этого, используйте ключ nullglob # (shopt -s nullglob). # Спасибо S.C. done

echo; echo

for file in [jx]* do rm -f $file # Удаление файлов, начинающихся с "j" или "x" в $PWD. echo "Удален файл \"$file\"". done

echo

exit 0

Если [список] в цикле for не задан, то в качестве оного используется переменная $@ -- список аргументов командной строки. Оень остроумно эта особенность проиллюстрирована в Пример A-18.

Пример 10-5. Цикл for без списка аргументов

#!/bin/bash

# Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты.

for a do echo -n "$a " done

# Список аргументов не задан, поэтому цикл работает с переменной '$@' #+ (список аргументов командной строки, включая пробельные символы).



echo

exit 0

При создании списка аргументов, в цикле for допускается пользоваться подстановкой команд. См. Пример 12-39, Пример 10-10 и Пример 12-33.

Пример 10-6. Создание списка аргументов в цикле for с помощью операции подстановки команд

#!/bin/bash # уЩЫЬ for гЯ [гаЩгЫЯЭ], гЯкФСЮЮйЭ г аЯЭЯниР аЯФгдСЮЯзЫЩ ЫЯЭСЮФ.

NUMBERS="9 7 3 8 37.53"

for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53 do echo -n "$number " done

echo exit 0

Более сложный пример использования подстановки команд при создании списка аргументов цикла.