# разделенные пробелами, которые и были записаны в переменные.

# 3) В последнюю переменную была записана вся оставшаяся часть строки.

# 4) Если команде "read" будет передано большее число переменных, чем подстрок

# в первой строке файла, то последние переменные останутся "пустыми".

echo "----"

# Эта проблема легко разрешается с помощью цикла:

while read line

do

echo "$line"

done <data-file

# Спасибо Heiner Steven за разъяснения.

echo "----"

# Разбор строки, разделенной на поля

# Для задания разделителя полей, используется переменная $IFS,

echo "Список всех пользователей:"

OIFS=$IFS; IFS=: # В файле /etc/passwd, в качестве разделителя полей

# используется символ ":" .

while read name passwd uid gid fullname ignore

do

echo "$name ($fullname)"

done </etc/passwd # перенаправление ввода.

IFS=$OIFS # Восстановление предыдущего состояния переменной $IFS.

# Эту часть кода написал Heiner Steven.

# Если переменная $IFS устанавливается внутри цикла,

#+ то отпадает необходимость сохранения ее первоначального значения

#+ во временной переменной.

# Спасибо Dim Segebart за разъяснения.

echo "----"

echo "Список всех пользователей:"

while IFS=: read name passwd uid gid fullname ignore

do

echo "$name ($fullname)"

done </etc/passwd # перенаправление ввода.

echo

echo "Значение переменной \$IFS осталось прежним: $IFS"

exit 0

Note

Передача информации, выводимой командой echo, по конвейеру команде read, будет вызывать ошибку.

Тем не менее, передача данных по конвейеру от cat, кажется срабатывает.

cat file1 file2 |

while read line

do

echo $line

done

Не смотря на это, как указывает Bjon Eriksson:

Пример 11-7. Проблемы чтения данных из канала (конвейера)

#!/bin/sh

# readpipe. sh

# Этот сценарий предоставил Bjon Eriksson.

last="(null)"

cat $0 |

while read line

do

echo "{$line}"

last=$line

done

printf "\nКонец, последняя строка:$last\n"

exit 0 # Конец сценария.

# Далее следует результат (частично) работы сценария.

#############################################

./readpipe. sh

