Пример 14-2. Запись результатов выполнения цикла в переменную
#!/bin/bash # csubloop.sh: Запись результатов выполнения цикла в переменнуюvariable1=`for i in 1 2 3 4 5 do echo -n "$i" # Здесь 'echo' -- это ключевой момент done`
echo "variable1 = $variable1" # variable1 = 12345
i=0 variable2=`while [ "$i" -lt 10 ] do echo -n "$i" # Опять же, команда 'echo' просто необходима. let "i += 1" # Увеличение на 1. done`
echo "variable2 = $variable2" # variable2 = 0123456789
exit 0
Подстановка команд позволяет существенно расширить набор инструментальных средств, которыми располагает Bash. Суть состоит в том, чтобы написать программу или сценарий, которая выводит результаты своей работы на stdout (как это делает подавляющее большинство утилит в UNIX) и записать вывод от программы в переменную. #include <stdio.h> /* Программа на C "Hello, world." */ int main() { printf( "Hello, world." ); return (0); } bash$ gcc -o hello hello.c #!/bin/bash # hello.sh greeting=`./hello` echo $greeting bash$ sh hello.sh Hello, world. |
Альтернативой обратным одиночным кавычкам, используемым для подстановки команд, можно считать такую форму записи: $(COMMAND). output=$(sed -n /"$1"/p $file) # К примеру из "grp.sh". # Запись в переменную содержимого текстового файла. File_contents1=$(cat $file1) File_contents2=$(<$file2) # Bash допускает и такую запись. |
Пример 10-7
Пример 10-26
Пример 9-26
Пример 12-2
Пример 12-15
Пример 12-12
Пример 12-39
Пример 10-13
Пример 10-10
Пример 12-24
Пример 16-7
Пример A-19
Пример 27-1
Пример 12-32
Пример 12-33
Пример 12-34
Глава 15. Арифметические подстановки
Арифметические подстановки -- это мощный инструмент, предназначенный для выполнения арифметических операций в сценариях.
Перевод строки в числовое выражение производится с помощью обратных одиночных кавычек, двойных круглых скобок или предложения let.
Вариации
Арифметические подстановки в обратных одиночных кавычках (часто используются совместно с командой expr)
z=`expr $z + 3` # Команда 'expr' вычисляет значение выражения.
Арифметические подстановки в двойных круглых скобках, и предложение let
В арифметических подстановках, обратные одиночные кавычки могут быть заменены на двойные круглые скобки $((...)) или очень удобной конструкцией, с применением предложения let.
z=$(($z+3)) # $((EXPRESSION)) -- это подстановка арифметического выражения. # Не путайте с #+ подстановкой команд.
let z=z+3 let "z += 3" # Кавычки позволяют вставляьб пробелы и специальные операторы. # Оператор 'let' вычисляет арифметическое выражение, #+ это не подстановка арифметического выражения.
Все вышеприведенные примеры эквивалентны. Вы можете использовать любую из этих форм записи "по своему вкусу".
Примеры арифметических подстановок в сценариях:
Пример 12-6
Пример 10-14
Пример 25-1
Пример 25-6
Пример A-19
Глава 16. Перенаправление ввода/вывода
В системе по-умолчанию всегда открыты три "файла" -- stdin (клавиатура), stdout (экран) и stderr (вывод сообщений об ошибках на экран). Эти, и любые другие открытые файлы, могут быть перенапрвлены. В данном случае, термин "перенаправление" означает получить вывод из файла, команды, программы, сценария или даже отдельного блока в сценарии (см. Пример 3-1 и Пример 3-2) и передать его на вход в другой файл, команду, программу или сценарий.
С каждым открытым файлом связан дескриптор файла. [44] Дескрипторы файлов stdin, stdout и stderr -- 0, 1 и 2, соответственно. При открытии дополнительных файлов, дескрипторы с 3 по 9 остаются незанятыми. Иногда дополнительные дескрипторы могут сослужить неплохую службу, временно сохраняя в себе ссылку на stdin, stdout или stderr. [45] Это упрощает возврат дескрипторов в нормальное состояние после сложных манипуляций с перенаправлением и перестановками (см.
Пример 16-1).
COMMAND_OUTPUT > # Перенаправление stdout (вывода) в файл. # Если файл отсутствовал, то он создется, иначе -- перезаписывается.
ls -lR > dir-tree.list # Создает файл, содержащий список дерева каталогов.
: > filename # Операция > усекает файл "filename" до нулевой длины. # Если до выполнения операции файла не существовало, # то создается новый файл с нулевой длиной (тот же эффект дает команда 'touch'). # Символ : выступает здесь в роли местозаполнителя, не выводя ничего.
> filename # Операция > усекает файл "filename" до нулевой длины. # Если до выполнения операции файла не существовало, # то создается новый файл с нулевой длиной (тот же эффект дает команда 'touch'). # (тот же результат, что и выше -- ": >", но этот вариант неработоспособен # в некоторых командных оболочках.)
COMMAND_OUTPUT >> # Перенаправление stdout (вывода) в файл. # Создает новый файл, если он отсутствовал, иначе -- дописывает в конец файла.
# Однострочные команды перенаправления # (затрагивают только ту строку, в которой они встречаются): # --------------------------------------------------------------------
1>filename # Перенаправление вывода (stdout) в файл "filename". 1>>filename # Перенаправление вывода (stdout) в файл "filename", файл открывается в режиме добавления. 2>filename # Перенаправление stderr в файл "filename". 2>>filename # Перенаправление stderr в файл "filename", файл открывается в режиме добавления. &>filename # Перенаправление stdout и stderr в файл "filename".
#============================================================================== # Перенаправление stdout, только для одной строки. LOGFILE=script.log
echo "Эта строка будет записана в файл \"$LOGFILE\"." 1>$LOGFILE echo "Эта строка будет добавлена в конец файла \"$LOGFILE\"." 1>>$LOGFILE echo "Эта строка тоже будет добавлена в конец файла \"$LOGFILE\"." 1>>$LOGFILE echo "Эта строка будет выведена на экран и не попадет в файл \"$LOGFILE\"." # После каждой строки, сделанное перенаправление автоматически "сбрасывается".
# Перенаправление stderr, только для одной строки. ERRORFILE=script.errors
bad_command1 2>$ERRORFILE # Сообщение об ошибке запишется в $ERRORFILE. bad_command2 2>>$ERRORFILE # Сообщение об ошибке добавится в конец $ERRORFILE. bad_command3 # Сообщение об ошибке будет выведено на stderr, #+ и не попадет в $ERRORFILE. # После каждой строки, сделанное перенаправление также автоматически "сбрасывается". #==============================================================================
2>&1 # Перенаправляется stderr на stdout. # Сообщения об ошибках передаются туда же, куда и стандартный вывод.
i>&j # Перенаправляется файл с дескриптором i в j. # Вывод в файл с дескриптором i передается в файл с дескриптором j.
>&j # Перенаправляется файл с дескриптором 1 (stdout) в файл с дескриптором j. # Вывод на stdout передается в файл с дескриптором j.
0< FILENAME < FILENAME # Ввод из файла. # Парная команде ">", часто встречается в комбинации с ней. # # grep search-word <filename
[j]<>filename # Файл "filename" открывается на чтение и запись, и связывается с дескриптором "j". # Если "filename" отсутствует, то он создается. # Если дескриптор "j" не указан, то, по-умолчанию, бередся дескриптор 0, stdin. # # Как одно из применений этого -- запись в конкретную позицию в файле. echo 1234567890 > File # Записать строку в файл "File". exec 3<> File # Открыть "File" и связать с дескриптором 3. read -n 4 <&3 # Прочитать 4 символа. echo -n . >&3 # Записать символ точки. exec 3>&- # Закрыть дескриптор 3. cat File # ==> 1234.67890 # Произвольный доступ, да и только!
| # Конвейер (канал). # Универсальное средство для объединения команд в одну цепочку. # Похоже на ">", но на самом деле -- более обширная. # Используется для объединения команд, сценариев, файлов и программ в одну цепочку (конвейер). cat *.txt | sort | uniq > result-file # Содержимое всех файлов .txt сортируется, удаляются повторяющиеся строки, # результат сохраняется в файле "result-file".
Операции перенаправления и/или конвейеры могут комбинироваться в одной командной строке.
command < input-file > output-file
command1 | command2 | command3 > output-file
См. Пример 12-23 и Пример A-17.
Допускается перенаправление нескольких потоков в один файл.
ls -yz >> command.log 2>&1 # Сообщение о неверной опции "yz" в команде "ls" будет записано в файл "command.log". # Поскольку stderr перенаправлен в файл.
Закрытие дескрипторов файлов
n<&-
Закрыть дескриптор входного файла n.
0<&-, <&-
Закрыть stdin.
n>&-
Закрыть дескриптор выходного файла n.
1>&-, >&-
Закрыть stdout.
Дочерние процессы наследуют дескрипторы открытых файлов. По этой причине и работают конвейеры. Чтобы предотвратить наследование дескрипторов -- закройте их перед запуском дочернего процесса.
# В конвейер передается только stderr.
exec 3>&1 # Сохранить текущее "состояние" stdout. ls -l 2>&1 >&3 3>&- | grep bad 3>&- # Закрыть дескр. 3 для 'grep' (но не для 'ls'). # ^^^^ ^^^^ exec 3>&- # Теперь закрыть его для оставшейся части сценария.
# Спасибо S.C.
Дополнительные сведения о перенаправлении ввода/вывода вы найдете в Приложение D.
16.1. С помощью команды exec
Команда exec <filename перенаправляет ввод со stdin на файл. С этого момента весь ввод, вместо stdin (обычно это клавиатура), будет производиться из этого файла. Это дает возможность читать содержимое файла, строку за строкой, и анализировать каждую введенную строку с помощью sed и/или awk.
Пример 16-1. Перенаправление stdin с помощью exec
#!/bin/bash # Перенаправление stdin с помощью 'exec'.
exec 6<&0 # Связать дескр. #6 со стандартным вводом (stdin). # Сохраняя stdin.
exec < data-file # stdin заменяется файлом "data-file"
read a1 # Читается первая строка из "data-file". read a2 # Читается вторая строка из "data-file."
echo echo " Следующие строки были прочитаны из файла." echo "-----------------------------------------" echo $a1 echo $a2
echo; echo; echo
exec 0<&6 6<&- # Восстанавливается stdin из дескр. #6, где он был предварительно сохранен, #+ и дескр. #6 закрывается ( 6<&- ) освобождая его для других процессов. # # <&6 6<&- дает тот же результат.
echo -n "Введите строку " read b1 # Теперь функция "read", как и следовало ожидать, принимает данные с обычного stdin. echo "Строка, принятая со stdin." echo "--------------------------" echo "b1 = $b1"
echo
exit 0
Аналогично, конструкция exec >filename перенаправляет вывод на stdout в заданный файл. После этого, весь вывод от команд, который обычно направляется на stdout, теперь выводится в этот файл.
Пример 16-2. Перенаправление stdout с помощью exec
#!/bin/bash # reassign-stdout.sh
LOGFILE=logfile.txt
exec 6>&1 # Связать дескр. #6 со stdout. # Сохраняя stdout.
exec > $LOGFILE # stdout замещается файлом "logfile.txt".
# ----------------------------------------------------------- # # Весь вывод от команд, в данном блоке, записывается в файл $LOGFILE.
echo -n "Logfile: " date echo "-------------------------------------" echo
echo "Вывод команды \"ls -al\"" echo ls -al echo; echo echo "Вывод команды \"df\"" echo df
# ----------------------------------------------------------- #
exec 1>&6 6>&- # Восстановить stdout и закрыть дескр. #6.
echo echo "== stdout восстановлено в значение по-умолчанию == " echo ls -al echo
exit 0
Пример 16-3. Одновременное перенаправление устройств, stdin и stdout, с помощью команды exec
#!/bin/bash # upperconv.sh # Преобразование символов во входном файле в верхний регистр.
E_FILE_ACCESS=70 E_WRONG_ARGS=71
if [ ! -r "$1" ] # Файл доступен для чтения? then echo "Невозможно прочитать из заданного файла!" echo "Порядок использования: $0 input-file output-file" exit $E_FILE_ACCESS fi # В случае, если входной файл ($1) не задан #+ код завершения будет этим же.
if [ -z "$2" ] then echo "Необходимо задать выходной файл." echo "Порядок использования: $0 input-file output-file" exit $E_WRONG_ARGS fi
exec 4<&0 exec < $1 # Назначить ввод из входного файла.
exec 7>&1 exec > $2 # Назначить вывод в выходной файл. # Предполагается, что выходной файл доступен для записи # (добавить проверку?).
# ----------------------------------------------- cat - | tr a-z A-Z # Перевод в верхний регистр # ^^^^^ # Чтение со stdin. # ^^^^^^^^^^ # Запись в stdout. # Однако, и stdin и stdout были перенаправлены. # -----------------------------------------------
exec 1>&7 7>&- # Восстановить stdout. exec 0<&4 4<&- # Восстановить stdin.
# После восстановления, следующая строка выводится на stdout, чего и следовало ожидать. echo "Символы из \"$1\" преобразованы в верхний регистр, результат записан в \"$2\"."
exit 0
16.2. Перенаправление для блоков кода
Блоки кода, такие как циклы while, until и for, условный оператор if/then, так же могут смешиваться с перенаправлением stdin. Даже функции могут использовать эту форму перенаправления (см. Пример 22-7). Оператор перенаправления <, в таких случаях, ставится в конце блока.
Пример 16-4. Перенаправление в цикл while
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если имя файла не задано. else Filename=$1 fi # Конструкцию проверки выше, можно заменить следующей строкой (подстановка параметров): #+ Filename=${1:-names.data}
count=0
echo
while [ "$name" != Smith ] # Почему переменная $name взята в кавычки? do read name # Чтение из $Filename, не со stdin. echo $name let "count += 1" done <"$Filename" # Перенаправление на ввод из файла $Filename. # ^^^^^^^^^^^^
echo; echo "Имен прочитано: $count"; echo
# Обратите внимание: в некоторых старых командных интерпретаторах, #+ перенаправление в циклы приводит к запуску цикла в субоболочке (subshell). # Таким образом, переменная $count, по окончании цикла, будет содержать 0, # значение, записанное в нее до входа в цикл. # Bash и ksh стремятся избежать запуска субоболочки (subshell), если это возможно, #+ так что этот сценарий, в этих оболочках, работает корректно. # # Спасибо Heiner Steven за это примечание.
exit 0
Пример 16-5. Альтернативная форма перенаправления в цикле while
#!/bin/bash
# Это альтернативный вариант предыдущего сценария.
# Предложил: by Heiner Steven #+ для случаев, когда циклы с перенаправлением #+ запускаются в субоболочке, из-за чего переменные, устанавливаемые в цикле, #+ не сохраняют свои значения по завершении цикла.
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если имя файла не задано. else Filename=$1 fi
exec 3<&0 # Сохранить stdin в дескр. 3. exec 0<"$Filename" # Перенаправить stdin.
count=0 echo
while [ "$name" != Smith ] do read name # Прочитать с перенаправленного stdin ($Filename). echo $name let "count += 1" done <"$Filename" # Цикл читает из файла $Filename. # ^^^^^^^^^^^^
exec 0<&3 # Восстановить stdin. exec 3<&- # Закрыть временный дескриптор 3.
echo; echo "Имен прочитано: $count"; echo
exit 0
Пример 16-6. Перенаправление в цикл until
#!/bin/bash # То же самое, что и в предыдущем примере, только для цикла "until".
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если файл не задан. else Filename=$1 fi
# while [ "$name" != Smith ] until [ "$name" = Smith ] # Проверка != изменена на =. do read name # Чтение из $Filename, не со stdin. echo $name done <"$Filename" # Перенаправление на ввод из файла $Filename. # ^^^^^^^^^^^^
# Результаты получаются теми же, что и в случае с циклом "while", в предыдущем примере.
exit 0
Пример 16-7. Перенаправление в цикл for
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если файл не задан. else Filename=$1 fi
line_count=`wc $Filename | awk '{ print $1 }'` # Число строк в файле. # # Слишком запутано, тем не менее показывает #+ возможность перенаправления stdin внутри цикла "for"... #+ если вы достаточно умны. # # Более короткий вариант line_count=$(wc < "$Filename")
for name in `seq $line_count` # "seq" выводит последовательность чисел. # while [ "$name" != Smith ] -- более запутанно, чем в случае с циклом "while" -- do read name # Чтение из файла $Filename, не со stdin. echo $name if [ "$name" = Smith ] then break fi done <"$Filename" # Перенаправление на ввод из файла $Filename. # ^^^^^^^^^^^^
exit 0
Предыдущий пример можно модифицировать так, чтобы перенаправить вывод из цикла.
Пример 16-8. Перенаправление устройств (stdin и stdout) в цикле for
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если файл не задан. else Filename=$1 fi
Savefile=$Filename.new # Имя файла, в котором сохраняются результаты. FinalName=Jonah # Имя, на котором завершается чтение.
line_count=`wc $Filename | awk '{ print $1 }'` # Число строк в заданном файле.
for name in `seq $line_count` do read name echo "$name" if [ "$name" = "$FinalName" ] then break fi done < "$Filename" > "$Savefile" # Перенаправление на ввод из файла $Filename, # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ и сохранение результатов в файле.
exit 0
Пример 16-9. Перенаправление в конструкции if/then
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # По-умолчанию, если файл не задан. else Filename=$1 fi
TRUE=1
if [ "$TRUE" ] # конструкции "if true" и "if :" тоже вполне допустимы. then read name echo $name fi <"$Filename" # ^^^^^^^^^^^^
# Читает только первую строку из файла.
exit 0
Пример 16-10. Файл с именами "names.data", для примеров выше
Aristotle Belisarius Capablanca Euler Goethe Hamurabi Jonah Laplace Maroczy Purcell Schmidt Semmelweiss Smith Turing Venn Wilson Znosko-Borowski
# Это файл с именами для примеров #+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh".
Перенаправление stdout для блока кода, может использоваться для сохранения результатов работы этого блока в файл.
См. Пример 3-2.
Встроенный документ -- это особая форма перенаправления для блоков кода.
16.3. Область применения
Как один из вариантов грамотного применения перенаправления ввода/вывода, можно назвать разбор и "сшивание" вывода от команд (см. Пример 11-6). Это позволяет создавать файлы отчетов и журналов регистрации событий.
Пример 16-11. Регистрация событий
#!/bin/bash # logevents.sh, автор: Stephane Chazelas.
# Регистрация событий в файле. # Сценарий должен запускаться с привилегиями root (что бы иметь право на запись в /var/log).
ROOT_UID=0 # Привилегии root имеет только пользователь с $UID = 0. E_NOTROOT=67 # Код завершения, если не root.
if [ "$UID" -ne "$ROOT_UID" ] then echo "Сценарий должен запускаться с привилегиями root." exit $E_NOTROOT fi
FD_DEBUG1=3 FD_DEBUG2=4 FD_DEBUG3=5
# Раскомментарьте одну из двух строк, ниже, для активизации сценария. # LOG_EVENTS=1 # LOG_VARS=1
log() # Запись даты и времени в файл. { echo "$(date) $*" >&7 # Добавляет в конец файла. # См. ниже. }
case $LOG_LEVEL in 1) exec 3>&2 4> /dev/null 5> /dev/null;; 2) exec 3>&2 4>&2 5> /dev/null;; 3) exec 3>&2 4>&2 5>&2;; *) exec 3> /dev/null 4> /dev/null 5> /dev/null;; esac
FD_LOGVARS=6 if [[ $LOG_VARS ]] then exec 6>> /var/log/vars.log else exec 6> /dev/null # Подавить вывод. fi
FD_LOGEVENTS=7 if [[ $LOG_EVENTS ]] then # then exec 7 >(exec gawk '{print strftime(), $0}' >> /var/log/event.log) # Строка, выше, не работает в Bash, версии 2.04. exec 7>> /var/log/event.log # Добавление в конец "event.log". log # Записать дату и время. else exec 7> /dev/null # Подавить вывод. fi
echo "DEBUG3: beginning" >&${FD_DEBUG3}
ls -l >&5 2>&4 # command1 >&5 2>&4
echo "Done" # command2
echo "sending mail" >&${FD_LOGEVENTS} # Написать "sending mail" в дескр. #7.
exit 0
Глава 17. Встроенные документы
Встроенный документ ( here document) является специальной формой перенаправления ввода/вывода, которая позволяет передать список команд интерактивной программе или команде, например ftp, telnet или ex. Конец встроенного документа выделяется "строкой-ограничителем", которая задается с помощью специальной последовательности символов <<. Эта последовательность -- есть перенаправление вывода из файла в программу, напоминает конструкцию interactive-program < command-file, где command-file содержит строки:
command #1 command #2 ...
Сценарий, использующий "встроенный документ" для тех же целей, может выглядеть примерно так:
#!/bin/bash interactive-program <<LimitString command #1 command #2 ... LimitString
В качестве строки-ограничителя должна выбираться такая последовательность символов, которая не будет встречаться в теле "встроенного документа".
Обратите внимание: использование встроенных документов может иногда с успехом применяться и при работе с неинтерактивными командами и утилитами.
Пример 17-1. dummyfile: Создание 2-х строчного файла-заготовки
#!/bin/bash
# Неинтерактивное редактирование файла с помощью 'vi'. # Эмуляция 'sed'.
E_BADARGS=65
if [ -z "$1" ] then echo "Порядок использования: `basename $0` filename" exit $E_BADARGS fi
TARGETFILE=$1
# Вставить 2 строки в файл и сохранить. #--------Начало встроенного документа-----------# vi $TARGETFILE <<x23LimitStringx23 i Это строка 1. Это строка 2. ^[ ZZ x23LimitStringx23 #----------Конец встроенного документа-----------#
# Обратите внимание: ^[, выше -- это escape-символ #+ Control-V <Esc>.
# Bram Moolenaar указывает, что этот скрипт может не работать с 'vim', #+ из-за возможных проблем взаимодействия с терминалом.
exit 0
Этот сценарий, с тем же эффектом, мог бы быть реализован, основываясь не на vi, а на ex. Встроенные документы, содержащие команды для ex, стали настолько обычным делом, что их уже смело можно вынести в отдельную категорию -- ex-сценарии.
Пример 17-2. broadcast: Передача сообщения всем, работающим в системе, пользователям
#!/bin/bash
wall << zzz23EndOfMessagezzz23 Пошлите, по электронной почте, ваш заказ на пиццу, системному администратору. (Добавьте дополнительный доллар, если вы желаете положить на пиццу анчоусы или грибы.) # Внимание: строки комментария тоже будут переданы команде 'wall' как часть текста. zzz23EndOfMessagezzz23
# Возможно, более эффективно это может быть сделано так: # wall <message-file # Однако, встроенный документ помогает сэкономить ваши силы и время.
exit 0
Пример 17-3. Вывод многострочных сообщений с помощью cat
#!/bin/bash
# Команда 'echo' прекрасно справляется с выводом однострочных сообщений, # но иногда необходимо вывести несколько строк. # Команда 'cat' и встроенный документ помогут вам в этом.
cat <<End-of-message ------------------------------------- Это первая строка сообщения. Это вторая строка сообщения. Это третья строка сообщения. Это четвертая строка сообщения. Это последняя строка сообщения. ------------------------------------- End-of-message
exit 0
#-------------------------------------------- # Команда "exit 0", выше, не позволить исполнить нижележащие строки.
# S.C. отмечает, что следующий код работает точно так же. echo "------------------------------------- Это первая строка сообщения. Это вторая строка сообщения. Это третья строка сообщения. Это четвертая строка сообщения. Это последняя строка сообщения. -------------------------------------" # Однако, в этом случае, двойные кавычки в теле сообщения, должны экранироваться.
Если строка-ограничитель встроенного документа начинается с символа - (<<-LimitString), то это приводит к подавлению вывода символов табуляции (но не пробелов). Это может оказаться полезным при форматировании текста сценария для большей удобочитаемости.
Пример 17-4. Вывод многострочных сообщений с подавлением символов табуляции
#!/bin/bash # То же, что и предыдущий сценарий, но...
# Символ "-", начинающий строку-ограничитель встроенного документа: <<- # подавляет вывод символов табуляции, которые могут встречаться в теле документа, # но не пробелов.
cat <<-ENDOFMESSAGE Это первая строка сообщения. Это вторая строка сообщения. Это третья строка сообщения. Это четвертая строка сообщения. Это последняя строка сообщения. ENDOFMESSAGE # Текст, выводимый сценарием, будет смещен влево. # Ведущие символы табуляции не будут выводиться.
# Вышеприведенные 5 строк текста "сообщения" начинаются с табуляции, а не с пробелов.
exit 0
Встроенные документы поддерживают подстановку команд и параметров. Что позволяет передавать различные параметры в тело встроенного документа.
Пример 17-5. Встроенные документы и подстановка параметров
#!/bin/bash # Вывод встроенного документа командой 'cat', с использованием подстановки параметров.
# Попробуйте запустить сценарий без аргументов, ./scriptname # Попробуйте запустить сценарий с одним аргументом, ./scriptname Mortimer # Попробуйте запустить сценарий с одним аргументом, из двух слов, в кавычках, # ./scriptname "Mortimer Jones"
CMDLINEPARAM=1 # Минимальное число аргументов командной строки.
if [ $# -ge $CMDLINEPARAM ] then NAME=$1 # Если аргументов больше одного, # то рассматривается только первый. else NAME="John Doe" # По-умолчанию, если сценарий запущен без аргументов. fi
RESPONDENT="автора этого сценария"
cat <<Endofmessage
Привет, $NAME! Примите поздравления от $RESPONDENT.
# Этот комментарий тоже выводится (почему?).
Endofmessage
# Обратите внимание на то, что пустые строки тоже выводятся.
exit 0
Заключая строку-ограничитель в кавычки или экранируя ее, можно запретить подстановку параметров в теле встроенного документа.
Пример 17-6. Отключение подстановки параметров
#!/bin/bash # Вывод встроенного документа командой 'cat', с запретом подстановки параметров.
NAME="John Doe" RESPONDENT="автора этого сценария"
cat <<'Endofmessage'
Привет, $NAME. Примите поздравления от $RESPONDENT.
Endofmessage
# Подстановка параметров не производится, если строка ограничитель # заключена в кавычки или экранирована. # Тот же эффект дают: # cat <<"Endofmessage" # cat <<\Endofmessage
exit 0
Еще один пример сценария, содержащего встроенный документ и подстановку параметров в его теле.
Пример 17-7. Передача пары файлов во входящий каталог на "Sunsite"
#!/bin/bash # upload.sh
# Передача пары файлов (Filename.lsm, Filename.tar.gz) # на Sunsite (ibiblio.org).
E_ARGERROR=65
if [ -z "$1" ] then echo "Порядок использования: `basename $0` filename" exit $E_ARGERROR fi
Filename=`basename $1` # Отсечь имя файла от пути к нему.
Server="ibiblio.org" Directory="/incoming/Linux" # Вообще, эти строки должны бы не "зашиваться" жестко в сценарий, # а приниматься в виде аргумента из командной строки.
Password="your.e-mail.address" # Измените на свой.
ftp -n $Server <<End-Of-Session # Ключ -n запрещает автоматическую регистрацию (auto-logon)
user anonymous "$Password" binary bell # "Звякнуть" после передачи каждого файла cd $Directory put "$Filename.lsm" put "$Filename.tar.gz" bye End-Of-Session
exit 0
Встроенные документы могут передаваться на вход функции, находящейся в том же сценарии.
Пример 17-8. Встроенные документы и функции
#!/bin/bash # here-function.sh
GetPersonalData () { read firstname read lastname read address read city read state read zipcode } # Это немного напоминает интерактивную функцию, но...
# Передать ввод в функцию. GetPersonalData <<RECORD001 Bozo Bozeman 2726 Nondescript Dr. Baltimore MD 21226 RECORD001
echo echo "$firstname $lastname" echo "$address" echo "$city, $state $zipcode" echo
exit 0
Встроенный документ можно передать "пустой команде" :. Такая конструкция, фактически, создает "анонимный" встроенный документ.
Пример 17-9. "Анонимный" Встроенный Документ
#!/bin/bash
: <<TESTVARIABLES ${HOSTNAME?}${USER?}${MAIL?} # Если одна из переменных не определена, то выводится сообщение об ошибке. TESTVARIABLES
exit 0
Подобную технику можно использовать для создания "блочных комментариев". |
#!/bin/bash # commentblock.sh
: << COMMENTBLOCK echo "Эта строка не будет выведена." Эта строка комментария не начинается с символа "#". Это еще одна строка комментария, которая начинается не с символа "#".
&*@!!++= Эта строка не вызовет ошибки, поскольку Bash проигнорирует ее. COMMENTBLOCK
echo "Код завершения \"COMMENTBLOCK\" = $?." # 0 # Показывает, что ошибок не возникало.
# Такая методика создания блочных комментариев #+ может использоваться для комментирования блоков кода во время отладки. # Это экономит силы и время, т.к. не нужно втавлять символ "#" в начале каждой строки, #+ а затем удалять их.
: << DEBUGXXX for file in * do cat "$file" done DEBUGXXX
exit 0
Еще одно остроумное применение встроенных документов -- встроенная справка к сценарию. |
#!/bin/bash # self-document.sh: сценарий со встроенной справкой # Модификация сценария "colm.sh".
DOC_REQUEST=70
if [ "$1" = "-h" -o "$1" = "--help" ] # Request help. then echo; echo "Порядок использования: $0 [directory-name]"; echo sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATION/p' "$0" | sed -e '/DOCUMENTATIONXX/d'; exit $DOC_REQUEST; fi
: << DOCUMENTATIONXX Сценарий выводит сведения о заданном каталоге в виде таблице. ------------------------------------------------------------- Сценарию необходимо передать имя каталога. Если каталог не указан или он недоступен для чтения, то выводятся сведения о текущем каталоге.
DOCUMENTATIONXX
if [ -z "$1" -o ! -r "$1" ] then directory=.
else directory="$1" fi
echo "Сведения о каталоге "$directory":"; echo (printf " PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \ ; ls -l "$directory" | sed 1d) | column -t
exit 0
Для встроенных документов, во время исполнения, создаются временные файлы, но эти файлы удаляются после открытия и недоступны для других процессов. bash$ bash -c 'lsof -a -p $$ -d0' << EOF > EOF lsof 1213 bozo 0r REG 3,5 0 30386 /tmp/t1213-0-sh (deleted) |
Некоторые утилиты не могут работать внутри встроенных документов. |
Часть 4. Материал повышенной сложности
Итак, мы вплотную подошли к изучению очень сложных и необычных аспектов написания сценариев. В этой части мы попытаемся "сбросить покров тайны" и заглянуть за пределы известного нам мира (представьте себе путешествие по территории, не отмеченной на карте).
Содержание
18. Регулярные выражения
18.1. Краткое введение в регулярные выражения
18.2. Globbing -- Подстановка имен файлов
19. Подоболочки, или Subshells
20. Ограниченный режим командной оболочки
21. Подстановка процессов
22. Функции
22.1. Сложные функции и сложности с функциями
22.2. Локальные переменные
22.2.1. Локальные переменные делают возможной рекурсию.
23. Псевдонимы
24. Списки команд
25. Массивы
26. Файлы
27. /dev и /proc
27.1. /dev
27.2. /proc
28. /dev/zero и /dev/null
29. Отладка сценариев
30. Необязательные параметры (ключи)
31. Широко распространенные ошибки
32. Стиль программирования
32.1. Неофициальные рекомендации по оформлению сценариев
33. Разное
33.1. Интерактивный и неинтерактивный режим работы
33.2. Сценарии-обертки
33.3. Операции сравнения: Альтернативные решения
33.4. Рекурсия
33.5. "Цветные" сценарии
33.6. Оптимизация
33.7. Разные советы
33.8. Проблемы безопасности
33.9. Проблемы переносимости
33.10. Сценарии командной оболочки под Windows
34. Bash, версия 2
Глава 18. Регулярные выражения
Для того, чтобы полностью реализовать потенциал командной оболочки, вам придется овладеть Регулярными Выражениями. Многие команды и утилиты, обычно используемые в сценариях, такие как grep, expr, sed и awk, используют Регулярные Выражения.
18.1. Краткое введение в регулярные выражения
Выражение -- это строка символов. Символы, которые имеют особое назначение, называются метасимволами. Так, например, кавычки могут выделять прямую речь, т.е. быть метасимволами для строки, заключенной в эти кавычки. Регулярные выражения -- это набор символов и/или метасимволов, которые наделены особыми свойствами. [46]
Основное назначение регулярных выражений -- это поиск текста по шаблону и работа со строками.
Звездочка -- * -- означает любое количество символов в строке, предшествующих "звездочке", в том числе и нулевое число символов.
Выражение "1133*" -- означает 11 + один или более символов "3" + любые другие символы: 113, 1133, 113312, и так далее.
Точка -- . -- означает не менее одного любого символа, за исключением символа перевода строки (\n). [47]
Выражение "13." будет означать 13 + по меньшей мере один любой символ (включая пробел): 1133, 11333, но не 13 (отсутствуют дополнительные символы).
Символ -- ^ -- означает начало строки, но иногда, в зависимости от контекста, означает отрицание в регулярных выражениях.
Знак доллара -- $ -- в конце регулярного выражения соответствует концу строки.
Выражение "^$" соответствует пустой строке.
Символы ^ и $ иногда еще называют якорями, поскольку они означают, или закрепляют, позицию в регулярных выражениях. |
Выражение "[xyz]" -- соответствует одному из символов x, y или z.
Выражение "[c-n]" соответствует одному из символов в диапазоне от c до n, включительно.
Выражение "[B-Pk-y]" соответствует одному из символов в диапазоне от B до P или в диапазоне от k до y, включительно.
Выражение "[a-z0-9]" соответствует одному из символов латиницы в нижнем регистре или цифре.
Выражение "[^b-d]" соответствует любому символу, кроме символов из диапазона от b до d, включительно. В данном случае, метасимвол ^ означает отрицание.
Объединяя квадратные скобки в одну последовательность, можно задать шаблон искомого слова. Так, выражение "[Yy][Ee][Ss]" соответствует словам yes, Yes, YES, yEs и так далее. Выражение "[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]" определяет шаблон для поиска любого номера карточки социального страхования (для США).
Обратный слэш -- \ -- служит для экранирования специальных символов, это означает, что экранированные символы должны интерпретироваться буквально, т.е. как простые символы.
Комбинация "\$" указывает на то, что символ "$" трактуется как обычный символ, а не как признак конца строки в регулярных выражениях. Аналогично, комбинация "\\" соответствует простому символу "\".
Экранированные "угловые скобки" -- \<...\> -- отмечают границы слова.
Угловые скобки должны экранироваться, иначе они будут интерпретироваться как простые символы.
Выражение "\<the\>" соответствует слову "the", и не соответствует словам "them", "there", "other" и т.п.
bash$ cat textfile
This is line 1, of which there is only one instance. This is the only instance of line 2. This is line 3, another line. This is line 4.
bash$ grep 'the' textfile
This is line 1, of which there is only one instance. This is the only instance of line 2. This is line 3, another line.
bash$ grep '\<the\>' textfile
This is the only instance of line 2.
Дополнительные метасимволы. Использующиеся при работе с egrep, awk и Perl
Знак вопроса -- ? -- означает, что предыдущий символ или регулярное выражение встречается 0 или 1 раз. В основном используется для поиска одиночных символов.
Знак "плюс" -- + -- указывает на то, что предыдущий символ или выражение встречается 1 или более раз. Играет ту же роль, что и символ "звездочка" (*), за исключением случая нулевого количества вхождений.
# GNU версии sed и awk допускают использование "+", # но его необходимо экранировать.
echo a111b | sed -ne '/a1\+b/p' echo a111b | grep 'a1\+b' echo a111b | gawk '/a1+b/' # Все три варианта эквивалентны.
# Спасибо S.C.
Экранированные "фигурные скобки" -- \{ \} -- задают число вхождений предыдущего выражения.
Экранирование фигурных скобок -- обязательное условие, иначе они будут интерпретироваться как простые символы. Такой порядок использования, технически, не является частью основного набора правил построения регулярных выражений.
Выражение "[0-9]\{5\}" -- в точности соответствует подстроке из пяти десятичных цифр (символов из диапазона от 0 до 9, включительно).
В "классической" (не совместимой с POSIX) версии awk, фигурные скобки не могут быть использованы. Однако, в gawk предусмотрен ключ --re-interval, который позволяет использовать (неэкранированные) фигурные скобки. bash$ echo 2222 | gawk --re-interval '/2{3}/' 2222 Язык программирования Perl и некоторые версии egrep не требуют экранирования фигурных скобок. |
Вертикальная черта -- | -- выполняет роль логического оператора "ИЛИ" в регулярных выражениях и служит для задания набора альтернатив.
bash$ egrep 're(a|e)d' misc.txt
People who read seem to be better informed than those who do not.
The clarinet produces sound by the vibration of its reed.
Некоторые версии sed, ed и ex поддерживают экранированные версии регулярных выражений, описанных выше. |
[:class:]
Это альтернативный способ указания диапазона символов.
Класс [:alnum:] -- соответствует алфавитным символам и цифрам. Эквивалентно выражению [A-Za-z0-9].
Класс [:alpha:] -- соответствует символам алфавита. Эквивалентно выражению [A-Za-z].
Класс [:blank:] -- соответствует символу пробела или символу табуляции.
Класс [:cntrl:] -- соответствует управляющим символам (control characters).
Класс [:digit:] -- соответствует набору десятичных цифр. Эквивалентно выражению [0-9].
Класс [:graph:]
(печатаемые и псевдографические символы) -- соответствует набору символов из диапазона ASCII 33 - 126. Это то же самое, что и класс [:print:], за исключением символа пробела.
Класс [:lower:] -- соответствует набору алфавитных символов в нижнем регистре. Эквивалентно выражению [a-z].
Класс [:print:]
(печатаемые символы) -- соответствует набору символов из диапазона ASCII 32 - 126. По своему составу этот класс идентичен классу [:graph:], описанному выше, за исключением того, что в этом классе дополнительно присутствует символ пробела.
Класс [:space:] -- соответствует пробельным символам (пробел и горизонтальная табуляция).
Класс [:upper:] -- соответствует набору символов алфавита в верхнем регистре. Эквивалентно выражению [A-Z].
Класс [:xdigit:] -- соответствует набору шестнадцатиричных цифр. Эквивалентно выражению [0-9A-Fa-f].
Вообще, символьные классы POSIX требуют заключения в кавычки или двойные квадратные скобки ([[ ]]). bash$ grep [[:digit:]] test.file abc=723 Эти символьные классы могут использоваться, с некоторыми ограничениями, даже в операциях подстановки имен файлов (globbing). bash$ ls -l ?[[:digit:]][[:digit:]]? -rw-rw-r-- 1 bozo bozo 0 Aug 21 14:47 a33b Примеры использования символьных классов в сценариях вы найдете в Пример 12-14 и Пример 12-15. |
Sed, awk и Perl, используемые в сценариях в качестве фильтров, могут принимать регулярные выражения в качестве входных аргументов. См. Пример A-13 и Пример A-19.
Книга "Sed & Awk" (авторы Dougherty и Robbins) дает полное и ясное представление о регулярных выражениях (см. раздел Литература).
18.2. Globbing -- Подстановка имен файлов
Bash, сам по себе, не распознает регулярные выражения. Но в сценариях можно использовать команды и утилиты, такие как sed и awk, которые прекрасно справляются с обработкой регулярных выражений.
Фактически, Bash может выполнять подстановку имен файлов, этот процесс называется "globbing", но при этом не используется стандартный набор регулярных выражений. Вместо этого, при выполнении подстановки имен файлов, производится распознавание и интерпретация шаблонных символов. В число интерпретируемых шаблонов входят символы * и ?, списки символов в квадратных скобках и некоторые специальные символы (например ^, используемый для выполнения операции отрицания). Применение шаблонных символов имеет ряд важных ограничений. Например, если имена файлов начинаются с точки (например так: .bashrc), то они не будут соответствовать шаблону, содержащему символ *. [48] Аналогично, символ ? в операции подстановки имен файлов имеет иной смысл, нежели в регулярных выражениях.
bash$ ls -l
total 2 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1 -rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 t2.sh -rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 test1.txt
bash$ ls -l t?.sh
-rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 t2.sh
bash$ ls -l [ab]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1
bash$ ls -l [a-c]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1
bash$ ls -l [^ab]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1 -rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 t2.sh -rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 test1.txt
bash$ ls -l {b*,c*,*est*}
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1 -rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1 -rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 test1.txt
bash$ echo *
a.1 b.1 c.1 t2.sh test1.txt
bash$ echo t*
t2.sh test1.txt
Даже команда echo может интерпретировать шаблонные символы в именах файлов.
См. также Пример 10-4.
Глава 19. Подоболочки, или Subshells
Запуск сценария приводит к запуску дочернего командного интерпретатора. Который выполняет интерпретацию и исполнение списка команд, содержащихся в файле сценария, точно так же, как если бы они были введены из командной строки. Любой сценарий запускается как дочерний процесс родительской командной оболочки, той самой, которая выводит перед вами строку приглашения к вводу на консоли или в окне xterm.
Сценарий может, так же, запустить другой дочерний процесс, в своей подоболочке. Это позволяет сценариям распараллелить процесс обработки данных по нескольким задачам, исполняемым одновременно.
Список команд в круглых скобках
( command1; command2; command3; ... )
Список команд, в круглых скобках, исполняется в подоболочке.
Значения переменных, определенных в дочерней оболочке, не могут быть переданы родительской оболочке. Они недоступны родительскому процессу. Фактически, они ведут себя как локальные переменные. |
#!/bin/bash # subshell.sh
echo
outer_variable=Outer
( inner_variable=Inner echo "Дочерний процесс, \"inner_variable\" = $inner_variable" echo "Дочерний процесс, \"outer\" = $outer_variable" )
echo
if [ -z "$inner_variable" ] then echo "Переменная inner_variable не определена в родительской оболочке" else echo "Переменная inner_variable определена в родительской оболочке" fi
echo "Родительский процесс, \"inner_variable\" = $inner_variable" # Переменная $inner_variable не будет определена # потому, что переменные, определенные в дочернем процессе, # ведут себя как "локальные переменные".
echo
exit 0
См. также Пример 31-1.
+
Смена текущего каталога в дочернем процессе (подоболочке) не влечет за собой смену текущего каталога в родительской оболочке.
Пример 19-2. Личные настройки пользователей
#!/bin/bash # allprofs.sh: вывод личных настроек (profiles) всех пользователей
# Автор: Heiner Steven # С некоторыми изменениями, внесенными автором документа.
FILE=.bashrc # Файл настроек пользователя, #+ в оригинальном сценарии называется ".profile".
for home in `awk -F: '{print $6}' /etc/passwd` do [ -d "$home" ] || continue # Перейти к следующей итерации, если нет домашнего каталога. [ -r "$home" ] || continue # Перейти к следующей итерации, если не доступен для чтения. (cd $home; [ -e $FILE ] && less $FILE) done
# По завершении сценария -- нет теобходимости выполнять команду 'cd', чтобы вернуться в первоначальный каталог, #+ поскольку 'cd $home' выполняется в подоболочке.
exit 0
Подоболочка может использоваться для задания "специфического окружения" для группы команд.
COMMAND1 COMMAND2 COMMAND3 ( IFS=: PATH=/bin unset TERMINFO set -C shift 5 COMMAND4 COMMAND5 exit 3 # Выход только из подоболочки. ) # Изменение переменных окружения не коснется родительской оболочки. COMMAND6 COMMAND7
Как вариант использования подоболочки -- проверка переменных.
if (set -u; : $variable) 2> /dev/null then echo "Переменная определена." fi
# Можно сделать то же самое по другому: [[ ${variable-x} != x || ${variable-y} != y ]] # или [[ ${variable-x} != x$variable ]] # или [[ ${variable+x} = x ]])
Еще одно применение -- проверка файлов блокировки:
if (set -C; : > lock_file) 2> /dev/null then echo "Этот сценарий уже запущен другим пользователем." exit 65 fi
# Спасибо S.C.
Процессы в подоболочках могут исполняться параллельно. Это позволяет разбить сложную задачу на несколько простых подзадач, выполняющих параллельную обработку информации.
Пример 19-3. Запуск нескольких процессов в подоболочках
(cat list1 list2 list3 | sort | uniq > list123) & (cat list4 list5 list6 | sort | uniq > list456) & # Слияние и сортировка двух списков производится одновременно. # Запуск в фоне гарантирует параллельное исполнение. # # Тот же эффект дает # cat list1 list2 list3 | sort | uniq > list123 & # cat list4 list5 list6 | sort | uniq > list456 &
wait # Ожидание завершения работы подоболочек.
diff list123 list456
Перенаправление ввода/вывода в/из подоболочки производится оператором построения конвейера "|", например, ls -al | (command).
Блок команд, заключенный в фигурные скобки не приводит к запуску дочерней подоболочки. { command1; command2; command3; ... } |
Команды, запрещенные в ограниченном режиме командной оболочки
Запуск сценария или его части в ограниченном режиме, приводит к наложению ограничений на использование некоторых команд. Эта мера предназначена для ограничения привилегий пользователя, запустившего сценарий, и минимизации возможного ущерба системе, который может нанести сценарий.
В ограниченном режиме запрещена команда cd -- смена текщего каталога.
Запрещено изменять переменные окружения $PATH, $SHELL, $BASH_ENV и $ENV.
Заперщен доступ к переменной $SHELLOPTS.
Запрещено перенаправление вывода.
Запрещен вызов утилит, в названии которых присутствует хотя бы один символ "слэш" (/).
Запрещен вызов команды exec для запуска другого процесса.
Запрещен ряд других команд, которые могут использовать сценарий для выполнения непредусмотренных действий.
Запрещен выход из ограниченного режима.
Пример 20-1. Запуск сценария в ограниченном режиме
#!/bin/bash # Если sha-bang задать в таком виде: "#!/bin/bash -r" # то это приведет к включению ограниченного режима с момента запуска скрипта.
echo
echo "Смена каталога." cd /usr/local echo "Текущий каталог: `pwd`" echo "Переход в домашний каталог." cd echo "Текущий каталог: `pwd`" echo
# До сих пор сценарий исполнялся в обычном, неограниченном режиме.
set -r # set --restricted имеет тот же эффект. echo "==> Переход в ограниченный режим. <=="
echo echo
echo "Попытка сменить текущий каталог в ограниченном режиме." cd .. echo "Текущий каталог остался прежним: `pwd`"
echo echo
echo "\$SHELL = $SHELL" echo "Попытка смены командного интерпретатора в ограниченном режиме." SHELL="/bin/ash" echo echo "\$SHELL= $SHELL"
echo echo
echo "Попытка перенаправления вывода в ограниченном режиме." ls -l /usr/bin > bin.files ls -l bin.files # Попробуем найти файл, который пытались создать.
echo
exit 0
Глава 21. Подстановка процессов
Подстановка процессов -- это аналог подстановки команд. Операция подстановки команд записывает в переменную результат выполнения некоторой команды, например, dir_contents=`ls -al` или xref=$(grep word datafile). Операция подстановки процессов передает вывод одного процесса на ввод другого (другими словами, передает результат выполнения одной команды -- другой).
Шаблон подстановки команды
Внутри круглых скобок
>(command)
<(command)
Таким образом инициируется подстановка процессов. Здесь, для передачи результата работы процесса в круглых скобках, используются файлы /dev/fd/<n>. [49]
Между круглой скобкой и символом "<" или ">", не должно быть пробелов, в противном случае это вызовет сообщение об ошибке. |
/dev/fd/63
bash$ echo <(true)
/dev/fd/63
Bash создает канал с двумя файловыми дескрипторами, --fIn и fOut--. stdin команды true присоединяется к fOut (dup2(fOut, 0)), затем Bash передает /dev/fd/fIn в качестве аргумента команде echo. В системах, где отсутствуют файлы /dev/fd/<n>, Bash может использовать временные файлы. (Спасибо S.C.)
cat <(ls -l) # То же самое, что и ls -l | cat
sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin) # Список файлов в трех основных каталогах 'bin', отсортированный по именам файлов. # Обратите внимание: на вход 'sort' поданы три самостоятельные команды.
diff <(command1) <(command2) # Выдаст различия в выводе команд.
tar cf >(bzip2 -c > file.tar.bz2) $directory_name # Вызовет "tar cf /dev/fd/?? $directory_name" и затем "bzip2 -c > file.tar.bz2". # # Из- за особенностей, присущих некоторым системам, связанным с /dev/fd/<n>, # канал между командами не обязательно должен быть именованным. # # Это можно сделать и так. # bzip2 -c < pipe > file.tar.bz2& tar cf pipe $directory_name rm pipe # или exec 3>&1 tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&- exec 3>&-
# Спасибо S.C.
Ниже приводится еще один очень интересный пример использования подстановки процессов.
# Фрагмент сценария из дистрибутива SuSE:
while read des what mask iface; do # Некоторые команды ... done < <(route -n)
# Чтобы проверить это, попробуем вставить команду, выполняющую какие либо действия. while read des what mask iface; do echo $des $what $mask $iface done < <(route -n)
# Вывод на экран: # Kernel IP routing table # Destination Gateway Genmask Flags Metric Ref Use Iface # 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
# Как указывает S.C. -- более простой для понимания эквивалент: route -n | while read des what mask iface; do # Переменные берут значения с устройства вывода конвейера (канала). echo $des $what $mask $iface done # На экран выводится то же самое, что и выше. # Однако, Ulrich Gayer отметил, что ... #+ этот вариант запускает цикл while в подоболочке, #+ и поэтому переменные не видны за пределами цикла, после закрытия канала.
Глава 22. Функции
Подобно "настоящим" языкам программирования, Bash тоже имеет функции, хотя и в несколько ограниченном варианте. Функция -- это подпрограмма, блок кода который реализует набор операций, своего рода "черный ящик", предназначенный для выполнения конкретной задачи. Функции могут использоваться везде, где имеются участки повторяющегося кода.
function function_name {
command...
}
или
function_name () {
command...
}
Вторая форма записи ближе к сердцу C-программистам (она же более переносимая).
Как и в языке C, скобка, открывающая тело функции, может помещаться на следующей строке.
function_name ()
{
command...
}
Вызов функции осуществляется простым указанием ее имени в тексте сценария.
Пример 22-1. Простая функция
#!/bin/bash
funky () { echo "Это обычная функция." } # Функция должна быть объявлена раньше, чем ее можно будет использовать.
# Вызов функции.
funky
exit 0
Функция должна быть объявлена раньше, чем ее можно будет использовать. К сожалению, в Bash нет возможности "опережающего объявления" функции, как например в C.
f1 # Эта строка вызовет сообщение об ошибке, поскольку функция "f1" еще не определена.
declare -f f1 # Это не поможет. f1 # По прежнему -- сообщение об ошибке.
# Однако...
f1 () { echo "Вызов функции \"f2\" из функции \"f1\"." f2 }
f2 () { echo "Функция \"f2\"." }
f1 # Функция "f2", фактически, не вызывается выше этой строки, #+ хотя ссылка на нее встречается выше, до ее объявления. # Это допускается.
# Спасибо S.C.
Допускается даже создание вложенных функций, хотя пользы от этого немного.
f1 () {
f2 () # вложенная { echo "Функция \"f2\", вложенная в \"f1\"." }
}
f2 # Вызывает сообщение об ошибке. # Даже "declare -f f2" не поможет.
echo
f1 # Ничего не происходит, простой вызов "f1", не означает автоматический вызов "f2". f2 # Теперь все нормально, вызов "f2" не приводит к появлению ошибки, #+ поскольку функция "f2" была определена в процессе вызова "f1".
# Спасибо S.C.
Объявление функции может размещаться в самых неожиданных местах.
ls -l | foo() { echo "foo"; } # Допустимо, но бесполезно.
if [ "$USER" = bozo ] then bozo_greet () # Объявление функции размещено в условном операторе. { echo "Привет, Bozo!" } fi
bozo_greet # Работает только у пользователя bozo, другие получат сообщение об ошибке.
# Нечто подобное можно использовать с определеной пользой для себя. NO_EXIT=1 # Will enable function definition below.
[[ $NO_EXIT -eq 1 ]] && exit() { true; } # Определение функции в последовательности "И-список". # Если $NO_EXIT равна 1, то объявляется "exit ()". # Тем самым, функция "exit" подменяет встроенную команду "exit".
exit # Вызывается функция "exit ()", а не встроенная команда "exit".
# Спасибо S.C.
22.1. Сложные функции и сложности с функциями
Функции могут принимать входные аргументы и возвращать код завершения.
function_name $arg1 $arg2
Доступ к входным аргументам, в функциях, производится посредством позиционных параметров, т.е. $1, $2 и так далее.
Пример 22-2. Функция с аргументами
#!/bin/bash # Функции и аргументы
DEFAULT=default # Значение аргумента по-умолчанию.
func2 () { if [ -z "$1" ] # Длина аргумента #1 равна нулю? then echo "-Аргумент #1 имеет нулевую длину.-" # Или аргумент не был передан функции. else echo "-Аргумент #1: \"$1\".-" fi
variable=${1-$DEFAULT} # Что делает echo "variable = $variable" #+ показанная подстановка параметра? # --------------------------- # Она различает отсутствующий аргумент #+ от "пустого" аргумента.
if [ "$2" ] then echo "-Аргумент #2: \"$2\".-" fi
return 0 }
echo
echo "Вызов функции без аргументов." func2 echo
echo "Вызов функции с \"пустым\" аргументом." func2 "" echo
echo "Вызов функции с неинициализированным аргументом." func2 "$uninitialized_param" echo
echo "Вызов функции с одним аргументом." func2 first echo
echo "Вызов функции с двумя аргументами." func2 first second echo
echo "Вызов функции с аргументами \"\" \"second\"." func2 "" second # Первый параметр "пустой" echo # и второй параметр -- ASCII-строка.
exit 0
Команда shift вполне применима и к аргументам функций (см. Пример 33-10). |
В отличие от других языков программирования, в сценариях на языке командной оболочке, в функции передаются аргументы по значению. [50] Если имена переменных (которые фактически являются указателями) передаются функции в виде аргументов, то они интерпретируются как обычные строки символов и не могут быть разыменованы. Функции интерпретируют свои аргументы буквально. |
код завершения
Функции возвращают значение в виде кода завершения. Код завершения может быть задан явно, с помощью команды return, в противном случае будет возвращен код завершения последней команды в функции (0 -- в случае успеха, иначе -- ненулевой код ошибки). Код завершения в сценарии может быть получен через переменную $?.
return
Завершает исполнение функции. Команда return [51] может иметь необязательный аргумент типа integer, который возвращается в вызывающий сценарий как "код завершения" функции, это значение так же записывается в переменную $?.