{#!/bin/sh}

{last="(null)"}

{cat $0 |}

{while read line}

{do}

{echo "{$line}"}

{last=$line}

{done}

{printf "nКонец, последняя строка:$last\n"}

Конец, последняя строка:(null)

Переменная (last) инициализируется в подоболочке, поэтому она оказывается

неинициализированной за его пределами.

Файловая система

НЕ нашли? Не то? Что вы ищете?

cd

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

(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)

[взято из упоминавшегося ранее примера]

Команда cd с ключом - P (physical) игнорирует символические ссылки.

Команда "cd -" выполняет переход в каталог $OLDPWD -- предыдущий рабочий каталог.

Caution

Неожиданным образом выполняется команда cd, если ей передать, в качестве каталога назначения, два слэша.

bash$

cd //

bash$

pwd

//

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

pwd

Выводит название текущего рабочего каталога (Print Working Directory) (см. Пример 11-8). Кроме того, имя текущего каталога хранится во внутренней переменной $PWD.

pushd, popd, dirs

Этот набор команд является составной частью механизма "закладок" на каталоги и позволяет перемещаться по каталогам вперед и назад в заданном порядке. Для хранения имен каталогов используется стек (LIFO -- "последний вошел, первый вышел").

pushd dir-name -- помещает имя текущего каталога в стек и осуществляет переход в каталог dir-name.

popd -- выталкивает, находящееся на вершине стека, имя каталога и одновременно осуществляет переход в каталог, оказавшийся на врешине стека.

dirs -- выводит содержимое стека каталогов (сравните с переменной $DIRSTACK). В случае успеха, обе команды -- pushd и popd автоматически вызывают dirs.

Эти команды могут оказаться весьма полезными, когда в сценарии нужно производить частую смену каталогов, но при этом не хочется жестко "зашивать" имена каталогов. Обратите внимание: содержимое стека каталогов постоянно хранится в переменной-массиве -- $DIRSTACK.

Пример 11-8. Смена текущего каталога

#!/bin/bash

dir1=/usr/local

dir2=/var/spool

pushd $dir1

# Команда 'dirs' будет вызвана автоматически (на stdout будет выведено содержимое стека).

echo "Выполнен переход в каталог `pwd`." # Обратные одиночные кавычки.

# Теперь можно выполнить какие либо действия в каталоге 'dir1'.

pushd $dir2

echo "Выполнен переход в каталог `pwd`."

# Теперь можно выполнить какие либо действия в каталоге 'dir2'.

echo "На вершине стека находится: $DIRSTACK."

popd

echo "Возврат в каталог `pwd`."

# Теперь можно выполнить какие либо действия в каталоге 'dir1'.

popd

echo "Возврат в первоначальный рабочий каталог `pwd`."

exit 0

Переменные

let

Команда let производит арифметические операции над переменными. В большинстве случаев, ее можно считать упрощенным вариантом команды expr.

Пример 11-9. Команда let, арифметические операции.

#!/bin/bash

echo

let a=11 # То же, что и 'a=11'

let a=a+5 # Эквивалентно "a = a + 5"

# (Двойные кавычки и дополнительные пробелы делают код более удобочитаемым)

echo "11 + 5 = $a"

let "a <<= 3" # Эквивалентно let "a = a << 3"

echo "\"\$a\" (=16) после сдвига влево на 3 разряда = $a"

let "a /= 4" # Эквивалентно let "a = a / 4"

echo "128 / 4 = $a"

let "a -= 5" # Эквивалентно let "a = a - 5"

echo "32 - 5 = $a"

let "a = a * 10" # Эквивалентно let "a = a * 10"

echo "27 * 10 = $a"

let "a %= 8" # Эквивалентно let "a = a % 8"

echo "270 mod 8 = $a (270 / 8 = 33, остаток = $a)"

echo

exit 0

eval

eval arg1 [arg2] ... [argN]

Транслирует список аргументов, из списка, в команды.

Пример 11-10. Демонстрация команды eval

#!/bin/bash

y=`eval ls - l` # Подобно y=`ls - l`

echo $y # но символы перевода строки не выводятся, поскольку имя переменной не в кавычках.

echo

echo "$y" # Если имя переменной записать в кавычках -- символы перевода строки сохраняются.

echo; echo

y=`eval df` # Аналогично y=`df`

echo $y # но без символов перевода строки.

# Когда производится подавление вывода символов LF (перевод строки), то анализ

#+ результатов различными утилитами, такими как awk, можно сделать проще.

exit 0

Пример 11-11. Принудительное завершение сеанса

#!/bin/bash

y=`eval ps ax | sed - n '/ppp/p' | awk '{ print $1 }'`

# Выяснить PID процесса 'ppp'.

kill -9 $y # "Прихлопнуть" его

# Предыдущие строки можно заменить одной строкой

# kill -9 `ps ax | awk '/ppp/ { print $1 }'

chmod 666 /dev/ttyS3

# Завершенный, по сигналу SIGKILL, ppp изменяет права доступа

# к последовательному порту. Вернуть их в первоначальное состояние.

rm /var/lock/LCK..ttyS3 # Удалить lock-файл последовательного порта.

exit 0

Пример 11-12. Шифрование по алгоритму "rot13"

#!/bin/bash

# Реализация алгоритма шифрования "rot13" с помощью 'eval'.

# Сравните со сценарием "rot13.sh".

setvar_rot_13() # Криптование по алгоритму "rot13"

{

local varname=$1 varvalue=$2

eval $varname='$(echo "$varvalue" | tr a-z n-za-m)'

}

setvar_rot_13 var "foobar" # Пропустить слово "foobar" через rot13.

echo $var # sbbone

echo $var | tr a-z n-za-m # foobar

# Расшифровывание.

# Пример предоставил Stephane Chazelas.

exit 0

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

Пример 11-13. Замена имени переменной на ее значение, в исходном тексте программы на языке Perl, с помощью eval

В программе "test. pl", на языке Perl:

...

my $WEBROOT = <WEBROOT_PATH>;

...

Эта попытка подстановки значения переменной вместо ее имени:

$export WEBROOT_PATH=/usr/local/webroot

$sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test. pl > out

даст такой результат:

my $WEBROOT = $WEBROOT_PATH;

Тем не менее:

$export WEBROOT_PATH=/usr/local/webroot

$eval sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test. pl > out

# ====

Этот вариант дал желаемый результат -- имя переменной, в тексте программы,

благополучно было заменено на ее значение:

my $WEBROOT = /usr/local/webroot

Caution

Команда eval может быть небезопасна. Если существует приемлемая альтернатива, то желательно воздерживаться от использования eval. Так, eval $COMMANDS исполняет код, который записан в переменную COMMANDS, которая, в свою очередь, может содержать весьма неприятные сюрпризы, например rm - rf *. Использование команды eval, для исполнения кода неизвестного происхождения, крайне опасно.

set

Команда set изменяет значения внутренних переменных сценария. Она может использоваться для переключения опций (ключей, флагов), определяющих поведение скрипта. Еще одно применение -- сброс/установка позиционных параметров (аргументов), значения которых будут восприняты как результат работы команды (set `command`).

Пример 11-14. Установка значений аргументов с помощью команды set

#!/bin/bash

# script "set-test"

# Вызовите сценарий с тремя аргументами командной строки,

# например: "./set-test one two three".

echo

echo "Аргументы перед вызовом set \`uname - a\` :"

echo "Аргумент #1 = $1"

echo "Аргумент #2 = $2"

echo "Аргумент #3 = $3"

set `uname - a` # Изменение аргументов

# значения которых берутся из результата работы `uname - a`

echo $_

echo "Аргументы после вызова set \`uname - a\` :"

# $1, $2, $3 и т. д. будут переустановлены в соответствии с выводом

#+ команды `uname - a`

echo "Поле #1 'uname - a' = $1"

echo "Поле #2 'uname - a' = $2"

echo "Поле #3 'uname - a' = $3"

echo ---

echo $_ # ---

echo

exit 0

Вызов set без параметров просто выводит список инициализированных переменных окружения.

bash$

set

AUTHORCOPY=/home/bozo/posts

BASH=/bin/bash

BASH_VERSION=$'2.05.8(1)-release'

...

XAUTHORITY=/home/bozo/.Xauthority

_=/etc/bashrc

variable22=abc

variable23=xzy

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

Пример 11-15. Изменение значений позиционных параметров (аргументов)

#!/bin/bash

variable="one two three four five"

set -- $variable

# Значения позиционных параметров берутся из "$variable".

first_param=$1

second_param=$2

shift; shift # сдвиг двух первых параметров.

remaining_params="$*"

echo

echo "первый параметр = $first_param" # one

echo "второй параметр = $second_param" # two

echo "остальные параметры = $remaining_params" # three four five

echo; echo

# Снова.

set -- $variable

first_param=$1

second_param=$2

echo "первый параметр = $first_param" # one

echo "второй параметр = $second_param" # two

# ======================================================

set --

# Позиционные параметры сбрасываются, если не задано имя переменной.

first_param=$1

second_param=$2

echo "первый параметр = $first_param" # (пустое значение)

echo "второй параметр = $second_param" # (пустое значение)

exit 0

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

unset

Команда unset удаляет переменную, фактически -- устанавливает ее значение в null. Обратите внимание: эта команда не может сбрасывать позиционные параметры (аргументы).

bash$

unset PATH

bash$

echo $PATH

bash$

Пример 11-16. "Сброс" переменной

#!/bin/bash

# unset. sh: Сброс переменной.

variable=hello # Инициализация.

echo "variable = $variable"

unset variable # Сброс.

# Тот же эффект дает variable=

echo "(unset) variable = $variable" # $variable = null.

exit 0

export

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

Пример 11-17. Передача переменных во вложенный сценарий awk, с помощью export

#!/bin/bash

# Еще одна версия сценария "column totaler" (col-totaler. sh)

# который суммирует заданную колонку (чисел) в заданном файле.

# Здесь используются переменные окружения, которые передаются сценарию 'awk'.

ARGS=2

E_WRONGARGS=65

if [ $# - ne "$ARGS" ] # Проверка количества входных аргументов.

then

echo "Порядок использования: `basename $0` filename column-number"

exit $E_WRONGARGS

fi

filename=$1

column_number=$2

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

export column_number

# Экспорт номера столбца.

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

# ----

awk '{ total += $ENVIRON["column_number"]

}

END { print total }' $filename

# ----

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

# Спасибо Stephane Chazelas.

exit 0

Tip

Допускается объединение инициализации и экспорта переменной в одну инструкцию: export var1=xxx.

Однако, как заметил Greg Keraunen, в некоторых ситуациях такая комбинация может давать иной результат, нежели раздельная инициализация и экспорт.

bash$

export var=(a b); echo ${var[0]}

(a b)

bash$

var=(a b); export var; echo ${var[0]}

a

declare, typeset

Команды declare и typeset задают и/или накладывают ограничения на переменные.

readonly

То же самое, что и declare -r, делает переменную доступной только для чтения, т. е. переменная становится подобна константе. При попытке изменить значение такой переменной выводится сообщение об ошибке. Эта команда может расцениваться как квалификатор типа const в языке C.

getopts

Мощный инструмент, используемый для разбора аргументов, передаваемых сценарию из командной строки. Это встроенная команда Bash, но имеется и ее "внешний" аналог /usr/bin/getopt, а так же программистам, пишущим на C, хорошо знакома похожая библиотечная функция getopt. Она позволяет обрабатывать серии опций, объединенных в один аргумент [25] и дополнительные аргументы, передаваемые сценарию (например, scriptname - abc - e /usr/local).

С командой getopts очень тесно взаимосвязаны скрытые переменные. $OPTIND -- указатель на аргумент (OPTion INDex) и $OPTARG (OPTion ARGument) -- дополнительный аргумент опции. Символ двоеточия, следующий за именем опции, указывает на то, что она имеет дополнительный аргумент.

Обычно getopts упаковывается в цикл while, в каждом проходе цикла извлекается очередная опция и ее аргумент (если он имеется), обрабатывается, затем уменьшается на 1 скрытая переменная $OPTIND и выполняется переход к началу новой итерации.

Note

1.  Опциям (ключам), передаваемым в сценарий из командной строки, должен предшествовать символ "минус" (-) или "плюс" (+). Этот префикс (- или +) позволяет getopts отличать опции (ключи) от прочих аргументов. Фактически, getopts не будет обрабатывать аргументы, если им не предшествует символ - или +, выделение опций будет прекращено как только встретится первый аргумент.

2.  Типичная конструкция цикла while с getopts несколько отличается от стандартной из-за отсутствия квадратных скобок, проверяющих условие продолжения цикла.

3.  Пример getopts, заменившей устаревшую, и не такую мощную, внешнюю команду getopt.

while getopts ":abcde:fg" Option

# Начальное объявление цикла анализа опций.

# a, b, c, d, e, f, g -- это возможные опции (ключи).

# Символ : после опции 'e' указывает на то, что с данной опцией может идти

# дополнительный аргумент.

do

case $Option in

a ) # Действия, предусмотренные опцией 'a'.

b ) # Действия, предусмотренные опцией 'b'.

...

e) # Действия, предусмотренные опцией 'e', а так же необходимо обработать $OPTARG,

# в которой находится дополнительный аргумент этой опции.

...

g ) # Действия, предусмотренные опцией 'g'.

esac

done

shift $(($OPTIND - 1))

# Перейти к следующей опции.

# Все не так сложно, как может показаться ;-)

Пример 11-18. Прием опций/аргументов, передаваемых сценарию, с помощью getopts

#!/bin/bash

# ex33.sh

# Обработка опций командной строки с помощью 'getopts'.

# Попробуйте вызвать этот сценарий как:

# 'scriptname - mn'

# 'scriptname - oq qOption' (qOption может быть любой произвольной строкой.)

# 'scriptname - qXXX - r'

#

# 'scriptname - qr' - Неожиданный результат: "r" будет воспринят как дополнительный аргумент опции "q"

# 'scriptname - q - r' - То же самое, что и выше

# Если опция ожидает дополнительный аргумент ("flag:"), то следующий параметр

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

NO_ARGS=0

E_OPTERROR=65

if [ $# - eq "$NO_ARGS" ] # Сценарий вызван без аргументов?

then

echo "Порядок использования: `basename $0` options (-mnopqrs)"

exit $E_OPTERROR # Если аргументы отсутствуют -- выход с сообщением

# о порядке использования скрипта

fi

# Порядок использования: scriptname - options

# Обратите внимание: дефис (-) обязателен

while getopts ":mnopq:rs" Option

do

echo $OPTIND

case $Option in

m ) echo "Сценарий #1: ключ - m-";;

n | o ) echo "Сценарий #2: ключ -$Option-";;

p ) echo "Сценарий #3: ключ - p-";;

q ) echo "Сценарий #4: ключ - q-, с аргументом \"$OPTARG\"";;

# Обратите внимание: с ключом 'q' должен передаваться дополнительный аргумент,

# в противном случае отработает выбор "по-умолчанию".

r | s ) echo "Сценарий #5: ключ -$Option-"'';;

* ) echo "Выбран недопустимый ключ.";; # ПО-УМОЛЧАНИЮ

esac

done

shift $(($OPTIND - 1))

# Переход к очередному параметру командной строки.

exit 0

Управление сценарием

source, . (точка)

Когда эта команда вызывается из командной строки, то это приводит к запуску указанного сценария. Внутри сценария, команда source file-name загружает файл file-name. Таким образом она очень напоминает директиву препроцессора языка C/C++ -- "#include". Может найти применение в ситуациях, когда несколько сценариев пользуются одним файлом с данными или библиотекой функций.

Пример 11-19. "Подключение" внешнего файла

#!/bin/bash

. data-file # Загрузка файла с данными.

# Тот же эффект дает "source data-file", но этот вариант более переносим.

# Файл "data-file" должен находиться в текущем каталоге,

#+ т. к. путь к нему не указан.

# Теперь, выведем некоторые переменные из этого файла.

echo "variable1 (из data-file) = $variable1"

echo "variable3 (из data-file) = $variable3"

let "sum = $variable2 + $variable4"

echo "Сумма variable2 + variable4 (из data-file) = $sum"

echo "message1 (из data-file): \"$message1\""

# Обратите внимание: кавычки экранированы

print_message Вызвана функция вывода сообщений, находящаяся в data-file.

exit 0

Файл data-file для Пример 11-19, представленного выше, должен находиться в том же каталоге.

# Этот файл подключается к сценарию.

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

# Загружаться может командой 'source' или '.' .

# Инициализация некоторых переменных.

variable1=22

variable2=474

variable3=5

variable4=97

message1="Привет! Как поживаете?"

message2="Досвидания!"

print_message ()

{

# Вывод сообщения переданного в эту функцию.

if [ - z "$1" ]

then

return 1

# Ошибка, если аргумент отсутствует.

fi

echo

until [ - z "$1" ]

do

# Цикл по всем аргументам функции.

echo - n "$1"

# Вывод аргумента с подавлением символа перевода строки.

echo - n " "

# Вставить пробел, для разделения выводимых аргументов.

shift

# Переход к следующему аргументу.

done

echo

return 0

}

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

Пример 11-20. Пример (бесполезный) сценария, который подключает себя самого.

#!/bin/bash

# self-source. sh: сценарий, который рекурсивно подключает себя самого."

# Из "Бестолковые трюки", том II.

MAXPASSCNT=100 # Максимальное количество проходов.

echo - n "$pass_count "

# На первом проходе выведет два пробела,

#+ т. к. $pass_count еще не инициализирована.

let "pass_count += 1"

# Операция инкремента неинициализированной переменной $pass_count

#+ на первом проходе вполне допустима.

# Этот прием срабатывает в Bash и pdksh, но,

#+ при переносе сценария в другие командные оболочки,

#+ он может оказаться неработоспособным или даже опасным.

# Лучшим выходом из положения, будет присвоить переменной $pass_count

#+ значение 0, если она неинициализирована.

while [ "$pass_count" - le $MAXPASSCNT ]

do

. $0 # "Подключение" самого себя.

# ./$0 (истинная рекурсия) в данной ситуации не сработает.

done

# Происходящее здесь фактически не является рекурсией как таковой,

#+ т. к. сценарий как бы "расширяет" себя самого

#+ (добавляя новый блок кода)

#+ на каждом проходе цикла 'while',

#+ командой 'source' в строке 22.

#

# Само собой разумеется, что первая строка (#!), вновь подключенного сценария,

#+ интерпретируется как комментарий, а не как начало нового сценария (sha-bang)

echo

exit 0 # The net effect is counting from 1 to 100.

# Very impressive.

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

#

# Напишите сценарий, который использовал бы этот трюк для чего либо полезного.

exit

Безусловное завершение работы сценария. Команде exit можно передать целое число, которое будет возвращено вызывающему процессу как код завершения. Вообще, считается хорошей практикой завершать работу сценария, за исключением простейших случаев, командой exit 0, чтобы проинформировать родительский процесс об успешном завершении.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34