• Глава 9. К вопросу о переменных
  • 9.1. Внутренние переменные
  • Пример 9-1. $IFS и пробельные символы
  • Пример 9-2. Ограничения времени ожидания ввода
  • Пример 9-3. Еще один пример ограничения времени ожидания ввода от пользователя
  • Пример 9-4. Ограничение времени ожидания команды read
  • Пример 9-5. Я -- root?
  • Пример 9-6. arglist: Вывод списка аргументов с помощью переменных $* и $@
  • Пример 9-7. Противоречия в переменных $* и $@
  • Пример 9-8. Содержимое $* и $@, когда переменная $IFS -- пуста
  • Пример 9-9. Переменная "подчеркивание"
  • 9.2. Работа со строками
  • Пример 9-10. Вставка пустых строк между параграфами в текстовом файле
  • Пример 9-11. Преобразование графических файлов из одного формата в другой, с изменением имени файла
  • 9.2.1. Использование awk при работе со строками
  • Пример 9-12. Альтернативный способ извлечения подстрок
  • 9.2.2. Дальнейшее обсуждение
  • 9.3. Подстановка параметров
  • Пример 9-13. Подстановка параметров и сообщения об ошибках
  • Пример 9-14. Подстановка параметров и сообщение о "порядке использования"
  • Пример 9-15. Длина переменной
  • Пример 9-16. Поиск по шаблону в подстановке параметров
  • Пример 9-17. Изменение расширений в именах файлов:
  • 9.4. Объявление переменных: declare и typeset
  • Пример 9-20. Объявление переменных с помощью инструкции declare
  • 9.5. Косвенные ссылки на переменные
  • Пример 9-21. Косвенные ссылки
  • Пример 9-22. Передача косвенных ссылок в awk
  • 9.6. $RANDOM: генерация псевдослучайных целых чисел
  • Пример 9-23. Генерация случайных чисел
  • Пример 9-24. Выбор случайной карты из колоды
  • Пример 9-25. Имитация бросания кубика с помощью RANDOM
  • Пример 9-26. Переустановка RANDOM
  • Пример 9-27. Получение псевдослучайных чисел с помощью awk
  • 9.7. Двойные круглые скобки
  • Пример 9-28. Работа с переменными в стиле языка C
  • Глава 10. Циклы и ветвления
  • 10.1. Циклы
  • Пример 10-1. Простой цикл for
  • Пример 10-2. Цикл for с двумя параметрами в каждом из элементов списка
  • Пример 10-3. Fileinfo: обработка списка файлов, находящегося в переменной
  • Пример 10-4. Обработка списка файлов в цикле for
  • Пример 10-5. Цикл for без списка аргументов
  • Пример 10-6. Создание списка аргументов в цикле for с помощью операции подстановки команд
  • Пример 10-7. grep для бинарных файлов
  • Пример 10-8. Список всех пользователей системы
  • Пример 10-9. Проверка авторства всех бинарных файлов в текущем каталоге
  • Пример 10-10. Список символических ссылок в каталоге
  • Пример 10-11. Список символических ссылок в каталоге, сохраняемый в файле
  • Пример 10-12. C-подобный синтаксис оператора цикла for
  • Пример 10-13. Работа с командой efax в пакетном режиме
  • Пример 10-14. Простой цикл while
  • Пример 10-15. Другой пример цикла while
  • Пример 10-16. Цикл while с несколькими условиями
  • Пример 10-17. C-подобный синтаксис оформления цикла while
  • Пример 10-18. Цикл until
  • 10.2. Вложенные циклы
  • Пример 10-19. Вложенный цикл
  • 10.3. Управление ходом выполнения цикла
  • Пример 10-20. Команды break и continue в цикле
  • Пример 10-21. Прерывание многоуровневых циклов
  • Пример 10-22. Передача управление в начало внешнего цикла
  • Пример 10-23. Живой пример использования "continue N"
  • 10.4. Операторы выбора
  • Пример 10-24. Использование case
  • Пример 10-25. Создание меню с помощью case
  • Пример 10-26. Оператор case допускает использовать подстановку команд вместо анализируемой переменной
  • Пример 10-27. Простой пример сравнения строк
  • Пример 10-28. Проверка ввода
  • Пример 10-29. Создание меню с помощью select
  • Пример 10-30. Создание меню с помощью select в функции
  • Глава 11. Внутренние команды
  • Пример 11-1. printf в действии
  • Пример 11-2. Ввод значений переменных с помощью read
  • Пример 11-3. Пример использования команды read без указания переменной для ввода
  • Пример 11-4. Ввод многострочного текста с помощью read
  • Пример 11-5. Обнаружение нажатия на курсорные клавиши
  • Пример 11-6. Чтение командой read из файла через перенаправление
  • Пример 11-7. Смена текущего каталога
  • Пример 11-8. Команда let, арифметические операции.
  • Пример 11-9. Демонстрация команды eval
  • Пример 11-10. Принудительное завершение сеанса
  • Пример 11-11. Шифрование по алгоритму "rot13"
  • Пример 11-12. Замена имени переменной на ее значение, в исходном тексте программы на языке Perl, с помощью eval
  • Пример 11-13. Установка значений аргументов с помощью команды set
  • Пример 11-14. Изменение значений позиционных параметров (аргументов)
  • Пример 11-15. "Сброс" переменной
  • Пример 11-16. Передача переменных во вложенный сценарий awk, с помощью export
  • Пример 11-17. Прием опций/аргументов, передаваемых сценарию, с помощью getopts
  • Пример 11-18. "Подключение" внешнего файла
  • Пример 11-19. Пример (бесполезный) сценария, который подключает себя самого.
  • Пример 11-21. Сценарий, который запускает себя самого
  • 11.1. Команды управления заданиями
  • Пример 11-22. Ожидание завершения процесса перед тем как продолжить работу
  • Пример 11-23. Сценарий, завершающий себя сам с помощью команды kill
  • Глава 12. Внешние команды, программы и утилиты
  • 12.1. Базовые команды
  • Пример 12-1. Создание оглавления диска для записи CDR, с помощью команды ls
  • 12.2. Более сложные команды
  • Пример 12-2. Badname, удаление файлов в текущем каталоге, имена которых содержат недопустимые символы и пробелы.
  • Пример 12-3. Удаление файла по его номеру inode
  • Пример 12-4. Использование команды xargs для мониторинга системного журнала
  • Пример 12-5. copydir, копирование файлов из текущего каталога в другое место, с помощью xargs
  • Пример 12-6. Пример работы с expr
  • 12.3. Команды для работы с датой и временем
  • Пример 12-7. Команда date
  • 12.4. Команды обработки текста
  • Пример 12-8. Частота встречаемости отдельных слов
  • Пример 12-9. Какие из файлов являются сценариями?
  • Пример 12-10. Генератор 10-значных случайных чисел
  • Пример 12-11. Мониторинг системного журнала с помощью tail
  • Пример 12-12. Сценарий-эмулятор "grep"
  • Пример 12-13. Поиск слов в словаре
  • Пример 12-14. toupper: Преобразование символов в верхний регистр.
  • Пример 12-15. lowercase: Изменение имен всех файлов в текущем каталоге в нижний регистр.
  • Пример 12-16. du: Преобразование текстового файла из формата DOS в формат UNIX.
  • Пример 12-17. rot13: Сверхслабое шифрование по алгоритму rot13.
  • Пример 12-18. Более "сложный" шифр
  • Пример 12-20. Пример форматирования списка файлов в каталоге
  • Пример 12-21. nl: Самонумерующийся сценарий.
  • 12.5. Команды для работы с файлами и архивами
  • Пример 12-23. Распаковка архива rpm
  • Пример 12-24. Удаление комментариев из файла с текстом программы на языке C
  • Пример 12-25. Исследование каталога /usr/X11R6/bin
  • Пример 12-26. "Расширенная" команда strings
  • Пример 12-27. Пример сравнения двух файлов с помощью cmp.
  • Пример 12-28. Утилиты basename и dirname
  • Пример 12-29. Проверка целостности файла
  • Пример 12-30. Декодирование файлов
  • 12.6. Команды для работы с сетью
  • Пример 12-31. Сценарий, отправляющий себя самого по электронной почте
  • 12.7. Команды управления терминалом
  • 12.8. Команды выполнения математических операций
  • Пример 12-32. Ежемесячные выплаты по займу
  • Пример 12-33. Перевод чисел из одной системы счисления в другую
  • Пример 12-34. Пример взаимодействия bc со "встроенным документом"
  • Пример 12-35. Вычисление числа "пи"
  • Пример 12-36. Преобразование чисел из десятичной в шестнадцатиричную систему счисления
  • Пример 12-37. Разложение числа на простые множители
  • Пример 12-38. Расчет гипотенузы прямоугольного треугольника
  • 12.9. Прочие команды
  • Пример 12-39. Использование seq для генерации списка аргументов цикла for
  • Пример 12-40. Использование getopt для разбора аргументов командной строки
  • Пример 12-41. Захват нажатых клавиш
  • Пример 12-42. Надежное удаление файла
  • Пример 12-43. Генератор имен файлов
  • Пример 12-44. Преобразование метров в мили
  • Пример 12-45. Пример работы с m4
  • Глава 13. Команды системного администрирования
  • Пример 13-1. Установка символа "забоя"
  • Пример 13-2. невидимый пароль: Отключение эхо-вывода на терминал
  • Пример 13-3.
  • Пример 13-4. Использование команды pidof при остановке процесса
  • Пример 13-5. Проверка образа CD
  • Пример 13-6. Создание файловой системы в обычном файле
  • Пример 13-7. Добавление нового жесткого диска
  • Пример 13-8. Сценарий killall, из каталога /etc/rc.d/init.d
  • Глава 14. Подстановка команд
  • Пример 14-1. Глупая выходка
  • Пример 14-2. Запись результатов выполнения цикла в переменную
  • Глава 15. Арифметические подстановки
  • Глава 16. Перенаправление ввода/вывода
  • 16.1. С помощью команды exec
  • Пример 16-1. Перенаправление stdin с помощью exec
  • Пример 16-2. Перенаправление stdout с помощью exec
  • Пример 16-3. Одновременное перенаправление устройств, stdin и stdout, с помощью команды exec
  • 16.2. Перенаправление для блоков кода
  • Пример 16-4. Перенаправление в цикл while
  • Пример 16-5. Альтернативная форма перенаправления в цикле while
  • Пример 16-6. Перенаправление в цикл until
  • Пример 16-7. Перенаправление в цикл for
  • Пример 16-8. Перенаправление устройств (stdin и stdout) в цикле for
  • Пример 16-9. Перенаправление в конструкции if/then
  • Пример 16-10. Файл с именами "names.data", для примеров выше
  • 16.3. Область применения
  • Пример 16-11. Регистрация событий
  • Глава 17. Встроенные документы
  • Пример 17-1. dummyfile: Создание 2-х строчного файла-заготовки
  • Пример 17-2. broadcast: Передача сообщения всем, работающим в системе, пользователям
  • Пример 17-3. Вывод многострочных сообщений с помощью cat
  • Пример 17-4. Вывод многострочных сообщений с подавлением символов табуляции
  • Пример 17-5. Встроенные документы и подстановка параметров
  • Пример 17-6. Отключение подстановки параметров
  • Пример 17-7. Передача пары файлов во входящий каталог на "Sunsite"
  • Пример 17-8. Встроенные документы и функции
  • Пример 17-9. "Анонимный" Встроенный Документ
  • Пример 17-10. Блочный комментарий
  • Пример 17-11. Встроенная справка к сценарию
  • Часть 3. Углубленный материал

    Глава 9. К вопросу о переменных

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

    9.1. Внутренние переменные

    Встроенные переменные $BASH

    путь к исполняемому файлу Bash

    bash$ echo $BASH

    /bin/bash


    $BASH_VERSINFO[n]

    это массив, состоящий из 6 элементов, и содержащий информацию о версии Bash. Очень похожа на переменную $BASH_VERSION, описываемую ниже.

    # Информация о версии Bash:


    for n in 0 1 2 3 4 5

    do

    echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"

    done


    # BASH_VERSINFO[0] = 2 # Major version no.

    # BASH_VERSINFO[1] = 05 # Minor version no.

    # BASH_VERSINFO[2] = 8 # Patch level.

    # BASH_VERSINFO[3] = 1 # Build version.

    # BASH_VERSINFO[4] = release # Release status.

    # BASH_VERSINFO[5] = i386-redhat-linux-gnu # Architecture

    # (same as $MACHTYPE).


    $BASH_VERSION

    версия Bash, установленного в системе

    bash$ echo $BASH_VERSION

    2.04.12(1)-release


    tcsh% echo $BASH_VERSION

    BASH_VERSION: Undefined variable.


    Проверка переменной $BASH_VERSION -- неплохой метод проверки типа командной оболочки, под которой исполняется скрипт. Переменная $SHELL не всегда дает правильный ответ.

    $DIRSTACK

    содержимое вершины стека каталогов (который управляется командами pushd и popd)

    Эта переменная соответствует команде dirs, за исключением того, что dirs показывает полное содержимое всего стека каталогов.

    $EDITOR

    заданный по-умолчанию редактор, вызываемый скриптом, обычно vi или emacs.

    $EUID

    "эффективный" идентификационный номер пользователя (Effective User ID)

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

    Значение переменной $EUID необязательно должно совпадать с содержимым переменной $UID.

    $FUNCNAME

    имя текущей функции

    xyz23 ()

    {

    echo "Исполняется функция $FUNCNAME." # Исполняется функция xyz23.

    }


    xyz23


    echo "FUNCNAME = $FUNCNAME" # FUNCNAME =

    # Пустое (Null) значение за пределеми функций.


    $GLOBIGNORE

    Перечень шаблонных символов, которые будут проигнорированы при выполнении подстановки имен файлов (globbing) .

    $GROUPS

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

    Это список групп (массив) идентификационных номеров групп для текущего пользователя, как эо записано в /etc/passwd.

    root# echo $GROUPS

    0


    root# echo ${GROUPS[1]}

    1


    root# echo ${GROUPS[5]}

    6


    $HOME

    домашний каталог пользователя, как правило это /home/username (см. Пример 9-13)

    $HOSTNAME

    Сетевое имя хоста устанавливается командой hostname во время исполнения инициализирующих сценариев на загрузке системы. Внутренняя переменная $HOSTNAME Bash получает свое значение посредством вызова функции gethostname(). См. так же Пример 9-13.

    $HOSTTYPE

    тип машины

    Подобно $MACHTYPE, идентифицирует аппаратную архитектуру.

    bash$ echo $HOSTTYPE

    i686

    $IFS

    разделитель полей во вводимой строке (IFS -- Input Field Separator)

    По-умолчанию -- пробельный символ (пробел, табуляция и перевод строки), но может быть изменен, например, для разбора строк, в которых отдельные поля разделены запятыми. Обратите внимание: при составлении содержимого переменной $*, Bash использует первый символ из $IFS для разделения аргументов. См. Пример 5-1.

    bash$ echo $IFS | cat -vte

    $


    bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'

    w:x:y:z


    При всем при том следует помнить, что при использовании $IFS пробельные символы обрабатываются несколько иначе, чем все остальные.

    Пример 9-1. $IFS и пробельные символы

    #!/bin/bash

    # При использовании $IFS, пробельные символы обрабатываются иначе, чем все остальные.


    output_args_one_per_line()

    {

    for arg

    do echo "[$arg]"

    done

    }


    echo; echo "IFS=\" \""

    echo "-------"


    IFS=" "

    var=" a b c "

    output_args_one_per_line $var # output_args_one_per_line `echo " a b c "`

    #

    # [a]

    # [b]

    # [c]


    echo; echo "IFS=:"

    echo "-----"


    IFS=:

    var=":a::b:c:::" # То же самое, только пробелы зменены символом ":".

    output_args_one_per_line $var

    #

    # []

    # [a]

    # []

    # [b]

    # [c]

    # []

    # []

    # []


    # То же самое происходит и с разделителем полей "FS" в awk.


    # Спасибо Stephane Chazelas.


    echo


    exit 0


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

    $LC_COLLATE

    Чаще всего устанавливается в .bashrc или /etc/profile, эта переменная задает порядок сортировки символов, в операциях подстановки имен файлов и в поиске по шаблону. При неверной настройке переменной LC_COLLATE можно получить весьма неожиданные результаты.

    Начиная с версии 2.05, Bash, в операциях подстановки имен файлов, не делает различий между символами верхнего и нижнего регистров, в диапазонах символов в квадратных скобках. Например,, ls [A-M]* выведет как File1.txt, так и file1.txt. Возврат к общепринятому стандарту поведения шаблонов в квадратных скобках выполняется установкой переменной LC_COLLATE в значение C командой export LC_COLLATE=C в файле /etc/profile и/или ~/.bashrc.

    $LC_CTYPE

    Эта внутренняя переменная определяет кодировку символов. Используется в операциях подстановки и поиске по шаблону.

    $LINENO

    Номер строки исполняемого сценария. Эта переменная имеет смысл только внутри исполняемого сценария и чаще всего применяется в отладочных целях.

    # *** BEGIN DEBUG BLOCK ***

    last_cmd_arg=$_ # Запомнить.


    echo "Строка $LINENO: переменная \"v1\" = $v1"

    echo "Последний аргумент командной строки = $last_cmd_arg"

    # *** END DEBUG BLOCK ***


    $MACHTYPE

    аппаратная архитектура

    Идентификатор аппаратной архитектуры.

    bash$ echo $MACHTYPE

    i686

    $OLDPWD

    прежний рабочий каталог ("OLD-Print-Working-Directory")

    $OSTYPE

    тип операционной системы

    bash$ echo $OSTYPE

    linux

    $PATH

    путь поиска, как правило включает в себя каталоги /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, и т.д.

    Когда командный интерпретатор получает команду, то он автоматически пытается отыскать соответствующий исполняемый файл в указанном списке каталогов (в переменной $PATH). Каталоги, в указанном списке, должны отделяться друг от друга двоеточиями. Обычно, переменная $PATH инициализируется в /etc/profile и/или в ~/.bashrc (см. Глава 26).

    bash$ echo $PATH

    /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin


    Инструкция PATH=${PATH}:/opt/bin добавляет каталог /opt/bin в конец текущего пути поиска. Иногда может оказаться целесообразным, внутри сценария, временно добавить какой-либо каталог к пути поиска. По завершении работы скрипта, эти изменения будут утеряны (вспомните о том, что невозможно изменить переменные окружения вызывающего процесса).

    Текущий "рабочий каталог", ./, обычно не включается в $PATH из соображений безопасности.

    $PIPESTATUS

    Код возврата канала (конвейера). Интересно, что это не то же самое, что код возврата последней исполненной команды.

    bash$ echo $PIPESTATUS

    0


    bash$ ls -al | bogus_command

    bash: bogus_command: command not found

    bash$ echo $PIPESTATUS

    141


    bash$ ls -al | bogus_command

    bash: bogus_command: command not found

    bash$ echo $?

    127


    Переменная $PIPESTATUS может давать неверные значения при вызове из командной строки.

    tcsh% bash


    bash$ who | grep nobody | sort

    bash$ echo ${PIPESTATUS[*]}

    0


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

    Спасибо Wayne Pollock за замечания и предоставленный пример.

    $PPID

    Переменная $PPID хранит PID (идентификатор) родительского процесса.[ 19 ]

    Сравните с командой pidof.

    $PS1

    prompt, приглашение командной строки.

    $PS2

    Вторичное приглашение командной строки, выводится тогда, когда от пользователя ожидается дополнительный ввод. Отображается как ">".

    $PS3

    Третичное приглашение (prompt), выводится тогда, когда пользователь должен сделать выбор в операторе select (см. Пример 10-29).

    $PS4

    Приглашение (prompt) четвертого уровня, выводится в начале каждой строки вывода тогда, когда сценарий вызывается с ключом -x. Отображается как "+".

    $PWD

    рабочий (текущий) каталог

    Аналог встроенной команды pwd.

    #!/bin/bash


    E_WRONG_DIRECTORY=73


    clear # Очистка экрана.


    TargetDirectory=/home/bozo/projects/GreatAmericanNovel


    cd $TargetDirectory

    echo "Удаление файлов в каталоге $TargetDirectory."


    if [ "$PWD" != "$TargetDirectory" ]

    then # Защита от случайного удаления файлов не в том каталоге.

    echo "Неверный каталог!"

    echo "Переменная $PWD указывает на другой каталог!"

    exit $E_WRONG_DIRECTORY

    fi


    rm -rf *

    rm .[A-Za-z0-9]* # удалить "скрытые" файлы (начинающиеся с ".")

    # rm -f .[^.]* ..?* удалить файлы, чьи имена начинаются с нескольких точек.

    # (shopt -s dotglob; rm -f *) тоже работает верно.

    # Спасибо S.C. за замечание.


    # Имена файлов могут содержать любые символы из диапазона 0-255, за исключением "/".

    # Оставляю вопрос удаления файлов с "необычными" символами для самостоятельного изучения.


    # Здесь можно вставить дополнительные действия, по мере необходимости.


    echo

    echo "Конец."

    echo "Файлы, из каталога $TargetDirectory, удалены."

    echo


    exit 0


    $REPLY

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

    #!/bin/bash


    echo

    echo -n "Ваше любимое растение? "

    read


    echo "Ваше любимое растение: $REPLY."

    # REPLY хранит последнее значение, прочитанное командой "read" тогда, и только тогда

    #+ когда команде "read" не передается имя переменной.


    echo

    echo -n "Ваш любимый фрукт? "

    read fruit

    echo "Ваш любимый фрукт $fruit."

    echo "но..."

    echo "Значение переменной \$REPLY осталось равным $REPLY."

    # Переменная $REPLY не была перезаписана потому, что

    # следующей команде "read", в качестве аргумента была передана переменная $fruit


    echo


    exit 0


    $SECONDS

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

    #!/bin/bash

    # Автор: Mendel Cooper

    # Дополнен переводчиком.

    #


    TIME_LIMIT=10

    INTERVAL=1


    echo

    echo "Для прерывания работы сценария, ранее чем через $TIME_LIMIT секунд, нажмите Control-C."

    echo


    while [ "$SECONDS" -le "$TIME_LIMIT" ]

    do

    # Оригинальный вариант сценария содержал следующие строки

    # if [ "$SECONDS" -eq 1 ]

    # then

    # units=second

    # else

    # units=seconds

    # fi

    #

    # Однако, из-за того, что в русском языке для описания множественного числа

    # существует большее число вариантов, чем в английском,

    # переводчик позволил себе смелость несколько подправить сценарий

    # (прошу ногами не бить! ;-) )

    # === НАЧАЛО БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ ===


    let "last_two_sym = $SECONDS - $SECONDS / 100 * 100" # десятки и единицы

    if [ "$last_two_sym" -ge 11 -a "$last_two_sym" -le 19 ]

    then

    units="секунд" # для чисел, которые заканчиваются на "...надцать"

    else

    let "last_sym = $last_two_sym - $last_two_sym / 10 * 10" # единицы

    case "$last_sym" in

    "1" )

    units="секунду" # для чисел, заканчивающихся на 1

    ;;

    "2" | "3" | "4" )

    units="секунды" # для чисел, заканчивающихся на 2, 3 и 4

    ;;

    * )

    units="секунд" # для всех остальных (0, 5, 6, 7, 8, 9)

    ;;

    esac

    fi

    # === КОНЕЦ БЛОКА ИЗМЕНЕНИЙ, ВНЕСЕННЫХ ПЕРЕВОДЧИКОМ ===


    echo "Сценарий отработал $SECONDS $units."

    # В случае перегруженности системы, скрипт может перескакивать через отдельные

    #+ значения счетчика

    sleep $INTERVAL

    done


    echo -e "\a" # Сигнал!


    exit 0


    $SHELLOPTS

    список допустимых опций интерпретатора shell. Переменная доступна только для чтения.

    bash$ echo $SHELLOPTS

    braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs


    $SHLVL

    Уровень вложенности shell. Если в командной строке

    echo $SHLVL

    дает 1, то в сценарии значение этой переменной будет больше на 1, т.е. 2.

    $TMOUT

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

    К сожалению это возможно только во время ожидания ввода с консоли или в окне терминала. А как было бы здорово, если бы можно было использовать эту внутреннюю переменную, скажем в комбинации с командой read! Но в данном контексте эта переменная абсолютно не применима и потому фактически бесполезна в сценариях. (Есть сведения о том, что в ksh время ожидания ввода командой read можно ограничить.)

    Организация ограничения времени ожидания ввода от пользователя в сценариях возможна, но это требут довольно сложных махинаций. Как один из вариантов, можно предложить организовать прерывание цикла ожидания по сигналу. Но это потребует написание функции обработки сигналов командой trap (см. Пример 29-5).

    Пример 9-2. Ограничения времени ожидания ввода

    #!/bin/bash

    # timed-input.sh


    # TMOUT=3 бесполезно в сценариях


    TIMELIMIT=3 # Три секунды в данном случае, но может быть установлено и другое значение


    PrintAnswer()

    {

    if [ "$answer" = TIMEOUT ]

    then

    echo $answer

    else # Чтобы не спутать разные варианты вывода.

    echo "Ваше любимое растение $answer"

    kill $! # "Прибить" ненужную больше функцию TimerOn, запущенную в фоновом процессе.

    # $! -- PID последнего процесса, запущенного в фоне.

    fi


    }


    TimerOn()

    {

    sleep $TIMELIMIT && kill -s 14 $$ &

    # Ждать 3 секунды, после чего выдать sigalarm сценарию.

    }


    Int14Vector()

    {

    answer="TIMEOUT"

    PrintAnswer

    exit 14

    }


    trap Int14Vector 14 # переназначить процедуру обработки прерывания от таймера (14)


    echo "Ваше любимое растение? "

    TimerOn

    read answer

    PrintAnswer


    # По общему признанию, это не очень хороший способ ограничения времени ожидания,

    #+ однако опция "-t"команды "read" упрощает задачу.

    # См. "t-out.sh", ниже.


    # Если вам нужно что-то более элегантное...

    #+ подумайте о написании программы на C или C++,

    #+ с использованием соответствующих библиотечных функций, таких как 'alarm' и 'setitimer'.


    exit 0

    В качестве альтернативы можно использовать stty.

    Пример 9-3. Еще один пример ограничения времени ожидания ввода от пользователя

    #!/bin/bash

    # timeout.sh


    # Автор: Stephane Chazelas,

    # дополнен автором документа.


    INTERVAL=5 # предел времени ожидания


    timedout_read() {

    timeout=$1

    varname=$2

    old_tty_settings=`stty -g`

    stty -icanon min 0 time ${timeout}0

    eval read $varname # или просто read $varname

    stty "$old_tty_settings"

    # См. man stty.

    }


    echo; echo -n "Как Вас зовут? Отвечайте быстрее! "

    timedout_read $INTERVAL your_name


    # Такой прием может не работать на некоторых типах терминалов.

    # Максимальное время ожидания зависит от терминала.

    # (чаще всего это 25.5 секунд).


    echo


    if [ ! -z "$your_name" ] # Если имя было введено...

    then

    echo "Вас зовут $your_name."

    else

    echo "Вы не успели ответить."

    fi


    echo


    # Алгоритм работы этого сценария отличается от "timed-input.sh".

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


    exit 0

    Возможно самый простой способ -- использовать опцию -t команды read.

    Пример 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. Примеры сценариев:

    1. Пример 12-6

    2. Пример 9-15

    3. Пример 9-16

    4. Пример 9-17

    5. Пример 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

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

    Пример 10-7. grep для бинарных файлов

    #!/bin/bash

    # bin-grep.sh: Поиск строк в двоичных файлах.


    # замена "grep" для бинарных файлов.

    # Аналогично команде "grep -a"


    E_BADARGS=65

    E_NOFILE=66


    if [ $# -ne 2 ]

    then

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

    exit $E_BADARGS

    fi


    if [ ! -f "$2" ]

    then

    echo "Файл \"$2\" не найден."

    exit $E_NOFILE

    fi


    for word in $( strings "$2" | grep "$1" )

    # Инструкция "strings" возвращает список строк в двоичных файлах.

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

    do

    echo $word

    done


    # Как указывает S.C., вышепрведенное объявление цикла for может быть упрощено

    # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'


    # Попробуйте что нибудь подобное: "./bin-grep.sh mem /bin/ls"


    exit 0

    Еще один пример.

    Пример 10-8. Список всех пользователей системы

    #!/bin/bash

    # userlist.sh


    PASSWORD_FILE=/etc/passwd

    n=1 # Число пользователей


    for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )

    # Разделитель полей = : ^^^^^^

    # Вывод первого поля ^^^^^^^^

    # Данные берутся из файла паролей ^^^^^^^^^^^^^^^^^

    do

    echo "Пользователь #$n = $name"

    let "n += 1"

    done


    # Пользователь #1 = root

    # Пользователь #2 = bin

    # Пользователь #3 = daemon

    # ...

    # Пользователь #30 = bozo


    exit 0

    И заключительный пример использования подстановки команд при создании [списка].

    Пример 10-9. Проверка авторства всех бинарных файлов в текущем каталоге

    #!/bin/bash

    # findstring.sh:

    # Поиск заданной строки в двоичном файле.


    directory=/usr/local/bin/

    fstring="Free Software Foundation" # Поиск файлов от FSF.


    for file in $( find $directory -type f -name '*' | sort )

    do

    strings -f $file | grep "$fstring" | sed -e "s%$directory%%"

    # Команде "sed" передается выражение (ключ -e),

    #+ для того, чтобы изменить обычный разделитель "/" строки поиска и строки замены

    #+ поскольку "/" - один из отфильтровываемых символов.

    # Использование такого символа порождает сообщение об ошибке (попробуйте).

    done


    exit 0


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

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

    # Измените сценарий таким образом, чтобы он брал

    #+ $directory и $fstring из командной строки.

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

    Пример 10-10. Список символических ссылок в каталоге

    #!/bin/bash

    # symlinks.sh: Список символических ссылок в каталоге.


    directory=${1-`pwd`}

    # По-умолчанию в текущем каталоге,

    # Блок кода, который выполняет аналогичные действия.

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

    # ARGS=1 # Ожидается один аргумент командной строки.

    #

    # if [ $# -ne "$ARGS" ] # Если каталог поиска не задан...

    # then

    # directory=`pwd` # текущий каталог

    # else

    # directory=$1

    # fi

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


    echo "символические ссылки в каталоге \"$directory\""


    for file in "$( find $directory -type l )" # -type l = символические ссылки

    do

    echo "$file"

    done | sort # В противном случае получится неотсортированный список.


    # Как отмечает Dominik 'Aeneas' Schnitzer,

    #+ в случае отсутствия кавычек для $( find $directory -type l )

    #+ сценарий "подавится" именами файлов, содержащими пробелы.


    exit 0

    Вывод цикла может быть перенаправлен со stdout в файл, ниже приводится немного модифицированный вариант предыдущего примера, демонстрирующий эту возможность.

    Пример 10-11. Список символических ссылок в каталоге, сохраняемый в файле

    #!/bin/bash

    # symlinks.sh: Список символических ссылок в каталоге.


    OUTFILE=symlinks.list # файл со списком


    directory=${1-`pwd`}

    # По-умолчанию -- текущий каталог,


    echo "символические ссылки в каталоге \"$directory\"" > "$OUTFILE"

    echo "---------------------------" >> "$OUTFILE"


    for file in "$( find $directory -type l )" # -type l = символические ссылки

    do

    echo "$file"

    done | sort >> "$OUTFILE" # перенаправление вывода

    # ^^^^^^^^^^^^^ в файл.


    exit 0

    Оператор цикла for имеет и альтернативный синтаксис записи -- очень похожий на синтаксис оператора for в языке C. Для этого используются двойные круглые скобки.

    Пример 10-12. C-подобный синтаксис оператора цикла for

    #!/bin/bash

    # Два вапианта оформления цикла.


    echo


    # Стандартный синтаксис.

    for a in 1 2 3 4 5 6 7 8 9 10

    do

    echo -n "$a "

    done


    echo; echo


    # +==========================================+


    # А теперь C-подобный синтаксис.


    LIMIT=10


    for ((a=1; a <= LIMIT ; a++)) # Двойные круглые скобки и "LIMIT" без "$".

    do

    echo -n "$a "

    done # Конструкция заимствована из 'ksh93'.


    echo; echo


    # +=========================================================================+


    # Попробуем и C-шный оператор "запятая".


    for ((a=1, b=1; a <= LIMIT ; a++, b++)) # Запятая разделяет две операции, которые выполняются совместно.

    do

    echo -n "$a-$b "

    done


    echo; echo


    exit 0

    См. так же Пример 25-10, Пример 25-11 и Пример A-7.

    ---

    А сейчас пример сценария, который может найти "реальное" применение.

    Пример 10-13. Работа с командой efax в пакетном режиме

    #!/bin/bash


    EXPECTED_ARGS=2

    E_BADARGS=65


    if [ $# -ne $EXPECTED_ARGS ]

    # Проверка наличия аргументов командной строки.

    then

    echo "Порядок использования: `basename $0` phone# text-file"

    exit $E_BADARGS

    fi


    if [ ! -f "$2" ]

    then

    echo "Файл $2 не является текстовым файлом"

    exit $E_BADARGS

    fi


    fax make $2 # Создать fax-файлы из текстовых файлов.


    for file in $(ls $2.0*) # Все файлы, получившиеся в результате преобразования.

    # Используется шаблонный символ в списке.

    do

    fil="$fil $file"

    done


    efax -d /dev/ttyS3 -o1 -t "T$1" $fil # отправить.


    # Как указывает S.C., в цикл for может быть вставлена сама команда отправки в виде:

    # efax -d /dev/ttyS3 -o1 -t "T$1" $2.0*

    # но это не так поучительно [;-)].


    exit 0

    while

    Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.

    while [condition] do command... done


    Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.

    while [condition] ; do


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

    Пример 10-14. Простой цикл while

    #!/bin/bash


    var0=0

    LIMIT=10


    while [ "$var0" -lt "$LIMIT" ]

    do

    echo -n "$var0 " # -n подавляет перевод строки.

    var0=`expr $var0 + 1` # допускается var0=$(($var0+1)).

    done


    echo


    exit 0

    Пример 10-15. Другой пример цикла while

    #!/bin/bash


    echo


    while [ "$var1" != "end" ] # возможна замена на while test "$var1" != "end"

    do

    echo "Введите значение переменной #1 (end - выход) "

    read var1 # Конструкция 'read $var1' недопустима (почему?).

    echo "переменная #1 = $var1" # кавычки обязательны, потому что имеется символ "#".

    # Если введено слово 'end', то оно тоже выводится на экран.

    # потому, что проверка переменной выполняется в начале итерации (перед вводом).

    echo

    done


    exit 0

    Оператор while может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла должен быть несколько иным.

    Пример 10-16. Цикл while с несколькими условиями

    #!/bin/bash


    var1=unset

    previous=$var1


    while echo "предыдущее значение = $previous"

    echo

    previous=$var1 # запомнить предыдущее значение

    [ "$var1" != end ]

    # В операторе "while" присутствуют 4 условия, но только последнее управляет циклом.

    # *последнее* условие - единственное, которое вычисляется.

    do

    echo "Введите значение переменной #1 (end - выход) "

    read var1

    echo "текущее значение = $var1"

    done


    # попробуйте самостоятельно разобраться в сценарии works.


    exit 0

    Как и в случае с for, цикл while может быть записан в C-подобной нотации, с использованием двойных круглых скобок (см. так же Пример 9-28).

    Пример 10-17. C-подобный синтаксис оформления цикла while

    #!/bin/bash

    # wh-loopc.sh: Цикл перебора от 1 до 10.


    LIMIT=10

    a=1


    while [ "$a" -le $LIMIT ]

    do

    echo -n "$a "

    let "a+=1"

    done # Пока ничего особенного.


    echo; echo


    # +=================================================================+


    # А теперь оформим в стиле языка C.


    ((a = 1)) # a=1

    # Двойные скобки допускают наличие лишних пробелов в выражениях.


    while (( a <= LIMIT )) # В двойных скобках символ "$" перед переменными опускается.

    do

    echo -n "$a "

    ((a += 1)) # let "a+=1"

    # Двойные скобки позволяют наращивание переменной в стиле языка C.

    done


    echo


    # Теперь, программисты, пишущие на C, могут чувствовать себя в Bash как дома.


    exit 0

    Стандартное устройство ввода stdin, для цикла while, можно перенаправить на файл с помощью команды перенаправления < в конце цикла.

    until

    Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.

    until [condition-is-true] do command... done


    Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования.

    Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.

    until [condition-is-true] ; do


    Пример 10-18. Цикл until

    #!/bin/bash


    until [ "$var1" = end ] # Проверка условия производится в начале итерации.

    do

    echo "Введите значение переменной #1 "

    echo "(end - выход)"

    read var1

    echo "значение переменной #1 = $var1"

    done


    exit 0


    10.2. Вложенные циклы

    Цикл называется вложенным, если он размещается внутри другого цикла. На первом проходе, внешний цикл вызывает внутренний, который исполняется до своего завершения, после чего управление передается в тело внешнего цикла. На втором проходе внешний цикл опять вызывает внутренний. И так до тех пор, пока не завершится внешний цикл. Само собой, как внешний, так и внутренний циклы могут быть прерваны командой break.

    Пример 10-19. Вложенный цикл

    #!/bin/bash

    # Вложенные циклы "for".


    outer=1 # Счетчик внешнего цикла.


    # Начало внешнего цикла.

    for a in 1 2 3 4 5

    do

    echo "Итерация #$outer внешнего цикла."

    echo "---------------------"

    inner=1 # Сброс счетчика вложенного цикла.


    # Начало вложенного цикла.

    for b in 1 2 3 4 5

    do

    echo "Итерация #$inner вложенного цикла."

    let "inner+=1" # Увеличить счетчик итераций вложенного цикла.

    done

    # Конец вложенного цикла.


    let "outer+=1" # Увеличить счетчик итераций внешнего цикла.

    echo # Пустая строка для отделения итераций внешнего цикла.

    done

    # Конец внешнего цикла.


    exit 0

    Демонстрацию вложенных циклов "while" вы найдете в Пример 25-6, а вложение цикла "while" в "until" -- в Пример 25-8.


    10.3. Управление ходом выполнения цикла

    break, continue

    Для управления ходом выполнения цикла служат команды break и continue[ 23 ] и точно соответствуют своим аналогам в других языках программирования. Команда break прерывает исполнение цикла, в то время как continue передает управление в начало цикло, минуя все последующие команды в теле цикла.

    Пример 10-20. Команды break и continue в цикле

    #!/bin/bash


    LIMIT=19 # Верхний предел


    echo

    echo "Печать чисел от 1 до 20 (исключая 3 и 11)."


    a=0


    while [ $a -le "$LIMIT" ]

    do

    a=$(($a+1))


    if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # Исключить 3 и 11

    then

    continue # Переход в начало цикла.

    fi


    echo -n "$a "

    done


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

    # Почему число 20 тоже выводится?


    echo; echo


    echo Печать чисел от 1 до 20, но взгляните, что происходит после вывода числа 2


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


    # Тот же цикл, только 'continue' заменено на 'break'.


    a=0


    while [ "$a" -le "$LIMIT" ]

    do

    a=$(($a+1))


    if [ "$a" -gt 2 ]

    then

    break # Завершение работы цикла.

    fi


    echo -n "$a "

    done


    echo; echo; echo


    exit 0

    Команде break может быть передан необязательный параметр. Команда break без параметра прерывает тот цикл, в который она вставлена, а break N прерывает цикл, стоящий на N уровней выше (причем 1-й уровень -- это уровень текущего цикла, прим. перев.).

    Пример 10-21. Прерывание многоуровневых циклов

    #!/bin/bash

    # break-levels.sh: Прерывание циклов.


    # "break N" прерывает исполнение цикла, стоящего на N уровней выше текущего.


    for outerloop in 1 2 3 4 5

    do

    echo -n "Группа $outerloop: "


    for innerloop in 1 2 3 4 5

    do

    echo -n "$innerloop "


    if [ "$innerloop" -eq 3 ]

    then

    break # Попробуйте "break 2",

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

    fi

    done


    echo

    done


    echo


    exit 0

    Команда continue, как и команда break, может иметь необязательный параметр. В простейшем случае, команда continue передает управление в начало текущего цикла, а команда continue N прерывает исполнение текущего цикла и передает управление в начало внешнего цикла, отстоящего от текущего на N уровней (причем 1-й уровень -- это уровень текущего цикла, прим. перев.).

    Пример 10-22. Передача управление в начало внешнего цикла

    #!/bin/bash

    # Команда "continue N" передает управление в начало внешнего цикла, отстоящего от текущего на N уровней.


    for outer in I II III IV V # внешний цикл

    do

    echo; echo -n "Группа $outer: "


    for inner in 1 2 3 4 5 6 7 8 9 10 # вложенный цикл

    do


    if [ "$inner" -eq 7 ]

    then

    continue 2 # Передача управления в начало цикла 2-го уровня.

    # попробуйте убрать параметр 2 команды "continue"

    fi


    echo -n "$inner " # 8 9 10 никогда не будут напечатаны.

    done


    done


    echo; echo


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

    # Подумайте, где реально можно использовать "continue N" в сценариях.


    exit 0

    Пример 10-23. Живой пример использования "continue N"

    # Albert Reiner привел пример использования "continue N":

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


    # Допустим, у меня есть большое количество задач, обрабатывающие некоторые данные,

    #+ которые хранятся в некоторых файлах, с именами, задаваемыми по шаблону,

    #+ в заданном каталоге.

    #+ Есть несколько машин, которым открыт доступ к этому каталогу

    #+ и я хочу распределить обработку информации между машинами.

    #+ тогда я обычно для каждой машины пишу нечто подобное:


    while true

    do

    for n in .iso.*

    do

    [ "$n" = ".iso.opts" ] && continue

    beta=${n#.iso.}

    [ -r .Iso.$beta ] && continue

    [ -r .lock.$beta ] && sleep 10 && continue

    lockfile -r0 .lock.$beta || continue

    echo -n "$beta: " `date`

    run-isotherm $beta

    date

    ls -alF .Iso.$beta

    [ -r .Iso.$beta ] && rm -f .lock.$beta

    continue 2

    done

    break

    done


    # Конкретная реализация цикла, особенно sleep N, зависит от конкретных применений,

    #+ но в общем случае он строится по такой схеме:


    while true

    do

    for job in {шаблон}

    do

    {файл уже обработан или обрабатывается} && continue

    {пометить файл как обрабатываемый, обработать, пометить как обработанный}

    continue 2

    done

    break # Или что нибудь подобное `sleep 600', чтобы избежать завершения.

    done


    # Этот сценарий завершит работу после того как все данные будут обработаны

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

    #+ соответствующих lock-файлоа позволяет вести обработку на нескольких машинах

    #+ одновременно, не производя дублирующих вычислений [которые, в моем случае,

    #+ выполняются в течении нескольких часов, так что для меня это очень важно].

    #+ Кроме того, поскольку поиск необработанных файлов всегда начинается с

    #+ самого начала, можно задавать приоритеты в именах файлов. Конечно, можно

    #+ обойтись и без `continue 2', но тогда придется ввести дополнительную

    #+ проверку -- действительно ли был обработан тот или иной файл

    #+ (чтобы перейти к поиску следующего необработанного файла).

    Конструкция continue N довольно сложна в понимании и применении, поэтому, вероятно лучше будет постараться избегать ее использования.


    10.4. Операторы выбора

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

    case (in) / esac

    Конструкция case эквивалентна конструкции switch в языке C/C++. Она позволяет выполнять тот или иной участок кода, в зависимости от результатов проверки условий. Она является, своего рода, краткой формой записи большого количества операторов if/then/else и может быть неплохим инструментом при создании разного рода меню.

    case "$variable" in "$condition1" ) command... ;; "$condition2" ) command... ;; esac


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

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

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

    ? Блок case должен завершаться ключевым словом esac (case записанное в обратном порядке).


    Пример 10-24. Использование case

    #!/bin/bash


    echo; echo "Нажмите клавишу и затем клавишу Return."

    read Keypress


    case "$Keypress" in

    [a-z] ) echo "буква в нижнем регистре";;

    [A-Z] ) echo "Буква в верхнем регистре";;

    [0-9] ) echo "Цифра";;

    * ) echo "Знак пунктуации, пробел или что-то другое";;

    esac # Допускается указыватль диапазоны символов в [квадратных скобках].


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

    # --------

    # Сейчас сценарий считывает нажатую клавишу и завершается.

    # Измените его так, чтобы сценарий продолжал отвечать на нажатия клавиш,

    # но завершался бы только после ввода символа "X".

    # Подсказка: заключите все в цикл "while".


    exit 0

    Пример 10-25. Создание меню с помощью case

    #!/bin/bash


    # Грубый пример базы данных


    clear # Очистка экрана


    echo " Список"

    echo " ------"

    echo "Выберите интересующую Вас персону:"

    echo

    echo "[E]vans, Roland"

    echo "[J]ones, Mildred"

    echo "[S]mith, Julie"

    echo "[Z]ane, Morris"

    echo


    read person


    case "$person" in

    # Обратите внимание: переменная взята в кавычки.


    "E" | "e" )

    # Пользователь может ввести как заглавную, так и строчную букву.

    echo

    echo "Roland Evans"

    echo "4321 Floppy Dr."

    echo "Hardscrabble, CO 80753"

    echo "(303) 734-9874"

    echo "(303) 734-9892 fax"

    echo "revans@zzy.net"

    echo "Старый друг и партнер по бизнесу"

    ;;

    # Обратите внимание: блок кода, анализирующий конкретный выбор, завершается

    # двумя символами "точка-с-запятой".


    "J" | "j" )

    echo

    echo "Mildred Jones"

    echo "249 E. 7th St., Apt. 19"

    echo "New York, NY 10009"

    echo "(212) 533-2814"

    echo "(212) 533-9972 fax"

    echo "milliej@loisaida.com"

    echo "Подружка"

    echo "День рождения: 11 февраля"

    ;;


    # Информация о Smith и Zane будет добавлена позднее.


    * )

    # Выбор по-умолчанию.

    # "Пустой" ввод тоже обрабатывается здесь.

    echo

    echo "Нет данных."

    ;;


    esac


    echo


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

    # --------

    # Измените этот сценарий таким образом, чтобы он не завершал работу

    #+ после вывода информации о персоне, а переходил на ожидание нового

    #+ ввода от пользователя.


    exit 0

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

    #! /bin/bash


    case "$1" in

    "") echo "Порядок использования: ${0##*/} <filename>"; exit 65;; # Параметры командной строки отсутствуют,

    # или первый параметр -- "пустой".

    # Обратите внимание на ${0##*/} это подстановка параметра ${var##pattern}. В результате получается $0.


    -*) FILENAME=./$1;; # Если имя файла (аргумент $1) начинается с "-",

    # то заменить его на ./$1

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


    * ) FILENAME=$1;; # В противном случае -- $1.

    esac


    Пример 10-26. Оператор case допускает использовать подстановку команд вместо анализируемой переменной

    #!/bin/bash

    # Подстановка команд в "case".


    case $( arch ) in # команда "arch" возвращает строку, описывающую аппаратную апхитектуру.

    i386 ) echo "Машина на базе процессора 80386";;

    i486 ) echo "Машина на базе процессора 80486";;

    i586 ) echo "Машина на базе процессора Pentium";;

    i686 ) echo "Машина на базе процессора Pentium2 или выше";;

    * ) echo "Машина на другом типе процессора";;

    esac


    exit 0

    Оператор case допускает использование шаблонных конструкций.

    Пример 10-27. Простой пример сравнения строк

    #!/bin/bash

    # match-string.sh: простое сравнение строк


    match_string ()

    {

    MATCH=0

    NOMATCH=90

    PARAMS=2 # Функция требует два входных аргумента.

    BAD_PARAMS=91


    [ $# -eq $PARAMS ] || return $BAD_PARAMS


    case "$1" in

    "$2") return $MATCH;;

    * ) return $NOMATCH;;

    esac


    }


    a=one

    b=two

    c=three

    d=two


    match_string $a # неверное число аргументов

    echo $? # 91


    match_string $a $b # не равны

    echo $? # 90


    match_string $b $d # равны

    echo $? # 0


    exit 0

    Пример 10-28. Проверка ввода

    #!/bin/bash

    # isalpha.sh: Использование "case" для анализа строк.


    SUCCESS=0

    FAILURE=-1


    isalpha () # Проверка - является ли первый символ строки символом алфавита.

    {

    if [ -z "$1" ] # Вызов функции без входного аргумента?

    then

    return $FAILURE

    fi


    case "$1" in

    [a-zA-Z]*) return $SUCCESS;; # Первый символ - буква?

    * ) return $FAILURE;;

    esac

    } # Сравните с функцией "isalpha ()" в языке C.


    isalpha2 () # Проверка - состоит ли вся строка только из символов алфавита.

    {

    [ $# -eq 1 ] || return $FAILURE


    case $1 in

    *[!a-zA-Z]*|"") return $FAILURE;;

    *) return $SUCCESS;;

    esac

    }


    isdigit () # Проверка - состоит ли вся строка только из цифр.

    { # Другими словами - является ли строка целым числом.

    [ $# -eq 1 ] || return $FAILURE


    case $1 in

    *[!0-9]*|"") return $FAILURE;;

    *) return $SUCCESS;;

    esac

    }


    check_var () # Интерфейс к isalpha

    {

    if isalpha "$@"

    then

    echo "\"$*\" начинается с алфавитного символа."

    if isalpha2 "$@"

    then # Дальнейшая проверка не имеет смысла, если первй символ не буква.

    echo "\"$*\" содержит только алфавитные символы."

    else

    echo "\"$*\" содержит по меньшей мере один не алфавитный символ."

    fi

    else

    echo "\"$*\" начинсется с не алфавитного символа ."

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

    #+ то считается, что строка содержит "не алфавитной" символ.

    fi


    echo


    }


    digit_check () # Интерфейс к isdigit ().

    {

    if isdigit "$@"

    then

    echo "\"$*\" содержит только цифры [0 - 9]."

    else

    echo "\"$*\" содержит по меньшей мере один не цифровой символ."

    fi


    echo


    }


    a=23skidoo

    b=H3llo

    c=-What?

    d=What?

    e=`echo $b` # Подстановка команды.

    f=AbcDef

    g=27234

    h=27a34

    i=27.34


    check_var $a

    check_var $b

    check_var $c

    check_var $d

    check_var $e

    check_var $f

    check_var # Вызов без параметра, что произойдет?

    #

    digit_check $g

    digit_check $h

    digit_check $i


    exit 0 # Сценарий дополнен S.C.


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

    # --------

    # Напишите функцию 'isfloat ()', которая проверяла бы вещественные числа.

    # Подсказка: Эта функция подобна функции 'isdigit ()',

    #+ надо лишь добавить анализ наличия десятичной точки.

    select

    Оператор select был заимствован из Korn Shell, и является еще одним инструментом, используемым при создании меню.

    select variable [in list] do command... break done


    Этот оператор предлагает пользователю выбрать один из представленных вариантов. Примечательно, что select по-умолчанию использует в качестве приглашения к вводу (prompt) -- PS3 (#? ), который легко изменить.

    Пример 10-29. Создание меню с помощью select

    #!/bin/bash


    PS3='Выберите ваш любимый овощ: ' # строка приглашения к вводу (prompt)


    echo


    select vegetable in "бобы" "морковь" "картофель" "лук" "брюква"

    do

    echo

    echo "Вы предпочитаете $vegetable."

    echo ";-))"

    echo

    break # если 'break' убрать, то получится бесконечный цикл.

    done


    exit 0

    Если в операторе select список in list не задан, то в качестве списка будет использоваться список аргументов ($@), передаваемый сценарию или функции.

    Сравните это с поведением оператора цикла

    for variable [in list]

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

    Пример 10-30. Создание меню с помощью select в функции

    #!/bin/bash


    PS3='Выберите ваш любимый овощ: '


    echo


    choice_of()

    {

    select vegetable

    # список выбора [in list] отсутствует, поэтому 'select' использует входные аргументы функции.

    do

    echo

    echo "Вы предпочитаете $vegetable."

    echo ";-))"

    echo

    break

    done

    }


    choice_of бобы рис морковь редис томат шпинат

    # $1 $2 $3 $4 $5 $6

    # передача списка выбора в функцию choice_of()


    exit 0

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


    Глава 11. Внутренние команды

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

    Действие, когда какая либо команда или сама командная оболочка инициирует (порождает) новый подпроцесс, что бы выполнить какую либо работу, называется ветвлением (forking) процесса. Новый процесс называется "дочерним" (или "потомком"), а породивший его процесс -- "родительским" (или "предком"). В результате и потомок и предок продолжают исполняться одновременно -- параллельно друг другу.

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

    Внутренние команды могут иметь внешние аналоги. Например, внутренняя команда Bash -- echo имеет внешний аналог /bin/echo и их поведение практически идентично.

    #!/bin/bash


    echo "Эта строка выводится внутренней командой \"echo\"."

    /bin/echo "А эта строка выводится внешней командой the /bin/echo."


    Ключевое слово (keyword) -- это зарезервированное слово, синтаксический элемент (token) или оператор. Ключевые слова имеют специальное назначение для командного интерпретатора, и фактически являются элементами синтаксиса языка командной оболочки. В качестве примера можно привести "for", "while", "do", "!", которые являются ключевыми (или зарезервированными) словами. Подобно встроенным командам, ключевые слова жестко зашиты в Bash, но в отличие от встроенных команд, ключевые слова не являются командами как таковыми, хотя при этом могут являться их составной частью[ 24 ].

    Ввод/вывод

    echo

    выводит (на stdout) выражение или содержимое переменной (см. Пример 4-1).

    echo Hello

    echo $a


    Для вывода экранированных символов, echo требует наличие ключа -e. См. Пример 5-2.

    Обычно, командв echo выводит в конце символ перевода строки. Подавить вывод это символа можно ключом -n.

    Команда echo может использоваться для передачи информации по конвейеру другим командам.

    if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]]

    then

    echo "$VAR содержит подстроку \"txt\""

    fi


    Кроме того, команда echo, в комбинации с подстановкой команд может учавствовать в операции присвоения значения переменной.

    a=`echo "HELLO" | tr A-Z a-z`

    См. так же Пример 12-15, Пример 12-2, Пример 12-32 и Пример 12-33.

    Следует запомнить, что команда echo `command` удалит все символы перевода строки, которые будут выведены командой command.

    Переменная $IFS обычно содержит символ перевода строки \n, как один из вариантов пробельного символа. Bash разобьет вывод команды command, по пробельным символам, на аргументы и передаст их команде echo, которая выведет эти аргументы, разделенные пробелами.

    bash$ ls -l /usr/share/apps/kjezz/sounds

    -rw-r--r-- 1 root root 1407 Nov 7 2000 reflect.au

    -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au


    bash$ echo `ls -l /usr/share/apps/kjezz/sounds`

    total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au


    Это встроенная команда Bash и имеет внешний аналог /bin/echo.

    bash$ type -a echo

    echo is a shell builtin

    echo is /bin/echo


    printf

    printf -- команда форматированного вывода, расширенный вариант команды echo и ограниченный вариант библиотечной функции printf() в языке C, к тому же синтаксис их несколько отдичается друг от друга.

    printf format-string... parameter...

    Это встроенная команда Bash. Имеет внешний аналог /bin/printf или /usr/bin/printf. За более подробной информацией обращайтесь к страницам справочного руководства man 1 printf по системным командам.

    Старые версии Bash могут не поддерживать команду printf.

    Пример 11-1. printf в действии

    #!/bin/bash

    # printf demo


    # От переводчика:

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

    # частей в вещественных числах, может использоваться символ "запятая"

    # (в русских локалях), поэтому данный сценарий может выдавать сообщение

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

    # Тогда попробуйте заменить в определении числа PI десятичную точку

    # на запятую -- это должно помочь. ;-)


    PI=3,14159265358979

    DecimalConstant=31373

    Message1="Поздравляю,"

    Message2="Землянин."


    echo


    printf "Число пи с точностью до 2 знака после запятой = %1.2f" $PI

    echo

    printf "Число пи с точностью до 9 знака после запятой = %1.9f" $PI # Даже округляет правильно.


    printf "\n" # Перевод строки,


    printf "Константа = \t%d\n" $DecimalConstant # Вставлен символ табуляции (\t)


    printf "%s %s \n" $Message1 $Message2


    echo


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

    # Эмуляция функции 'sprintf' в языке C.

    # Запись форматированной строки в переменную.


    echo


    Pi12=$(printf "%1.12f" $PI)

    echo "Число пи с точностью до 12 знака после запятой = $Pi12"


    Msg=`printf "%s %s \n" $Message1 $Message2`

    echo $Msg; echo $Msg


    exit 0

    Одно из полезных применений команды printf -- форматированный вывод сообщений об ошибках

    E_BADDIR=65


    var=nonexistent_directory


    error()

    {

    printf "$@" >&2

    # Форматированный вывод аргументов на stderr.

    echo

    exit $E_BADDIR

    }


    cd $var || error $"Невозможно перейти в каталог %s." "$var"


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


    read

    "Читает" значение переменной с устройства стандартного ввода -- stdin, в интерактивном режиме это означает клавиатуру. Ключ -a позволяет записывать значения в массивы (см. Пример 25-3).

    Пример 11-2. Ввод значений переменных с помощью read

    #!/bin/bash


    echo -n "дите значение переменной 'var1': "

    # Ключ -n подавляет вывод символа перевода строки.


    read var1

    # Обратите внимание -- перед именем переменной отсутствует символ '$'.


    echo "var1 = $var1"


    echo


    # Одной командой 'read' можно вводить несколько переменных.

    echo -n "дите значения для переменных 'var2' и 'var3' (через пробел или табуляцию): "

    read var2 var3

    echo "var2 = $var2 var3 = $var3"

    # Если было введено значение только одной переменной, то вторая останется "пустой".


    exit 0

    Если команде read не была передано ни одной переменной, то ввод будет осуществлен в переменную $REPLY.

    Пример 11-3. Пример использования команды read без указания переменной для ввода

    #!/bin/bash


    echo


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

    # Первый блок кода.

    echo -n "Введите значение: "

    read var

    echo "\"var\" = "$var""

    # Здесь нет ничего неожиданного.

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


    echo


    echo -n "Введите другое значение: "

    read # Команда 'read' употребляется без указания переменной для ввода,

    #+ тем не менее...

    #+ По-умолчанию ввод осуществляется в переменную $REPLY.

    var="$REPLY"

    echo "\"var\" = "$var""

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


    echo


    exit 0

    Обычно, при вводе в окне терминала с помощью команды "read", символ \ служит для экранирования символа перевода строки. Ключ -r заставляет интерпретировать символ \ как обычный символ.

    Пример 11-4. Ввод многострочного текста с помощью read

    #!/bin/bash


    echo


    echo "Введите строку, завершающуюся символом \\, и нажмите ENTER."

    echo "Затем введите вторую строку, и снова нажмите ENTER."

    read var1 # При чтении, символ "\" экранирует перевод строки.

    # первая строка \

    # вторая строка


    echo "var1 = $var1"

    # var1 = первая строка вторая строка


    # После ввода каждой строки, завершающейся символом "\",

    # вы можете продолжать ввод на другой строке.


    echo; echo


    echo "Введите другую строку, завершающуюся символом \\, и нажмите ENTER."

    read -r var2 # Ключ -r заставляет команду "read" воспринимать "\"

    # как обычный символ.

    # первая строка \


    echo "var2 = $var2"

    # var2 = первая строка \


    # Ввод данных прекращается сразу же после первого нажатия на клавишу ENTER.


    echo


    exit 0

    Команда read имеет ряд очень любопытных опций, которые позволяют выводить подсказку - приглашение ко вводу (prompt), и даже читать данные не дожидаясь нажатия на клавишу ENTER.

    # Чтение данных, не дожидаясь нажатия на клавишу ENTER.


    read -s -n1 -p "Нажмите клавишу " keypress

    echo; echo "Была нажата клавиша "\"$keypress\""."


    # -s -- подавляет эхо-вывод, т.е. ввод с клавиатуры не отображается на экране.

    # -n N -- ввод завершается автоматически, сразу же после ввода N-го символа.

    # -p -- задает вид строки подсказки - приглашения к вводу (prompt).


    # Использование этих ключей немного осложняется тем, что они должны следовать в определенном порядке.


    Ключ -n, кроме всего прочего, позволяет команде read обнаруживать нажатие курсорных и некоторых других служебных клавиш.

    Пример 11-5. Обнаружение нажатия на курсорные клавиши

    #!/bin/bash

    # arrow-detect.sh: Обнаружение нажатия на курсорные клавиши, и не только...

    # Спасибо Sandro Magi за то что показал мне -- как.


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

    # Коды клавиш.

    arrowup='\[A'

    arrowdown='\[B'

    arrowrt='\[C'

    arrowleft='\[D'

    insert='\[2'

    delete='\[3'

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


    SUCCESS=0

    OTHER=65


    echo -n "Нажмите на клавишу... "

    # Может потребоваться нажать на ENTER, если была нажата клавиша

    # не входящая в список выше.

    read -n3 key # Прочитать 3 символа.


    echo -n "$key" | grep "$arrowup" #Определение нажатой клавиши.

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"."

    exit $SUCCESS

    fi


    echo -n "$key" | grep "$arrowdown"

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"

    exit $SUCCESS

    fi


    echo -n "$key" | grep "$arrowrt"

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"О\"."

    exit $SUCCESS

    fi


    echo -n "$key" | grep "$arrowleft"

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"."

    exit $SUCCESS

    fi


    echo -n "$key" | grep "$insert"

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"Insert\"."

    exit $SUCCESS

    fi


    echo -n "$key" | grep "$delete"

    if [ "$?" -eq $SUCCESS ]

    then

    echo "Нажата клавиша \"Delete\"."

    exit $SUCCESS

    fi


    echo " Нажата какая-то другая клавиша."


    exit $OTHER


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

    # ---------

    # 1) Упростите сценарий, заменив множество if-ов

    #+ одной конструкцией 'case'.

    # 2) Добавьте определение нажатий на клавиши "Home", "End", "PgUp" и "PgDn".

    Ключ -t позволяет ограничивать время ожидания ввода командой read (см. Пример 9-4).

    Команда read может считывать значения для переменных из файла, перенаправленного на stdin. Если файл содержит не одну строку, то переменной будет присвоена только первая строка. Если команде read будет передано несколько переменных, то первая строка файла будет разбита, по пробелам, на несколько подстрок, каждая из которых будет записана в свою переменную. Будьте осторожны!

    Пример 11-6. Чтение командой read из файла через перенаправление

    #!/bin/bash


    read var1 <data-file

    echo "var1 = $var1"

    # Первая строка из "data-file" целиком записывается в переменную var1


    read var2 var3 <data-file

    echo "var2 = $var2 var3 = $var3"

    # Обратите внимание!

    # Поведение команды "read" далеко от ожидаемого!

    # 1) Произошел возврат к началу файла.

    # 2) Вместо того, чтобы последовательно читать строки из файла,

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

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

    # 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

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

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

    cat file1 file2 |

    while read line

    do

    echo $line

    done


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

    cd

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

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

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

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

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

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

    bash$ cd //

    bash$ pwd

    //

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

    pwd

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

    pushd, popd, dirs

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

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

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

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

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

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

    #!/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-8. Команда 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-9. Демонстрация команды 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-10. Принудительное завершение сеанса

    #!/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-11. Шифрование по алгоритму "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-12. Замена имени переменной на ее значение, в исходном тексте программы на языке 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

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

    set

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

    Пример 11-13. Установка значений аргументов с помощью команды 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-14. Изменение значений позиционных параметров (аргументов)

    #!/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-15. "Сброс" переменной

    #!/bin/bash

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


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

    echo "variable = $variable"


    unset variable # Сброс.

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

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


    exit 0

    export

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

    Пример 11-16. Передача переменных во вложенный сценарий 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

    Допускается объединение инициализации и экспорта переменной в одну инструкцию: 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 и выполняется переход к началу новой итерации.

    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-17. Прием опций/аргументов, передаваемых сценарию, с помощью 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-18. "Подключение" внешнего файла

    #!/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-18, представленного выше, должен находиться в том же каталоге.

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

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

    # Загружаться может командой '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-19. Пример (бесполезный) сценария, который подключает себя самого.

    #!/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, чтобы проинформировать родительский процесс об успешном завершении.

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

    exec

    Это встроенная команда интерпретатора shell, заменяет текущий процесс новым процессом, запускаемым командой exec. Обычно, когда командный интерпретатор встречает эту команду, то он порождает дочерний процесс, чтобы исполнить команду. При использовании встроенной команды exec, оболочка не порождает еще один процесс, а заменяет текущий процесс другим. Для сценария это означает его завершение сразу после исполнения команды exec. По этой причине, если вам встретится exec в сценарии, то, скорее всего это будет последняя команда в сценарии.

    Пример 11-20. Команда exec

    #!/bin/bash


    exec echo "Завершение \"$0\"." # Это завершение работы сценария.


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

    # Следующие ниже строки никогда не будут исполнены

    echo "Эта строка никогда не будет выведена на экран."


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

    # Проверьте код завершения сценария

    #+ командой 'echo $?'.

    # Он точно не будет равен 99.

    Пример 11-21. Сценарий, который запускает себя самого

    #!/bin/bash

    # self-exec.sh


    echo


    echo "Эта строка в сценарии единственная, но она продолжает выводиться раз за разом."

    echo "PID остался равным $$."

    # Демонстрация того, что команда exec не порождает дочерний процесс.


    echo "==================== Для завершения - нажмите Ctl-C ===================="


    sleep 1


    exec $0 # Запуск очередного экземпляра этого же сценария

    #+ который замещает предыдущий.


    echo "Эта строка никогда не будет выведена!" # Почему?


    exit 0

    Команда exec так же может использоваться для перенаправления. Так, команда exec <zzz-file заменит стандартное устройство ввода (stdin) файлом zzz-file (см. Пример 16-1).

    Ключ -exec команды find -- это не то же самое, что встроенная команда exec.

    shopt

    Эта команда позволяет изменять ключи (опции) оболочки на лету (см. Пример 23-1 и Пример 23-2). Ее часто можно встретить в стартовых файлах, но может использоваться и в обычных сценариях. Требует Bash версии 2 или выше.

    shopt -s cdspell

    # Исправляет незначительные орфографические ошибки в именах каталогов в команде 'cd'


    cd /hpme # Oops! Имелось ввиду '/home'.

    pwd # /home

    # Shell исправил опечатку.


    Команды

    true

    Команда возвращает код завершения -- ноль, или успешное завершение, и ничего больше.

    # Бесконечный цикл

    while true # вместо ":"

    do

    operation-1

    operation-2

    ...

    operation-n

    # Следует предусмотреть способ завершения цикла.

    done


    false

    Возвращает код завершения, свидетельствующий о неудаче, и ничего более.

    # Цикл, который никогда не будет исполнен

    while false

    do

    # Следующий код не будет исполнен никогда.

    operation-1

    operation-2

    ...

    operation-n

    done


    type [cmd]

    Очень похожа на внешнюю команду which, type cmd выводит полный путь к "cmd". В отличие от which, type является внутренней командой Bash. С опцией -a не только различает ключевые слова и внутренние команды, но и определяет местоположение внешних команд с именами, идентичными внутренним.

    bash$ type '['

    [ is a shell builtin

    bash$ type -a '['

    [ is a shell builtin

    [ is /usr/bin/[


    hash [cmds]

    Запоминает путь к заданной команде (в хэш-таблице командной оболочки), благодаря чему, при повторном обращении к ней, оболочка или сценарий уже не будет искать путь к команде в $PATH. При вызове команды hash без аргументов, просто выводит содержимое хэш-таблицы. С ключом -r -- очищает хэш-таблицу.

    help

    help COMMAND -- выводит краткую справку по использованию внутренней команды COMMAND. Аналог команды whatis, только для внутренних команд.

    bash$ help exit

    exit: exit [n]

    Exit the shell with a status of N. If N is omitted, the exit status

    is that of the last command executed.


    11.1. Команды управления заданиями

    Некоторые из нижеследующих команд принимают, в качестве аргумента, "идентификатор задания". См. таблицу в конце главы.

    jobs

    Выводит список заданий, исполняющихся в фоне. Команда ps более информативна.

    Задания и процессы легко спутать. Некоторые внутренние команды, такие как kill, disown и wait принимают в качестве параметра либо номер задания, либо номер процесса. Команды fg, bg и jobs принимают только номер задания.

    bash$ sleep 100 &

    [1] 1384


    bash $ jobs

    [1]+ Running sleep 100 &


    "1" -- это номер задания (управление заданиями осуществляет текущий командный интерпретатор), а "1384" -- номер процесса (управление процессами осуществляется системой). Завершить задание/процесс ("прихлопнуть") можно либо командой kill %1, либо kill 1384.

    Спасибо S.C.

    disown

    Удаляет задание из таблицы активных заданий командной оболочки.

    fg, bg

    Команда fg переводит задание из фона на передний план. Команда bg перезапускает приостановленное задание в фоновом режиме. Если эти команды были вызваны без указания номера задания, то они воздействуют на текущее исполняющееся задание.

    wait

    Останавливает работу сценария до тех пор пока не будут завершены все фоновые задания или пока не будет завершено задание/процесс с указанным номером задания/PID процесса. Возвращает код завершения указанного задания/процесса.

    Вы можете использовать команду wait для предотвращения преждевременного завершения сценария до того, как завершит работу фоновое задание.

    Пример 11-22. Ожидание завершения процесса перед тем как продолжить работу

    #!/bin/bash


    ROOT_UID=0 # Только пользователь с $UID = 0 имеет привилегии root.

    E_NOTROOT=65

    E_NOPARAMS=66


    if [ "$UID" -ne "$ROOT_UID" ]

    then

    echo "Для запуска этого сценария вы должны обладать привилегиями root."

    exit $E_NOTROOT

    fi


    if [ -z "$1" ]

    then

    echo "Порядок использования: `basename $0` имя-файла"

    exit $E_NOPARAMS

    fi


    echo "Обновляется база данных 'locate'..."

    echo "Это может занять продолжительное время."

    updatedb /usr & # Должна запускаться с правами root.


    wait

    # В этом месте сценарий приостанавливает свою работу до тех пор, пока не отработает 'updatedb'.

    # Желательно обновить базу данных перед тем как выполнить поиск файла.


    locate $1


    # В худшем случае, без команды wait, сценарий завершил бы свою работу до того,

    # как завершила бы работу утилита 'updatedb',

    # сделав из нее "осиротевший" процесс.


    exit 0

    Команда wait может принимать необязательный параметр -- номер задания/процесса, например, wait %1 или wait $PPID. См. таблицу идентификации заданий.

    При запуске команды в фоне из сценария может возникнуть ситуация, когда сценарий приостанавливает свою работу до тех пор, пока не будет нажата клавиша ENTER. Это, кажется, происходит с командами, делающими вывод на stdout. Такое поведение может вызывать раздражение у пользователя.

    #!/bin/bash

    # test.sh


    ls -l &

    echo "Done."

    bash$ ./test.sh

    Done.

    [bozo@localhost test-scripts]$ total 1

    -rwxr-xr-x 1 bozo bozo 34 Oct 11 15:09 test.sh

    _


    Разместив команду wait, после запуска фонового задания, можно предотвратить такое поведение сценария.

    #!/bin/bash

    # test.sh


    ls -l &

    echo "Done."

    wait

    bash$ ./test.sh

    Done.

    [bozo@localhost test-scripts]$ total 1

    -rwxr-xr-x 1 bozo bozo 34 Oct 11 15:09 test.sh

    Перенаправление вывода в файл или даже на устройство /dev/null также снимает эту проблему.

    suspend

    Действует аналогично нажатию на комбинацию клавиш Control+-Z, за исключением того, что она приостанавливает работу командной оболочки.

    logout

    Завершает сеанс работы командной оболочки, можно указать необязательный код завершения.

    times

    Выдает статистику исполнения команд в единицах системного времени, в следующем виде:

    0m0.020s 0m0.020s

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

    kill

    Принудительное завершение процесса путем передачи ему соответствующего сигнала (см. Пример 13-4).

    Пример 11-23. Сценарий, завершающий себя сам с помощью команды kill

    #!/bin/bash

    # self-destruct.sh


    kill $$ # Сценарий завершает себя сам.

    # Надеюсь вы еще не забыли, что "$$" -- это PID сценария.


    echo "Эта строка никогда не будет выведена."

    # Вместо него на stdout будет выведено сообщение "Terminated".


    exit 0


    # Какой код завершения вернет сценарий?

    #

    # sh self-destruct.sh

    # echo $?

    # 143

    #

    # 143 = 128 + 15

    # сигнал TERM

    Команда kill -l выведет список всех сигналов. Команда kill -9 -- это "жесткий kill", она используется, как правило, для завершения зависших процессов, которые упорно отказываются "умирать", отвергая простой kill. Иногда достаточно подать команду kill -15. "Процессы-зомби", т.е. процессы, "родители" которых уже завершили работу, не могут быть "убиты" таким способом (невозможно "убить" "мертвого"), рано или поздно с ними "расправится" процесс init.

    command

    Директива command COMMAND запрещает использование псевдонимов и функций с именем "COMMAND".

    Это одна из трех директив командного интерпретатора, которая влияет на обработку команд. Другие две -- builtin и enable.

    builtin

    Конструкция builtin BUILTIN_COMMAND запускает внутреннюю команду "BUILTIN_COMMAND", на время запрещая использование функций и внешних системных команд с тем же именем.

    enable

    Либо запрещает, либо разрешает вызов внутренних команд. Например, enable -n kill запрещает использование внутренней команды kill, в результате, когда интерпретатор встретит команду kill, то он вызовет внешнюю команду kill, т.е. /bin/kill.

    Команда enable -a выведет список всех внутренних команд, указывая для каждой -- действительно ли она разрешена. Команда enable -f filename загрузит внутренние команды как разделяемую библиотеку (DLL) из указанного объектного файла[ 26 ].

    autoload

    Перенесена в Bash из ksh. Если функция объявлена как autoload, то она будет загружена из внешнего файла в момент первого вызова[ 27 ]. Такой прием помогает экономить системные ресурсы.

    Обратите внимание: autoload не является частью ядра Bash. Ее необходимо загрузить с помощью команды enable -f (см. выше).

    Таблица 11-1. Идентификация заданий

    НотацияОписание
    %NНомер задания [N]
    %SВызов (командная строка) задания, которая начинается со строки S
    %?SВызов (командная строка) задания, которая содержит строку S
    %%"текущее" задание (последнее задание приостановленное на переднем плане или запущенное в фоне)
    %+"текущее" задание (последнее задание приостановленное на переднем плане или запущенное в фоне)
    %-Последнее задание
    $!Последний фоновый процесс

    Глава 12. Внешние команды, программы и утилиты

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


    12.1. Базовые команды

    Первая команда, с которой сталкиваются новички

    ls

    Команда вывода "списка" файлов. Многие недооценивают всю мощь этой скромной команды. Например, с ключом -R, рекурсивный обход дерева каталогов, командв ls выводит содержимое каталогов в виде древовидной структуры. Вот еще ряд любопытных ключей (опций) команды ls: -S -- сортировка по размеру файлов, -t -- сортировка по времени последней модификации файла и -i -- выводит список файлов с их inode (см. Пример 12-3).

    Пример 12-1. Создание оглавления диска для записи CDR, с помощью команды ls

    #!/bin/bash

    # burn-cd.sh

    # Сценарий, автоматизирующий процесс прожигания CDR.


    SPEED=2 # Если ваше "железо" поддерживает более высокую скорость записи -- можете увеличить этот параметр

    IMAGEFILE=cdimage.iso

    CONTENTSFILE=contents

    DEFAULTDIR=/opt # В этом каталоге находятся файлы, которые будут записаны на CD.

    # Каталог должен существовать.


    # Используется пакет "cdrecord" от Joerg Schilling.

    # (http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html)


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

    #+ то необходимо установить флаг suid на cdrecord

    #+ (chmod u+s /usr/bin/cdrecord, эта команда должна быть выполнена root-ом).


    if [ -z "$1" ]

    then

    IMAGE_DIRECTORY=$DEFAULTDIR

    # Каталог по-умолчанию, если иной каталог не задан из командной строки.

    else

    IMAGE_DIRECTORY=$1

    fi


    # Создать файл "table of contents".

    ls -lRF $IMAGE_DIRECTORY > $IMAGE_DIRECTORY/$CONTENTSFILE

    # Ключ "l" -- "расширенный" формат вывода списка файлов.

    # Ключ "R" -- рекурсивный обход дерева каталогов.

    # Ключ "F" -- добавляет дополнительные метки к именам файлов (к именам каталогов добавдяет оконечный символ /).

    echo "Создано оглавление."


    # Создать iso-образ.

    mkisofs -r -o $IMAGFILE $IMAGE_DIRECTORY

    echo "Создан iso-образ файловой системы ISO9660 ($IMAGEFILE)."


    # "Прожигание" CDR.

    cdrecord -v -isosize speed=$SPEED dev=0,0 $IMAGEFILE

    echo "Запись диска."

    echo "Наберитесь терпения, это может потребовать некоторого времени."


    exit 0

    cat, tac

    cat -- это акроним от concatenate, выводит содержимое списка файлов на stdout. Для объединения файлов в один файл может использоваться в комбинации с операциями перенаправления (> или >>).

    cat filename cat file.1 file.2 file.3 > file.123

    Ключ -n, команды cat, вставляет порядковые номера строк в выходном файле. Ключ -b -- нумерут только не пустые строки. Ключ -v выводит непечатаемые символы в нотации с символом ^. Ключ -s заменяет несколько пустых строк, идущих подряд, одной пустой строкой.

    см. также Пример 12-21 and Пример 12-17.

    tac -- выводит содержимое файлов в обратном порядке, от последней строки к первой.

    rev

    выводит все строки файла задом наперед на stdout. Это не то же самое, что tac. Команда rev сохраняет порядок следования строк, но переворачивает каждую строку задом наперед.

    bash$ cat file1.txt

    Это строка 1.

    Это строка 2.


    bash$ tac file1.txt

    Это строка 2.

    Это строка 1.


    bash$ rev file1.txt

    .1 акортс отЭ

    .2 акортс отЭ


    cp

    Команда копирования файлов. cp file1 file2 скопирует file1 в file2, перезаписав file2 если он уже существовал (см. Пример 12-5).

    С флагами -a и -r, или -R выполняет копирование дерева каталогов.

    mv

    Команда перемещения файла. Эквивалентна комбинации команд cp и rm. Может использоваться для перемещения большого количества файлов или для переименования каталогов. Примеры использования команды mv вы найдете в Пример 9-17 и Пример A-3.

    При использовании в неинтерактивных сценариях, команде mv следует передавать ключ -f, чтобы подавить запрос подтверждения на перемещение.

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

    bash$ mv source_directory target_directory


    bash$ ls -lF target_directory

    total 1

    drwxrwxr-x 2 bozo bozo 1024 May 28 19:20 source_directory/


    rm

    Удаляет (remove) файл(ы). Ключ -f позволяет удалять даже файлы ТОЛЬКО-ДЛЯ-ЧТЕНИЯ и подавляет запрос подтверждения на удаление.

    С ключом -r, удаляет все файлы в подкаталогах.

    rmdir

    Удаляет каталог. Удаляемый каталог не должен содержать файлов, включая "скрытые файлы"[ 28 ], иначе каталог не будет удален.

    mkdir

    Создает новый каталог. mkdir -p project/programs/December создает каталог с заданным именем в требуемом каталоге. Ключ -p позволяет создавать промежуточные родительские каталоги.

    chmod

    Изменяет атрибуты существующего файла (см. Пример 11-10).

    chmod +x filename

    # Делает файл "filename" доступным для исполнения всем пользователям.


    chmod u+s filename

    # Устанавливается бит "suid" для "filename".

    # В результате, любой пользователь сможет запустить "filename" с привилегиями владельца файла.

    # (Это не относится к файлам-сценариям на языке командной оболочки.)


    chmod 644 filename

    # Выдает право на запись/чтение владельцу файла "filename", и право на чтение

    # всем остальным

    # (восьмеричное число).


    chmod 1777 directory-name

    # Выдает право на чтение, запись и исполнение файлов в каталоге,

    # дополнительно устанавливает "sticky bit".

    # Это означает, что удалять файлы в этом каталоге могут только владельцы файлов,

    # владелец каталога и, само собой разумеется, root.


    chattr

    Изменяет атрибуты файла. Эта команда подобна команде chmod, за исключением синтаксиса вызова, и работает исключительно в файловой системе ext2.

    ln

    Создает ссылку на существующий файл. Чаще всего используется с ключом -s, что означает символическую, или "мягкую" (symbolic или "soft") ссылку. Позволяет задавать несколько имен одному и тому же файлу и превосходная альтернатива "псевдонимам" (алиасам) (см. Пример 4-6).

    ln -s oldfile newfile создает ссылку, с именем newfile, на существующий файл oldfile, .

    man, info

    Команды доступа к справочным и информационным страницам по системным командам и установленным программам и утилитам. Как правило, страницы info содержат более подробную информацию, чем man.


    12.2. Более сложные команды

    Команды для более опытных пользователей

    find

    -exec COMMAND \;

    Для каждого найденного файла, соответствующего заданному шаблону поиска, выполняет команду COMMAND. Командная строка должна завершаться последовательностью символов \; (здесь символ ";" экранирован обратным слэшем, чтобы информировать командную оболочку о том, что символ ";" должен быть передан команде find как обычный символ). Если COMMAND содержит {}, то find подставляет полное имя найденного файла вместо "{}".

    bash$ find ~/ -name '*.txt'

    /home/bozo/.kde/share/apps/karm/karmdata.txt

    /home/bozo/misc/irmeyc.txt

    /home/bozo/test-scripts/1.txt


    find /home/bozo/projects -mtime 1

    # Найти все файлы в каталоге /home/bozo/projects и вложенных подкаталогах,

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

    #

    # mtime = время последнего изменения файла

    # ctime = время последнего изменения атрибутов файла (через 'chmod' или как-то иначе)

    # atime = время последнего обращения к файлу


    DIR=/home/bozo/junk_files

    find "$DIR" -type f -atime +5 -exec rm {} \;

    # Удалить все файлы в каталоге "/home/bozo/junk_files"

    #+ к которым не было обращений в течение последних 5 дней.

    #

    # "-type filetype", где

    # f = обычный файл

    # d = каталог, и т.п.

    # (Полный список ключей вы найдете в 'man find'.)


    find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;


    # Поиск всех IP-адресов (xxx.xxx.xxx.xxx) в файлах каталога /etc.

    # Однако эта команда выводит не только IP-адреса, как этого избежать?


    # Примерно так:


    find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \

    | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'

    # [:digit:] -- один из символьных классов

    # введен в стандарт POSIX 1003.2.


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


    Не следует путать опцию -exec команды find с внутренней командой Bash -- exec.

    Пример 12-2. Badname, удаление файлов в текущем каталоге, имена которых содержат недопустимые символы и пробелы.

    #!/bin/bash


    # Удаление файлов в текущем каталоге, чьи имена содержат недопустимые символы.


    for filename in *

    do

    badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`

    # Недопустимые символы в именах файлов: + { ; " \ = ? ~ ( ) < > & * | $

    rm $badname 2>/dev/null # Сообщения об ошибках "выстреливаются" в никуда.

    done


    # Теперь "позаботимся" о файлах, чьи имена содержат пробельные символы.

    find . -name "* *" -exec rm -f {} \;

    # На место "{}", find подставит полное имя файла.

    # Символ '\' указывает на то, что ';' интерпретируется как обычный символ, а не как конец команды.


    exit 0


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

    # Строки, приведенные ниже, не будут выполнены, т.к. выше стоит команда "exit".


    # Альтернативный вариант сценария:

    find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;

    exit 0

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

    Пример 12-3. Удаление файла по его номеру inode

    #!/bin/bash

    # idelete.sh: Удаление файла по номеру inode.


    # Этот прием используется в тех случаях, когда имя файла начинается с недопустимого символа,

    #+ например, ? или -.


    ARGCOUNT=1 # Имя файла должно быть передано в сценарий.

    E_WRONGARGS=70

    E_FILE_NOT_EXIST=71

    E_CHANGED_MIND=72


    if [ $# -ne "$ARGCOUNT" ]

    then

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

    exit $E_WRONGARGS

    fi


    if [ ! -e "$1" ]

    then

    echo "Файл \""$1"\" не найден."

    exit $E_FILE_NOT_EXIST

    fi


    inum=`ls -i | grep "$1" | awk '{print $1}'`

    # inum = номер inode (index node) файла

    # Каждый файл имеет свой inode, где хранится информация о физическом расположении файла.


    echo; echo -n "Вы совершенно уверены в том, что желаете удалить \"$1\" (y/n)? "

    # Ключ '-v' в команде 'rm' тоже заставит команду вывести подобный запрос.

    read answer

    case "$answer" in

    [nN]) echo "Передумали?"

    exit $E_CHANGED_MIND

    ;;

    *) echo "Удаление файла \"$1\".";;

    esac


    find . -inum $inum -exec rm {} \;

    echo "Файл "\"$1"\" удален!"


    exit 0

    Дополнительные примеры по использованию команды find вы найдете в Пример 12-22, Пример 3-4 и Пример 10-9. В страницах справочного ркуоводства (man find) вы найдете более подробную информацию об этой достаточно сложной и мощной команде.

    xargs

    Команда передачи аргументов указанной команде. Она разбивает поток аргументов на отдельные составляющие и поочередно передает их заданной команде для обработки. Эта команда может рассматриваться как мощная замена обратным одиничным кавычкам. Зачастую, когда команды, заключенные в обратные одиночные кавычки, завершаются с ошибкой too many arguments (слишком много аргументов), использование xargs позволяет обойти это ограничение. Обычно, xargs считывает список аргументов со стандартного устройства ввода stdin или из канала (конвейера), но может считывать информацию и из файла.

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

    bash$ ls -l

    total 0

    -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1

    -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2


    bash$ ls -l | xargs

    total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2


    ls | xargs -p -l gzip -- упакует с помощью gzip все файлы в текущем каталоге, выводя запрос на подтверждение для каждого файла.

    xargs имеет очень любопытный ключ -n NN, который ограничивает количество передаваемых аргументов за один "присест" числом NN.

    ls | xargs -n 8 echo -- выведет список файлов текущего каталога в 8 колонок.

    Еще одна полезная опция -- -0, в комбинации с find -print0 или grep -lZ позволяет обрабатывать аргументы, содержащие пробелы и кавычки.

    find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

    grep -rliwZ GUI / | xargs -0 rm -f

    Обе вышеприведенные команды удалят все файлы, содержащие в своем имени комбинацию символов "GUI". (Спасибо S.C.)

    Пример 12-4. Использование команды xargs для мониторинга системного журнала

    #!/bin/bash


    # Создание временного файла мониторинга в текщем каталоге,

    # куда переписываются несколько последних строк из /var/log/messages.


    # Обратите внимание: если сценарий запускается обычным пользователем,

    # то файл /var/log/messages должен быть доступен на чтение этому пользователю.

    # #root chmod 644 /var/log/messages


    LINES=5


    ( date; uname -a ) >>logfile

    # Время и информация о системе

    echo --------------------------------------------------------------------- >>logfile

    tail -$LINES /var/log/messages | xargs | fmt -s >>logfile

    echo >>logfile

    echo >>logfile


    exit 0


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

    # --------

    # Измените сценарий таким образом, чтобы он мог отслеживать изменения в /var/log/messages

    #+ с интервалом в 20 минут.

    # Подсказка: воспользуйтесь командой "watch".

    Пример 12-5. copydir, копирование файлов из текущего каталога в другое место, с помощью xargs

    #!/bin/bash


    # Копирует все файлы из текущего каталога

    # в каталог, указанный в командной строке.


    if [ -z "$1" ] # Выход, если каталог назначения не задан.

    then

    echo "Порядок использования: `basename $0` directory-to-copy-to"

    exit 65

    fi


    ls . | xargs -i -t cp ./{} $1

    # Этот сценария является точным эквивалентом

    # cp * $1

    # если в именах файлов не содержатся пробельные символы.


    exit 0

    expr

    Универсальный обработчик выражений: вычисляет заданное выражение (аргументы должны отделяться пробелами). Выражения могут быть арифметическими, логическими или строковыми.

    expr 3 + 5

    возвратит 8

    expr 5 % 3

    возвратит 2

    expr 5 \* 3

    возвратит 15

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

    y=`expr $y + 1`

    Операция инкремента переменной, то же самое, что и let y=y+1, или y=$(($y+1)). Пример подстановки арифметических выражений.

    z=`expr substr $string $position $length`

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

    Пример 12-6. Пример работы с expr

    #!/bin/bash


    # Демонстрация некоторых приемов работы с командой 'expr'

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


    echo


    # Арифметические операции

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


    echo "Арифметические операции"

    echo

    a=`expr 5 + 3`

    echo "5 + 3 = $a"


    a=`expr $a + 1`

    echo

    echo "a + 1 = $a"

    echo "(инкремент переменной)"


    a=`expr 5 % 3`

    # остаток от деления (деление по модулю)

    echo

    echo "5 mod 3 = $a"


    echo

    echo


    # Логические операции

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


    # Возвращает 1 если выражение истинноо, 0 -- если ложно,

    #+ в противоположность соглашениям, принятым в Bash.


    echo "Логические операции"

    echo


    x=24

    y=25

    b=`expr $x = $y` # Сравнение.

    echo "b = $b" # 0 ( $x -ne $y )

    echo


    a=3

    b=`expr $a \> 10`

    echo 'b=`expr $a \> 10`, поэтому...'

    echo "Если a > 10, то b = 0 (ложь)"

    echo "b = $b" # 0 ( 3 ! -gt 10 )

    echo


    b=`expr $a \< 10`

    echo "Если a < 10, то b = 1 (истина)"

    echo "b = $b" # 1 ( 3 -lt 10 )

    echo

    # Обратите внимание на необходимость экранирования операторов.


    b=`expr $a \<= 3`

    echo "Если a <= 3, то b = 1 (истина)"

    echo "b = $b" # 1 ( 3 -le 3 )

    # Существует еще оператор "\>=" (больше или равно).


    echo

    echo


    # Операции сравнения

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


    echo "Операции сравнения"

    echo

    a=zipper

    echo "a is $a"

    if [ `expr $a = snap` ]

    then

    echo "a -- это не zipper"

    fi


    echo

    echo


    # Операции со строками

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


    echo "Операции со строками"

    echo


    a=1234zipper43231

    echo "Строка над которой производятся операции: \"$a\"."


    # length: длина строки

    b=`expr length $a`

    echo "длина строки \"$a\" равна $b."


    # index: позиция первого символа подстроки в строке

    b=`expr index $a 23`

    echo "Позиция первого символа \"2\" в строке \"$a\" : \"$b\"."


    # substr: извлечение подстроки, начиная с заданной позиции, указанной длины

    b=`expr substr $a 2 6`

    echo "Подстрока в строке \"$a\", начиная с позиции 2,\

    и длиной в 6 символов: \"$b\"."


    # При выполнении поиска по шаблону, по-умолчанию поиск

    #+ начинается с ***начала*** строки.

    #

    # Использование регулярных выражений

    b=`expr match "$a" '[0-9]*'` # Подсчет количества цифр.

    echo Количество цифр с начала строки \"$a\" : $b.

    b=`expr match "$a" '\([0-9]*\)'` # Обратите внимание на экранирование круглых скобок

    # == ==

    echo "Цифры, стоящие в начале строки \"$a\" : \"$b\"."


    echo


    exit 0

    Вместо оператора match можно использовать оператор :. Например, команда b=`expr $a : [0-9]*` является точным эквивалентом для b=`expr match $a [0-9]*` в примере, рассмотренном выше.

    #!/bin/bash


    echo

    echo "Операции над строками с использованием конструкции \"expr \$string : \" "

    echo "========================================================================"

    echo


    a=1234zipper5FLIPPER43231


    echo "Строка, над которой выполняются операции: \"`expr "$a" : '\(.*\)'`\"."

    # Экранирование круглых скобок в шаблоне == ==


    # Если скобки не экранировать...

    #+ то 'expr' преобразует строковый операнд в целое число.


    echo "Длина строки \"$a\" равна `expr "$a" : '.*'`." # Длина строки


    echo "Количество цифр с начала строки \"$a\" равно `expr "$a" : '[0-9]*'`."


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


    echo


    echo "Цифры, стоящие в начале строки \"$a\" : `expr "$a" : '\([0-9]*\)'`."

    # == ==

    echo "Первые 7 символов в строке \"$a\" : `expr "$a" : '\(.......\)'`."

    # ====== == ==

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

    #

    echo "Последние 7 символов в строке \"$a\" : `expr "$a" : '.*\(.......\)'`."

    # ========= оператор конца строки ^^

    # (фактически означает переход через любое количество символов, пока

    #+ не будет найдена требуемая подстрока)


    echo


    exit 0


    Этот пример демонстрирует необходимость экранирования оператора группировки -- \( ... \) в регулярных выражениях, при поиске по шаблону командой expr.

    Perl, sed и awk имеют в своем распоряжении более мощный аппарат анализа строк. Коротенький скрипт на sed или awk, внутри сценария (см. Section 33.2) -- значительно более привлекательная альтернатива использованию expr при анализе строк.

    Дополнительные примеры, по обработке строк, вы найдете в Section 9.2.


    12.3. Команды для работы с датой и временем

    Время/дата и измерение интервалов времени

    date

    Команда date без параметров выводит дату и время на стандартное устройство вывода stdout. Она становится гораздо интереснее при использовании дополнительных ключей форматирования вывода.

    Пример 12-7. Команда date

    #!/bin/bash

    # Примеры использования команды 'date'


    echo "Количество дней, прошедших с начала года: `date +%j`."

    # Символ '+' обязателен при использовании форматирующего аргумента

    # %j, возвращающего количество дней, прошедших с начала года.


    echo "Количество секунд, прошедших с 01/01/1970 : `date +%s`."

    # %s количество секунд, прошедших с начала "эпохи UNIX",

    #+ но насколько этот ключ полезен?


    prefix=temp

    suffix=`eval date +%s` # Ключ "+%s" характерен для GNU-версии 'date'.

    filename=$prefix.$suffix

    echo $filename

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

    #+ даже лучше, чем с использованием $$.


    # Дополнительную информацию вы найдете в 'man date'.


    exit 0

    Ключ -u дает UTC время (Universal Coordinated Time -- время по Гринвичу).

    bash$ date

    Fri Mar 29 21:07:39 MST 2002


    bash$ date -u

    Sat Mar 30 04:07:42 UTC 2002


    zdump

    Отображает время для указанной временной зоны.

    bash$ zdump EST

    EST Tue Sep 18 22:09:22 2001 EST


    time

    Выводит подробную статистику по исполнению некоторой команды.

    time ls -l / даст нечто подобное:

    0.00user 0.01system 0:00.05elapsed 16%CPU (0avgtext+0avgdata 0maxresident)k

    0inputs+0outputs (149major+27minor)pagefaults 0swaps


    См. так же очень похожую команду times, обсуждавшуюся в предыдущем разделе.

    Начиная с версии 2.0 Bash, команда time стала зарезервированным словом интерпретатора, с несколько измененным поведением в конвейере.

    touch

    Утилита устанавливает время последнего обращения/изменения файла в текущее системное время или в заданное время, но так же может использоваться для создания нового пустого файла. Команда touch zzz создаст новый пустой файл с именем zzz, если перед этим файл zzz отсутствовал. Кроме того, такие пустые файлы могут использоваться для индикации, например, времени последнего изменения в проекте.

    Эквивалентом команды touch могут служить : >> newfile или >> newfile (для обычных файлов).

    at

    Команда at -- используется для запуска заданий в заданное время. В общих чертах она напоминает crond, однако, at используется для однократного запуска набора команд.

    at 2pm January 15 -- попросит ввести набор команд, которые необходимо запустить в указанное время. Эти команды должны быть совместимыми со сценариями командной оболочки. Ввод завершается нажатием комбинации клавиш Ctl-D.

    Ключ -f или операция перенаправления ввода (<), заставляет at прочитать список команд из файла. Этот файл должен представлять из себя обычный сценарий, на языке командной оболочки и, само собой разумеется, такой сценарий должен быть неинтерактивным. Может использоваться совместно с командой run-parts для запуска различных наборов сценариев.

    bash$ at 2:30 am Friday < at-jobs.list

    job 2 at 2000-10-27 02:30


    batch

    Команда batch, управляющая запуском заданий, напоминает команду at, но запускает список команд только тогда, когда загруженность системы упадет ниже .8. Подобно команде at, с ключом -f, может считывать набор команд из файла.

    cal

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

    sleep

    Приостанавливает исполнение сценария на заданное количество секунд, ничего не делая. Может использоваться для синхронизации процессов, запущенных в фоне, проверяя наступление ожидаемого события так часто, как это необходимо. Например, Пример 29-6.

    sleep 3

    # Пауза, длительностью в 3 секунды.


    Команда sleep по-умолчанию принимает количество секунд, но ей можно передать и количество часов и минут и даже дней.

    sleep 3 h

    # Приостановка на 3 часа!


    Для запуска команд через заданные интервалы времени лучше использовать watch .

    usleep

    Microsleep (здесь символ "u" должен читаться как буква греческого алфавита -- "мю", или префикс микро). Это то же самое, что и sleep, только интервал времени задается в микросекундах. Может использоваться для очень тонкой синхронизации процессов.

    usleep 30

    # Приостановка на 30 микросекунд.


    Эта команда является частью пакета initscripts/rc-scripts в дистрибутиве Red Hat.

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

    hwclock, clock

    Команда hwclock используется для получения доступа или коррекции аппаратных часов компьютера. С некоторыми ключами требует наличия привилегий root. Сенарий /etc/rc.d/rc.sysinit использует команду hwclock для установки системного времени во время загрузки.

    Команда clock -- это синоним команды hwclock.


    12.4. Команды обработки текста

    sort

    Сортирует содержимое файла, часто используется как промежуточный фильтр в конвейерах. Эта команда сортирует поток текста в порядке убывания или возрастания, в зависимости от заданных опций. Ключ -m используется для сортировки и объединения входных файлов. В странице info перечислено большое количество возможных вариантов ключей. См. Пример 10-9, Пример 10-10 и Пример A-9.

    tsort

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

    uniq

    Удаляет повторяющиеся строки из отсортированного файла. Эту команду часто можно встретить в конвейере с командой sort.

    cat list-1 list-2 list-3 | sort | uniq > final.list

    # Содержимое файлов,

    # сортируется,

    # затем удаляются повторяющиеся строки,

    # и результат записывается в выходной файл.


    Ключ -c выводит количество повторяющихся строк.

    bash$ cat testfile

    Эта строка встречается только один раз.

    Эта строка встречается дважды.

    Эта строка встречается дважды.

    Эта строка встречается трижды.

    Эта строка встречается трижды.

    Эта строка встречается трижды.


    bash$ uniq -c testfile

    1 Эта строка встречается только один раз.

    2 Эта строка встречается дважды.

    3 Эта строка встречается трижды.


    bash$ sort testfile | uniq -c | sort -nr

    3 Эта строка встречается трижды.

    2 Эта строка встречается дважды.

    1 Эта строка встречается только один раз.


    Команда sort INPUTFILE | uniq -c | sort -nr выводит статистику встречаемости строк в файле INPUTFILE (ключ -nr, в команде sort, означает сортировку в порядке убывания). Этот шаблон может с успехом использоваться при анализе файлов системного журнала, словарей и везде, где необходимо проанализировать лексическую структуру документа.

    Пример 12-8. Частота встречаемости отдельных слов

    #!/bin/bash

    # wf.sh: "Сырой" анализ частоты встречаемости слова в текстовом файле.


    ARGS=1

    E_BADARGS=65

    E_NOFILE=66


    if [ $# -ne "$ARGS" ] # Файл для анализа задан?

    then

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

    exit $E_BADARGS

    fi


    if [ ! -f "$1" ] # Проверка существования файла.

    then

    echo "Файл \"$1\" не найден."

    exit $E_NOFILE

    fi


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

    # main ()

    sed -e 's/\.//g' -e 's/ /\

    /g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr

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

    # Подсчет количества вхождений


    # Точки и пробелы заменяются

    #+ символами перевода строки,

    #+ затем символы переводятся в нижний регистр

    #+ и наконец подсчитывается количество вхождений,

    #+ и выполняется сортировка по числу вхождений.

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


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

    # ---------

    # 1) Добавьте команду 'sed' для отсечения других знаков пунктуации, например, запятых.

    # 2) Добавьте удаление лишних пробелов и других пробельных символов.

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

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


    exit 0

    bash$ cat testfile

    Эта строка встречается только один раз.

    Эта строка встречается дважды.

    Эта строка встречается дважды.

    Эта строка встречается трижды.

    Эта строка встречается трижды.

    Эта строка встречается трижды.


    bash$ ./wf.sh testfile

    6 Эта

    6 встречается

    6 строка

    3 трижды

    2 дважды

    1 только

    1 один

    1 раз


    expand, unexpand

    Команда expand преобразует символы табуляции в пробелы. Часто используется в конвейерной обработке текста.

    Команда unexpand преобразует пробелы в символы табуляции. Т.е. она является обратной по отношению к команде expand.

    cut

    Предназначена для извлечения отдельных полей из текстовых файлов. Напоминает команду print $N в awk, но более ограничена в своих возможностях. В простейших случаях может быть неплохой заменой awk в сценариях. Особую значимость, для команды cut, представляют ключи -d (разделитель полей) и -f (номер(а) поля(ей)).

    Использование команды cut для получения списка смонтированных файловых систем:

    cat /etc/mtab | cut -d ' ' -f1,2


    Использование команды cut для получения версии ОС и ядра:

    uname -a | cut -d" " -f1,3,11,12


    Использование команды cut для извлечения заголовков сообщений из электронных писем:

    bash$ grep '^Subject:' read-messages | cut -c10-80

    Re: Linux suitable for mission-critical apps?

    MAKE MILLIONS WORKING AT HOME3

    Spam complaint

    Re: Spam complaint


    Использование команды cut при разборе текстового файла:

    # Список пользователей в /etc/passwd.


    FILENAME=/etc/passwd


    for user in $(cut -d: -f1 $FILENAME)

    do

    echo $user

    done


    # Спсибо Oleg Philon за этот пример.


    cut -d ' ' -f2,3 filename эквивалентно awk -F'[ ]' '{ print $2, $3 }' filename

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

    paste

    Используется для объединения нескольких файлов в один многоколоночный файл.

    join

    Может рассматриваться как команда, родственная команде paste. Эта мощная утилита позволяет объединять два файла по общему полю, что представляет собой упрощенную версию реляционной базы данных.

    Команда join оперирует только двумя файлами и объедияет только те строки, которые имеют общее поле (обычно числовое), результат объединения выводится на stdout. Объединяемые файлы должны быть отсортированы по ключевому полю.

    File: 1.data


    100 Shoes

    200 Laces

    300 Socks


    File: 2.data


    100 $40.00

    200 $1.00

    300 $2.00


    bash$ join 1.data 2.data

    File: 1.data 2.data


    100 Shoes $40.00

    200 Laces $1.00

    300 Socks $2.00


    На выходе ключевое поле встречается только один раз.

    head

    Выводит начальные строки из файла на stdout (по-умолчанию -- 10 строк, но это число можно задать иным). Эта команда имеет ряд интересных ключей.

    Пример 12-9. Какие из файлов являются сценариями?

    #!/bin/bash

    # script-detector.sh: Отыскивает файлы сценариев в каталоге.


    TESTCHARS=2 # Проверяются первые два символа.

    SHABANG='#!' # Сценарии как правило начинаются с "sha-bang."


    for file in * # Обход всех файлов в каталоге.

    do

    if [[ `head -c$TESTCHARS "$file"` = "$SHABANG" ]]

    # head -c2 #!

    # Ключ '-c' в команде "head" выводит заданное

    #+ количество символов, а не строк.

    then

    echo "Файл \"$file\" -- сценарий."

    else

    echo "Файл \"$file\" не является сценарием."

    fi

    done

    exit 0

    Пример 12-10. Генератор 10-значных случайных чисел

    #!/bin/bash

    # rnd.sh: Генератор 10-значных случайных чисел


    # Автор: Stephane Chazelas.


    head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'


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


    # Описание

    # --------


    # head:

    # -c4 -- первые 4 байта.


    # od:

    # -N4 ограничивает вывод 4-мя байтами.

    # -tu4 беззнаковый десятичный формат вывода.


    # sed:

    # -n, в комбинации с флагом "p", в команде "s",

    # выводит только совпадающие с шаблоном строки.


    # Автор сценария описывает действия 'sed' таким образом:


    # head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'

    # ----------------------------------> |


    # Передает вывод в "sed" --------> |

    # пусть это будет 0000000 1198195154\n


    # sed начинает читать символы: 0000000 1198195154\n.

    # Здесь он находит символ перевода строки,

    # таким образом он получает строку (0000000 1198195154).

    # Затем он просматривает <диапазон><действие>. Первый и единственный -- это


    # диапазон действие

    # 1 s/.* //p


    # Номер строки попадает в заданный лиапазон, так что теперь он приступает к выполнению действия:

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

    # ("0000000 ") "ничем" (//), и если замена произведена -- выводит результат

    # ("p" -- это флаг команды "s", а не команда "p", которая имеет иное значение).


    # теперь sed готов продолжить чтение входного потока. (Обратите внимание:

    # если опустить ключ -n, то sed выведет строку еще раз)


    # Теперь sed дочитывает остаток строки.

    # Он готов приступить к анализу 2-й строки (которая отмечена '$'

    # как последняя).

    # Поскольку строка не попадает в заданный <диапазон>, на этом обработка прекращается.


    # Проще говоря, команда sed означает:

    # "В первой строке удалить любые символы, вплоть до последнего встреченного пробела,

    # и затем вывести остаток."


    # Сделать это можно более простым способом:

    # sed -e 's/.* //;q'


    # Где, заданы два <диапазона><действия> (можно записать и по другому

    # sed -e 's/.* //' -e q):


    # диапазон действие

    # ничего (для совпадающих строк) s/.* //

    # ничего (для совпадающих строк) q (quit)


    # Здесь sed считывает только первую строку.

    # Выполняет оба действия, и выводит строку перед завершением

    # (действие "q"), поскольку ключ "-n" опущен.


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


    # Простая альтернатива:

    # head -c4 /dev/urandom| od -An -tu4


    exit 0

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

    tail

    Выводит последние строки из файла на stdout (по-умолчанию -- 10 строк). Обычно используется для мониторинга системных журналов. Ключ -f, позволяет вести непрерывное наблюдение за добавляемыми строками в файл.

    Пример 12-11. Мониторинг системного журнала с помощью tail

    #!/bin/bash


    filename=sys.log


    cat /dev/null > $filename; echo "Создание / очистка временного файла."

    # Если файл отсутствует, то он создается,

    #+ и очищается, если существует.

    # : > filename и > filename дают тот же эффект.


    tail /var/log/messages > $filename

    # Файл /var/log/messages должен быть доступен для чтения.


    echo "В файл $filename записаны последние строки из /var/log/messages."


    exit 0

    См. также Пример 12-4, Пример 12-30 и Пример 29-6.

    grep

    Многоцелевая поисковая утилита, использующая регулярные выражения. Изначально это была команда в древнем строчном редакторе ed, g/re/p, что означает -- global - regular expression - print.

    grep pattern [file...]

    Поиск участков текста в файле(ах), соответствующих шаблону pattern, где pattern может быть как обычной строкой, так и регулярным выражением.

    bash$ grep '[rst]ystem.$' osinfo.txt

    The GPL governs the distribution of the Linux operating system.


    Если файл(ы) для поиска не задан, то команда grep работает как фильтр для устройства stdout, например в конвейере.

    bash$ ps ax | grep clock

    765 tty1 S 0:00 xclock

    901 pts/1 S 0:00 grep clock


    -i -- выполняется поиск без учета регистра символов.

    -w -- поиск совпадений целого слова.

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

    -r -- (рекурсивный поиск) поиск выполняется в текущем каталоге и всех вложенных подкаталогах.

    The -n option lists the matching lines, together with line numbers.

    bash$ grep -n Linux osinfo.txt

    2:This is a file containing information about Linux.

    6:The GPL governs the distribution of the Linux operating system.


    -v (или --invert-match) -- выводит только строки, не содержащие совпадений.

    grep pattern1 *.txt | grep -v pattern2


    # Выводятся строки из "*.txt", совпадающие с "pattern1",

    # но ***не*** совпадающие с "pattern2".


    -c (--count) -- выводит количество совпадений без вывода самих совпадений.

    grep -c txt *.sgml # (количество совпадений с "txt" в "*.sgml" файлах)


    # grep -cz .

    # ^ точка

    # означает подсчет (-c) непустых ("." -- содержащих хотя бы один символ) элементов,

    # разделенных нулевыми байтами (-z)

    #

    printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz . # 4

    printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$' # 5

    printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^' # 5

    #

    printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$' # 9

    # По-умолчанию, в качестве разделителя, принимается символ перевода строки (\n).


    # Обратите внимание: ключ -z характерен для GNU-версии "grep".


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


    Если grep вызывается для поиска по группе файлов, то вывод будет содержать указание на имена файлов, в которых найдены совпадения.

    bash$ grep Linux osinfo.txt misc.txt

    osinfo.txt:This is a file containing information about Linux.

    osinfo.txt:The GPL governs the distribution of the Linux operating system.

    misc.txt:The Linux operating system is steadily gaining in popularity.


    Для того, чтобы заставить grep выводить имя файла, когда поиск производится по одному-единственному файлу, достаточно указать устройство /dev/null в качестве второго файла.

    bash$ grep Linux osinfo.txt /dev/null

    osinfo.txt:This is a file containing information about Linux.

    osinfo.txt:The GPL governs the distribution of the Linux operating system.


    Если совпадение было найдено, то grep возвращает код завершения -- 0, это может оказаться полезным при выполнении поиска в условных операторах ( в таких случаях особый интерес может представлять ключ -q, который подавляет вывод).

    SUCCESS=0 # если найдено совпадение

    word=Linux

    filename=data.file


    grep -q "$word" "$filename" # "-q" -- подавляет вывод на stdout.


    if [ $? -eq $SUCCESS ]

    then

    echo "Образец $word найден в $filename"

    else

    echo "Образец $word в файле $filename не найден"

    fi


    Пример 29-6 -- пример поиска заданного образца в системном журнале, с помощью grep.

    Пример 12-12. Сценарий-эмулятор "grep"

    #!/bin/bash

    # grp.sh: Очень "грубая" реализация 'grep'.


    E_BADARGS=65


    if [ -z "$1" ] # Проверка наличия аргументов.

    then

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

    exit $E_BADARGS

    fi


    echo


    for file in * # Обход всех файлов в $PWD.

    do

    output=$(sed -n /"$1"/p $file) # Подстановка команд.


    if [ ! -z "$output" ] # Что произойдет, если кавычки вокруг "$output" убрать?

    then

    echo -n "$file: "

    echo $output

    fi # эквивалент: sed -ne "/$1/s|^|${file}: |p"


    echo

    done


    echo


    exit 0


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

    # ---------

    # 1) Добавьте вывод символов перевода строки, если найдено более одного совпадения в любом из файлов.

    # 2) Добавьте обработку различных ключей.

    egrep -- то же самое, что и grep -E. Эта команда использует несколько отличающийся, расширенный набор регулярных выражений, что позволяет выполнять поиск более гибко.

    fgrep -- то же самое, что и grep -F. Эта команда выполняет поиск строк символов (не регулярных выражений), что несколько увеличивает скорость поиска.

    Утилита agrep имеет более широкие возможности поиска приблизительных совпадений. Образец поиска может отличаться от найденной строки на указанное число символов.

    Для поиска по сжатым файлам следует использовать утилиты zgrep, zegrep или zfgrep. Они с успехом могут использоваться и для не сжатых файлов, но в этом случае они уступают в скорости обычным grep, egrep и fgrep. Они очень удобны при выполнении поиска по смешенному набору файлов -- когда одни файлы сжаты, а другие нет.

    Для поиска по bzip-файлам используйте bzgrep.

    look

    Команда look очень похожа на grep, и предназначена для поиска по "словарям" -- отсортированным файлам. По-умолчанию, поиск выполняется в файле /usr/dict/words, но может быть указан и другой словарь.

    Пример 12-13. Поиск слов в словаре

    #!/bin/bash

    # lookup: Выполняется поиск каждого слова из файла в словаре.


    file=words.data # Файл с искомыми словами.


    echo


    while [ "$word" != end ] # Последнее слово в файле.

    do

    read word # Из файла, потому, что выполнено перенаправление в конце цикла.

    look $word > /dev/null # Подавление вывода строк из словаря.

    lookup=$? # Код возврата команды 'look'.


    if [ "$lookup" -eq 0 ]

    then

    echo "Слово \"$word\" найдено."

    else

    echo "Слово \"$word\" не найдено."

    fi


    done <"$file" # Перенаправление ввода из файла $file, так что "чтение" производится оттуда.


    echo


    exit 0


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

    # Строки, расположенные ниже не будут исполнены, поскольку выше стоит команда "exit".


    # Stephane Chazelas предложил более короткий вариант:


    while read word && [[ $word != end ]]

    do if look "$word" > /dev/null

    then echo "Слово \"$word\" найдено."

    else echo "Слово \"$word\" не найдено."

    fi

    done <"$file"


    exit 0

    sed, awk

    Скриптовые языки, специально разработанные для анализа текстовых данных.

    sed

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

    awk

    Утилита контекстного поиска и преобразования текста, замечательный инструмент для извлечения и/или обработки полей (колонок) в структурированных текстовых файлах. Синтаксис awk напоминает язык C.

    wc

    wc -- "word count", счетчик слов в файле или в потоке:

    bash $ wc /usr/doc/sed-3.02/README

    20 127 838 /usr/doc/sed-3.02/README

    [20 строк 127 слов 838 символов]


    wc -w подсчитывает только слова.

    wc -l подсчитывает только строки.

    wc -c подсчитывает только символы.

    wc -L возвращает длину наибольшей строки.

    Подсчет количества .txt-файлов в текущем каталоге с помощью wc:

    $ ls *.txt | wc -l

    # Эта команда будет работать, если ни в одном из имен файлов "*.txt" нет символа перевода строки.


    # Альтернативный вариант:

    # find . -maxdepth 1 -name \*.txt -print0 | grep -cz .

    # (shopt -s nullglob; set -- *.txt; echo $#)


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


    Подсчет общего размера файлов, чьи имена начинаются с символов, в диапазоне d - h

    bash$ wc [d-h]* | grep total | awk '{print $3}'

    71832


    От переводчика: в случае, если у вас локаль отлична от "C", то вышеприведенная команда может не дать результата, поскольку wc вернет не слово "total", в конце вывода, а "итого". Тогда можно попробовать несколько измененный вариант:

    bash$ wc [d-h]* | grep итого | awk '{print $3}'

    71832


    Использование wc для подсчета количества вхождений слова "Linux" в основной исходный файл с текстом этого руководства.

    bash$ grep Linux abs-book.sgml | wc -l

    50


    См. также Пример 12-30 и Пример 16-7.

    Отдельные команды располагают функциональностью wc в виде своих ключей.

    ... | grep foo | wc -l

    # Часто встречающаяся конструкция, которая может быть сокращена.


    ... | grep -c foo

    # Ключ "-c" ("--count") команды grep.


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


    tr

    Замена одних символов на другие.

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

    Команда tr "A-Z" "*" <filename или tr A-Z \* <filename заменяет все символы верхнего регистра в filename на звездочки (вывод производится на stdout). В некоторых системах этот вариант может оказаться неработоспособным, тогда попробуйте tr A-Z '[**]'.

    Ключ -d удаляет символы из заданного диапазона.

    echo "abcdef" # abcdef

    echo "abcdef" | tr -d b-d # aef


    tr -d 0-9 <filename

    # Удалит все цифровые символы из файла "filename".


    Ключ --squeeze-repeats (-s) удалит все повторяющиеся последовательности символов. Может использоваться для удаления лишних пробельных символов.

    bash$ echo "XXXXX" | tr --squeeze-repeats 'X'

    X


    Ключ -c "complement" заменит символы в соответствии с шаблоном. Этот ключ воздействует только на те символы, которые НЕ соответствуют заданному шаблону.

    bash$ echo "acfdeb123" | tr -c b-d +

    +c+d+b++++


    Обратите внимание: команда tr корректно распознает символьные классы POSIX[ 29 ].

    bash$ echo "abcd2ef1" | tr '[:alpha:]' -

    ----2--1


    Пример 12-14. toupper: Преобразование символов в верхний регистр.

    #!/bin/bash

    # Преобразование символов в верхний регистр.


    E_BADARGS=65


    if [ -z "$1" ] # Стандартная проверка командной строки.

    then

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

    exit $E_BADARGS

    fi


    tr a-z A-Z <"$1"


    # Тот же эффект можно получить при использовании символьных классов POSIX:

    # tr '[:lower:]' '[:upper:]' <"$1"

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


    exit 0

    Пример 12-15. lowercase: Изменение имен всех файлов в текущем каталоге в нижний регистр.

    #! /bin/bash

    #

    # Изменит все имена файлов в текущем каталоге в нижнй регистр.

    #


    for filename in * # Обход всех файлов в каталоге.

    do

    fname=`basename $filename`

    n=`echo $fname | tr A-Z a-z` # Перевести символы в нижний регистр.

    if [ "$fname" != "$n" ] # Переименовать только те файлы, имена которых изменились.

    then

    mv $fname $n

    fi

    done


    exit 0


    # Сироки приведенные ниже не будут исполняться, поскольку выше стоит команда "exit".

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

    # Запустите эту часть сценария, удалив строки , стоящие выше.


    # Сценарий, приведенный выше, не работает с именами файлов, содержащими пробелы или символы перевода строки.


    # В связи с этим, Stephane Chazelas предложил следующий вариант:


    for filename in * # Нет необходимости использовать basename,

    # поскольку "*" возвращает имена, не содержащие "/".

    do n=`echo "$filename/" | tr '[:upper:]' '[:lower:]'`

    # символьные классы POSIX.

    # Завершающий слэш добавлен для того, чтобы символ перевода строки

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

    # Подстановка переменной:

    n=${n%/} # Удаление завершающего слэша, добавленного выше.

    [[ $filename == $n ]] || mv "$filename" "$n"

    # Проверка -- действительно ли изменилось имя файла.

    done


    exit 0

    Пример 12-16. du: Преобразование текстового файла из формата DOS в формат UNIX.

    #!/bin/bash

    # du.sh: Преобразование текстового файла из формата DOS в формат UNIX.


    E_WRONGARGS=65


    if [ -z "$1" ]

    then

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

    exit $E_WRONGARGS

    fi


    NEWFILENAME=$1.unx


    CR='\015' # Возврат каретки.

    # Строки в текстовых файлах DOS завершаются комбинацией символов CR-LF.


    tr -d $CR < $1 > $NEWFILENAME

    # Удалить символы CR и записать в новый файл.


    echo "Исходный текстовый файл: \"$1\"."

    echo "Преобразованный файл: \"$NEWFILENAME\"."


    exit 0

    Пример 12-17. rot13: Сверхслабое шифрование по алгоритму rot13.

    #!/bin/bash

    # rot13.sh: Классический алгоритм шифрования rot13,

    # который способен "расколоть" даже 3-х летний ребенок.


    # Порядок использования: ./rot13.sh filename

    # или ./rot13.sh <filename

    # или ./rot13.sh и ввести текст с клавиатуры (stdin)


    cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M' # "a" заменяется на "n", "b" на "o", и т.д.

    # Конструкция 'cat "$@"'

    #+ позволяет вводить данные как со stdin, так и из файла.


    exit 0

    Пример 12-18. Более "сложный" шифр

    #!/bin/bash

    # crypto-quote.sh: Ограниченное шифрование


    # Шифрование ограничивается простой заменой одних алфавитных символов другими.

    # Результат очень похож на шифры-загадки


    key=ETAOINSHRDLUBCFGJMQPVWZYXK

    # Здесь, "key" -- ни что иное, как "перемешанный" алфавит.

    # Изменение ключа "key" приведет к изменению шифра.


    # Конструкция 'cat "$@"' позволяет вводить данные как со stdin, так и из файла.

    # Если используется stdin, то ввод должен завершаться комбинацией Control-D.

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


    cat "$@" | tr "a-z" "A-Z" | tr "A-Z" "$key"

    # | в верхний регистр | шифрование

    # Такой прием позволяет шифровать как символы в верхнем регистре, так и в нижнем.

    # Неалфавитные символы остаются без изменений.


    # Попробуйте зашифровать какой либо текст, например

    # "Nothing so needs reforming as other people's habits."

    # --Mark Twain

    #

    # Результат будет:

    # "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."

    # --BEML PZERC


    # Для дешифрации можно использовать следующую комбинацию:

    # cat "$@" | tr "$key" "A-Z"


    # Этот нехитрый шифр может быть "взломан" 12-ти летним ребенком

    #+ с помощью карандаша и бумаги.


    exit 0

    Различные версии tr

    Утилита tr имеет две, исторически сложившиеся, версии. BSD-версия не использует квадратные скобки (tr a-z A-Z), в то время как SysV-версия использует их (tr '[a-z]' '[A-Z]'). GNU-версия утилиты tr напоминает версию BSD, но диапазоны символов обязательно должны заключаться в квадратные скобки.

    fold

    Выравнивает текст по ширине, разрывая, если это необходимо, слова. Особый интерес представляет ключ -s, который производит перенос строк по пробелам, стараясь не разрывать слова. (см. Пример 12-19 и Пример A-2).

    fmt

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

    Пример 12-19. Отформатированный список файлов.

    #!/bin/bash


    WIDTH=40 # 40 символов в строке.


    b=`ls /usr/local/bin` # Получить список файлов...


    echo $b | fmt -w $WIDTH


    # То же самое можно выполнить командой

    # echo $b | fold - -s -w $WIDTH

    exit 0

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

    Очень мощной альтернативой утилите fmt, является утилита par (автор Kamil Toman), которую вы сможете найти на http://www.cs.berkeley.edu/~amc/Par/.

    col

    Эта утилита с обманчивым названием удаляет из входного потока символы обратной подачи бумаги (код ESC 7). Она так же пытается заменить пробелы на табуляции. Основная область применения утилиты col -- фильтрация вывода отдельных утилит обработки текста, таких как groff и tbl.

    column

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

    Пример 12-20. Пример форматирования списка файлов в каталоге

    #!/bin/bash

    # За основу сценария взят пример "man column".


    (printf "PERMISSIONS LINKS OWNER GROUP SIZE DATE TIME PROG-NAME\n" \

    ; ls -l | sed 1d) | column -t


    # Команда "sed 1d" удаляет первую строку, выводимую командой ls,

    #+ (для локали "С" это строка: "total N",

    #+ где "N" -- общее количество файлов.


    # Ключ -t, команды "column", означает "табличное" представление.


    exit 0

    colrm

    Утилита удаления колонок. Удаляет колонки (столбцы) сиволов из файла и выводит результат на stdout. colrm 2 4 <filename -- удалит символы со 2-го по 4-й включительно, в каждой строке в файле filename.

    Если файл содержит символы табуляции или непечатаемые символы, то результат может получиться самым неожиданным. В таких случаях, как правило, утилиту colrm, в конвейере, окружают командами expand и unexpand.

    nl

    Нумерует строки в файле. nl filename -- выведет файл filename на stdout, и в начале каждой строки вставит ее порядковый номер, счет начинается с первой непустой строки. Если файл не указывается, то принимается ввод со stdin.

    Вывод команды nl очень напоминает cat -n, однако, по-умолчанию nl не нумерует пустые строки.

    Пример 12-21. nl: Самонумерующийся сценарий.

    #!/bin/bash


    # Сценарий выводит себя сам на stdout дважды, нумеруя строки сценария.


    # 'nl' вставит для этой строки номер 3, поскольку она не нумерует пустые строки.

    # 'cat -n' вставит для этой строки номер 5.


    nl `basename $0`


    echo; echo # А теперь попробуем вывести текст сценария с помощью 'cat -n'


    cat -n `basename $0`

    # Различия состоят в том, что 'cat -n' нумерует все строки.

    # Обратите внимание: 'nl -ba' -- сделает то же самое.


    exit 0

    pr

    Подготовка файла к печати. Утилита производит разбивку файла на страницы, приводя его в вид пригодный для печати или для вывода на экран. Разнообразные ключи позволяют выполнять различные манипуляции над строками и колонками, соединять строки, устанавливать поля, нумеровать строки, добавлять колонтитулы и многое, многое другое. Утилита pr соединяет в себе функциональность таких команд, как nl, paste, fold, column и expand.

    pr -o 5 --width=65 fileZZZ | more -- выдаст хорошо оформленное и разбитое на страницы содержимое файла fileZZZ.

    Хочу особо отметить ключ -d, который выводит строки с двойным интервалом (тот же эффект, что и sed -G).

    gettext

    GNU утилита, предназначена для нужд локализации и перевода сообщений программ, выводимых на экран, на язык пользователя. Не смотря на то, что это актуально, прежде всего, для программ на языке C, тем не менее gettext с успехом может использоваться в сценариях командной оболочки для тех же целей. См. info page.

    iconv

    Утилита преобразования текста из одной кодировки в другую. В основном используется для нужд локализации.

    recode

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

    TeX, gs

    TeX и Postscript -- языки разметки текста, используемые для подготовки текста к печати или выводу на экран.

    TeX -- это сложная система подготовки к печати, разработанная Дональдом Кнутом (Donald Knuth). Эту утилиту удобнее использовать внутри сценария, чем в командной строке, поскольку в сценарии проще один раз записать все необходимые параметры, передаваемые утилите, для получения необходимого результата.

    Ghostscript (gs) -- это GPL-версия интерпретатора Postscript.

    groff, tbl, eqn

    groff -- это еще один язык разметки текста и форматированного вывода. Является расширенной GNU-версией пакета roff/troff в UNIX-системах.

    tbl -- утилита обработки таблиц, должна рассматриваться как составная часть groff, так как ее задачей является преобразование таблиц в команды groff.

    eqn -- утилита преобразования математических выражений в команды groff.

    lex, yacc

    lex -- утилита лексического разбора текста. В Linux-системах заменена на свободно распространяемую утилиту flex.

    yacc -- утилита для создания синтаксических анализаторов, на основе набора грамматик, задаваемых разработчиком. В Linux-системах, эта утилита заменена на свободно распространяемую утилиту bison.


    12.5. Команды для работы с файлами и архивами

    Архивация

    tar

    Стандартная, для UNIX, утилита архивирования. Первоначально -- это была программа Tape ARchiving, которая впоследствии переросла в универсальный пакет, который может работать с любыми типами устройств (см. Пример 3-4). В GNU-версию tar была добавлена возможность одновременно производить сжатие tar-архива, например команда tar czvf archive_name.tar.gz * создает tar-архив дерева подкаталогов и вызывает gzip для выполнения сжатия, исключение составляют скрытые файлы в текущем каталоге ($PWD)[ 30 ].

    Некоторые, часто используемые, ключи команды tar:

    1. -c -- создать (create) новый архив

    2. -x -- извлечь (extract) файлы из архива

    3. --delete -- удалить (delete) файлы из архива

    Этот ключ игнорируется для накопителей на магнитной ленте.

    4. -r -- добавить (append) файлы в существующий архив

    5. -A -- добавить (append) tar-файлы в существующий архив

    6. -t -- список файлов в архиве (содержимое архива)

    7. -u -- обновить (update) архив

    8. -d -- операция сравнения архива с заданной файловой системой

    9. -z -- обработка архива с помощью gzip

    (Сжатие или разжатие, в зависимости от комбинации сопутствующих ключей -c или -x)

    10. -j -- обработка архива с помошью bzip2


    При восстановлении "битых" tar.gz архивов могут возникнуть определенные сложности, поэтому делайте несколько резервных копий.

    shar

    Утилита создания shell-архива. Архивируемые файлы объединяются в единый файл без выполнения сжатия, в результате получается архив -- по сути полноценный сценарий на языке командной оболочки, начинающийся со строки #!/bin/sh, который содержит полный набор команд, необходимый для разархивирования. Такого рода архивы до сих пор можно найти в некоторых телеконференциях в Internet, но в последнее время они активно вытесняются связкой tar/gzip. Для распаковки shar-архивов предназначена команда unshar.

    ar

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

    rpm

    Red Hat Package Manager, или rpm -- набор утилит, предназначенных для построения и обслуживания пакетов программного обеспечения как в исходном коде, так и в собранном (откомпилированном) виде. Среди всего прочего, включает в себя утилиты, производящие установку ПО, проверку зависимостей пакетов и проверку их целостности.

    Самый простой вариант установки ПО из rpm -- выполнить команду rpm -i package_name.rpm.

    Команда rpm -qa выдаст полный список всех установленных rpm-пакетов в данной системе. Команда rpm -qa package_name выведет только пакет(ы) с именем, содержащим комбинацию символов package_name.

    bash$ rpm -qa

    redhat-logos-1.1.3-1

    glibc-2.2.4-13

    cracklib-2.7-12

    dosfstools-2.7-1

    gdbm-1.8.0-10

    ksymoops-2.4.1-1

    mktemp-1.5-11

    perl-5.6.0-17

    reiserfs-utils-3.x.0j-2

    ...


    bash$ rpm -qa docbook-utils

    docbook-utils-0.6.9-2


    bash$ rpm -qa docbook | grep docbook

    docbook-dtd31-sgml-1.0-10

    docbook-style-dsssl-1.64-3

    docbook-dtd30-sgml-1.0-10

    docbook-dtd40-sgml-1.0-11

    docbook-utils-pdf-0.6.9-2

    docbook-dtd41-sgml-1.0-10

    docbook-utils-0.6.9-2


    cpio

    Специализированная утилита архивации и копирования (copy input and output). Используется все реже и реже, поскольку вытесняется более мощным архиватором tar/gzip. Наиболее употребительна для таких операций, как перемещение дерева каталогов.

    Пример 12-22. Пример перемещения дерева каталогов с помощью cpio

    #!/bin/bash


    # Копирование дерева каталогов с помощью cpio.


    ARGS=2

    E_BADARGS=65


    if [ $# -ne "$ARGS" ]

    then

    echo "Порядок использования: `basename $0` source destination"

    exit $E_BADARGS

    fi


    source=$1

    destination=$2


    find "$source" -depth | cpio -admvp "$destination"

    # Информацию по ключам утилиты cpio вы найдете в страницах руководства "man cpio".


    exit 0

    rpm2cpio

    Эта утилита конвертирует rpm-пакет в архив cpio.

    Пример 12-23. Распаковка архива rpm

    #!/bin/bash

    # de-rpm.sh: Распаковка архива 'rpm'


    : ${1?"Порядок использования: `basename $0` target-file"}

    # Сценарию должно быть передано имя архива 'rpm'.


    TEMPFILE=$$.cpio # Временный файл с "уникальным" именем.

    # $$ -- PID процесса сценария.


    rpm2cpio < $1 > $TEMPFILE # Конверсия из rpm в cpio.

    cpio --make-directories -F $TEMPFILE -i # Рапсковка cpio-архива.

    rm -f $TEMPFILE # Удаление cpio-архива.


    exit 0


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

    # Добавьте проверку на: 1) Существование "target-file"

    #+ 2) Действительно ли "target-file" является rpm-архивом.

    # Подсказка: используйте комсанду 'file'.

    Сжатие

    gzip

    Стандартная GNU/UNIX утилита сжатия, заменившая более слабую, и к тому же проприетарную, утилиту compress. Соответствующая утилита декомпрессии (разжатия) -- gunzip, которая является эквивалентом команды gzip -d.

    Для работы со сжатыми файлами в конвейере используется фильтр zcat, который выводит результат своей работы на stdout, допускает перенаправление вывода. Фактически это та же команда cat, только приспособленная для работы со сжатыми файлами (включая файлы, сжатые утилитой compress). Эквивалент команды zcat -- gzip -dc.

    В некоторых коммерческих версиях UNIX, команда zcat является синонимом команды uncompress -c, и не может работать с файлами, сжатыми с помощью gzip.

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

    bzip2

    Альтернативная утилита сжатия, обычно дает более высокую степень сжатия (но при этом работает медленнее), чем gzip, особенно это проявляется на больших файлах. Соответствующая утилита декомпрессии -- bunzip2.

    В современные версии tar добавлена поддержка bzip2.

    compress, uncompress

    Устаревшие проприетарные утилиты для работы с архивами, входящие в состав некоторых коммерческих дистрибутивов UNIX. В последнее время вытесняются более мощной утилитой gzip. Linux-дистрибутивы, как правило, включают в свой состав эти утилиты для обратной совместимости, однако gunzip корректно разархивирует файлы, обработанные с помощью compress.

    Утилита znew предназначена для преобразования compress-архивов в gzip-архивы.

    sq

    Еще одна утилита-фильтр сжатия, которая обслуживает только отсортированные списки слов. Использует стандартный, для фильтров, синтаксис вызова -- sq < input-file > output-file. Быстрая, но не такая эффективная как gzip. Соответствующая ей утилита декомпрессии называется unsq, синтаксис вызова аналогичен утилите sq.

    Вывод от sq может быть передан по конвейеру утилите gzip, для дальнейшего сжатия.

    zip, unzip

    Кроссплатформенная утилита архивирования и сжатия, совместимая, по формату архивного файла, с утилитой DOS -- pkzip.exe. "Zip"-архивы, по-моему, более приемлемый вариант для обмена данными через Internet, чем "tarballs" (тарболлы, или tar-архивы).

    unarc, unarj, unrar

    Этот набор утилит предназначен для распаковки архивов, созданных с помощью DOS архиваторов -- arc.exe, arj.exe и rar.exe.

    Получение сведений о файлах

    file

    Утилита идентификации файлов. Команда file file-name верне тип файла file-name, например, ascii text или data. Для этого она анализирует сигнатуру, или магическое число и сопоставляет ее со списком известных сигнатур из /usr/share/magic, /etc/magic или /usr/lib/magic (в зависимости от дистрибутива Linux/UNIX).

    -f -- ключ пакетного режима работы утилиты file, в этом случае утилита принимает список анализируемых имен файлов из заданного файла. Ключ -z используется для анализа файлов в архиве.

    bash$ file test.tar.gz

    test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix


    bash file -z test.tar.gz

    test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix)


    Пример 12-24. Удаление комментариев из файла с текстом программы на языке C

    #!/bin/bash

    # strip-comment.sh: Удаление комментариев (/* COMMENT */) из исходных текстов программ на языке C.


    E_NOARGS=65

    E_ARGERROR=66

    E_WRONG_FILE_TYPE=67


    if [ $# -eq "$E_NOARGS" ]

    then

    echo "Порядок использования: `basename $0` C-program-file" >&2 # Вывод сообщения на stderr.

    exit $E_ARGERROR

    fi


    # Проверка типа файла.

    type=`eval file $1 | awk '{ print $2, $3, $4, $5 }'`

    # "file $1" -- выводит тип файла...

    # затем awk удаляет первое поле -- имя файла...

    # после этого результат записывается в переменную "type".

    correct_type="ASCII C program text"


    if [ "$type" != "$correct_type" ]

    then

    echo

    echo "Этот сценарий работает только с исходными текстами программ на языке C."

    echo

    exit $E_WRONG_FILE_TYPE

    fi


    # Довольно замысловатый сценарий sed :

    #--------

    sed '

    /^\/\*/d

    /.*\/\*/d

    ' $1

    #--------

    # Если вы потратите несколько часов на изучение основ sed, то он станет немного понятнее.


    # Следовало бы добавить еще обработку

    #+ комментариев, расположенных в одной строке с кодом.

    # Оставляю это вам, в качестве упражнения.


    # Кроме того, этот сценарий удалит все строки, которые содержат комбинации символов "*/" или "/*",

    # не всегда желаемый результат.


    exit 0


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

    # Строки, расположенные ниже не будут исполнены из-за стоящей выше команды 'exit 0'.


    # Stephane Chazelas предложил другой, альтернативный вариант:


    usage() {

    echo "Порядок использования: `basename $0` C-program-file" >&2

    exit 1

    }


    WEIRD=`echo -n -e '\377'` # или WEIRD=$'\377'

    [[ $# -eq 1 ]] || usage

    case `file "$1"` in

    *"C program text"*) sed -e "s%/\*%${WEIRD}%g;s%\*/%${WEIRD}%g" "$1" \

    | tr '\377\n' '\n\377' \

    | sed -ne 'p;n' \

    | tr -d '\n' | tr '\377' '\n';;

    *) usage;;

    esac


    # Этот вариант, все еще некорректно обрабатывает такие строки как:

    # printf("/*");

    # или

    # /* /* ошибочный вложенный комментарий */

    #

    # Для обработки специальных случаев (\", \\" ...) придется написать синтаксический анализатор

    # (может быть с помощью lex или yacc?).


    exit 0

    which

    Команда which command-xxx вернет полный путь к "command-xxx". Очень полезна для того, чтобы узнать -- установлена ли та или иная утилита в системе.

    $bash which rm

    /usr/bin/rm


    whereis

    Очень похожа на which, упоминавшуюся выше. Команда whereis command-xxx вернет полный путь к "command-xxx", но кроме того, еще и путь к manpage -- файлу, странице справочника по заданной утилите.

    $bash whereis rm

    rm: /bin/rm /usr/share/man/man1/rm.1.bz2


    whatis

    Утилита whatis filexxx отыщет "filexxx" в своей базе данных. Может рассматриваться как упрощенный вариант команды man.

    $bash whatis whatis

    whatis (1) - search the whatis database for complete words


    Пример 12-25. Исследование каталога /usr/X11R6/bin

    #!/bin/bash


    # Что находится в каталоге /usr/X11R6/bin?


    DIRECTORY="/usr/X11R6/bin"

    # Попробуйте также "/bin", "/usr/bin", "/usr/local/bin", и т.д.


    for file in $DIRECTORY/*

    do

    whatis `basename $file` # Вывод информации о файле.

    done


    exit 0

    # Вывод этого сценария можно перенаправить в файл:

    # ./what.sh >>whatis.db

    # или включить постраничный просмотр на экране,

    # ./what.sh | less

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

    vdir

    Вывод списка файлов в каталоге. Тот же эффект имеет команда ls -l.

    Это одна из утилит GNU fileutils.

    bash$ vdir

    total 10

    -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo

    -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak

    -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.xrolo


    bash ls -l

    total 10

    -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo

    -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak

    -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.xrolo


    locate, slocate

    Команда locate определяет местонахождение файла, используя свою базу данных, создаваемую специально для этих целей. Команда slocate -- это защищенная версия locate (которая может оказаться простым псевдонимом команды slocate).

    $bash locate hickson

    /usr/lib/xephem/catalogs/hickson.edb


    readlink

    Возвращает имя файла, на который указывает символическая ссылка.

    bash$ readlink /usr/bin/awk

    ../../bin/gawk


    strings

    Команда strings используется для поиска печатаемых строк в двоичных файлах. Она выводит последовательности печатаемых символов, обнаруженных в заданном файле. Может использоваться для прикидочного анализа дамп-файлов (core dump) или для отыскания информации о типе файла, например для графических файлов неизвестного формата (например, strings image-file | more может вывести такую строчку: JFIF, что говорит о том, что мы имеем дело с графическим файлом в формате jpeg). В сценариях, вероятнее всего, вам придется использовать эту команду в связке с grep или sed. См. Пример 10-7 и Пример 10-9.

    Пример 12-26. "Расширенная" команда strings

    #!/bin/bash

    # wstrings.sh: "word-strings" (расширенная команда "strings")

    #

    # Этот сценарий фильтрует вывод команды "strings" путем проверки на соответствие

    #+ выводимых слов по файлу словаря.

    # Таким способом эффективно "отсекается" весь "мусор",

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


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

    # Стандартная проверка входных аргументов

    ARGS=1

    E_BADARGS=65

    E_NOFILE=66


    if [ $# -ne $ARGS ]

    then

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

    exit $E_BADARGS

    fi


    if [ ! -f "$1" ] # Проверка наличия файла.

    then

    echo "Файл \"$1\" не найден."

    exit $E_NOFILE

    fi

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


    MINSTRLEN=3 # Минимальная длина строки.

    WORDFILE=/usr/share/dict/linux.words # Файл словаря.

    # Можно указать иной

    #+ файл словаря

    #+ в формате -- "одно слово на строке".


    wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \

    tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`


    # Трансляция вывода от 'strings' с помощью нескольких 'tr'.

    # "tr A-Z a-z" -- перевод в нижний регистр.

    # "tr '[:space:]'" -- конвертирует пробелы в символы Z.

    # "tr -cs '[:alpha:]' Z" -- конвертирует неалфавитные символы в символы Z,

    #+ и удаляет повторяющиеся символы Z.

    # "tr -s '\173-\377' Z" -- Конвертирует все символы, с кодами выше 'z' в Z

    #+ и удаляет повторяющиеся символы Z,

    #+ эта команда удалит все символы, которые не были распознаны предыдущими

    #+ командами трансляции (tr).

    # Наконец, "tr Z ' '" -- преобразует все символы Z в пробелы,

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


    # Обратите внимание на технику многоуровневой обработки с помощью 'tr',

    #+ каждый раз эта команда вызывается с различным набором аргументов.


    for word in $wlist # Важно:

    # переменная $wlist не должна заключаться в кавычки.

    # "$wlist" -- не сработает.

    # Почему?

    do


    strlen=${#word} # Дина строки.

    if [ "$strlen" -lt "$MINSTRLEN" ] # Не рассматривать короткие строки.

    then

    continue

    fi


    grep -Fw $word "$WORDFILE" # Проверка слова по словарю.


    done


    exit 0

    Сравнение

    diff, patch

    diff: очень гибкая утилита сравнения файлов. Она выполняет построчное сравнение файлов. В отдельных случаях, таких как поиск по словарю, может оказаться полезной фильтрация файлов с помощью sort и uniq перед тем как отдать поток данных через конвейер утилите diff. diff file-1 file-2 -- выведет строки, имеющие отличия, указывая -- какому файлу, какая строка принадлежит.

    С ключом --side-by-side, команда diff выведет сравниваемые файлы в две колонки, с указанием несовпадающих строк. Ключи -c и -u так же служат для облегчения интерпретации результатов работы diff.

    Существует ряд интерфейсных оболочек для утилиты diff, среди них можно назвать: spiff, wdiff, xdiff и mgdiff.

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

    В общем случае, diff используется для генерации файла различий, который используется как аргумент команды patch. Ключ -e отвечает за вывод файла различий в формате, пригодном для использования с ed или ex.

    patch: гибкая утилита для "наложения заплат". С помощью файла различий, сгенерированного утилитой diff, утилита patch может использоваться для обновления устаревших версий файлов. Это позволяет распространять относительно небольшие "diff"-файлы вместо целых пакетов. Распространение "заплат" к ядру стало наиболее предпочтительным методом распространения более новых версий ядра Linux.

    patch -p1 <patch-file

    # Применит все изменения из 'patch-file'

    # к файлам, описанным там же.

    # Так выполняется обновление пакетов до более высоких версий.


    Наложение "заплат" на ядро:

    cd /usr/src

    gzip -cd patchXX.gz | patch -p0

    # Обновление исходных текстов ядра с помощью 'patch'.

    # Пример взят из файла "README",

    # автор не известен (Alan Cox?).


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

    bash$ diff -r ~/notes1 ~/notes2

    Only in /home/bozo/notes1: file02

    Only in /home/bozo/notes1: file03

    Only in /home/bozo/notes2: file04


    Утилита zdiff сравнивает сжатые, с помощью gzip, файлы.

    diff3

    Расширенная версия diff, которая сравнивает сразу 3 файла. В случае успеха возвращает 0, но, к сожалению, не дает никакой информации о результатах сравнения.

    bash$ diff3 file-1 file-2 file-3

    ====

    1:1c

    This is line 1 of "file-1".

    2:1c

    This is line 1 of "file-2".

    3:1c

    This is line 1 of "file-3"


    sdiff

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

    cmp

    Утилита cmp -- это упрощенная версия diff. В то время, как diff выводит подробную информацию об имеющихся различиях, утилита cmp лишь показывет номер строки и позицию в строке, где было встречено различие.

    Подобно команде diff, команда cmp возвращает код завершения 0, если файлы идентичны и 1, если они различны. Это позволяет использовать команду cmp в условных операторах.

    Пример 12-27. Пример сравнения двух файлов с помощью cmp.

    #!/bin/bash


    ARGS=2 # Ожидаются два аргумента командной строки.

    E_BADARGS=65

    E_UNREADABLE=66


    if [ $# -ne "$ARGS" ]

    then

    echo "Порядок использования: `basename $0` file1 file2"

    exit $E_BADARGS

    fi


    if [[ ! -r "$1" || ! -r "$2" ]]

    then

    echo "Оба файла должны существовать и должны быть доступны для чтения."

    exit $E_UNREADABLE

    fi


    cmp $1 $2 &> /dev/null # /dev/null -- "похоронит" вывод от команды "cmp".

    # cmp -s $1 $2 даст тот же результат ("-s" -- флаг "тишины" для "cmp")

    # Спасибо Anders Gustavsson за замечание.

    #

    # Также применимо к 'diff', т.е., diff $1 $2 &> /dev/null


    if [ $? -eq 0 ] # Проверка кода возврата команды "cmp".

    then

    echo "Файл \"$1\" идентичен файлу \"$2\"."

    else

    echo "Файл \"$1\" отличается от файла \"$2\"."

    fi


    exit 0

    Для работы с gzip файлами используется утилита zcmp.

    comm

    Универсальная утилита сравнения. Работает с отсортированными файлами.

    comm -options first-file second-file

    comm file-1 file-2 -- вывод в три колонки:

    ? колонка 1 = уникальные строки для file-1

    ? колонка 2 = уникальные строки для file-2

    ? колонка 3 = одинаковые строки.


    Ключи, подавляющие вывод в одной или более колонках.

    ? -1 -- подавление вывода в колонку 1

    ? -2 -- подавление вывода в колонку 2

    ? -3 -- подавление вывода в колонку 3

    ? -12 -- подавление вывода в колонки 1 и 2, и т.д.


    Утилиты

    basename

    Выводит только название файла, без каталога размещения. Конструкция basename $0 -- позволяет сценарию узнать свое имя, то есть имя файла, который был запущен. Это имя может быть использовано для вывода сообщений, напрмиер:

    echo "Порядок использования: `basename $0` arg1 arg2 ... argn"


    dirname

    Отсекает basename от полного имени файла и выводит только путь к файлу.

    Утилитам basename и dirname может быть передана любая строка, в качестве аргумента. Этот аргумент необязательно должен быть именем существующего файла (см. Пример A-8).

    Пример 12-28. Утилиты basename и dirname

    #!/bin/bash


    a=/home/bozo/daily-journal.txt


    echo "Basename для /home/bozo/daily-journal.txt = `basename $a`"

    echo "Dirname для /home/bozo/daily-journal.txt = `dirname $a`"

    echo

    echo "Мой домашний каталог `basename ~/`." # Можно указать просто ~.

    echo "Каталог моего домашнего каталога `dirname ~/`." # Можно указать просто ~.


    exit 0

    split

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

    sum, cksum, md5sum

    Эти утилиты предназначены для вычисления контрольных сумм. Контрольная сумма -- это некоторое число, вычисляемое исходя из содержимого файла, и служит для контроля целостности информации в файле. Сценарий может выполнять проверку контрольных сумм для того, чтобы убедиться, что файл не был изменен или поврежден. Для большей безопасности, рекомендуется использовать 128-битную сумму, генерируемую утилитой md5sum (message digest checksum).

    bash$ cksum /boot/vmlinuz

    1670054224 804083 /boot/vmlinuz


    bash$ md5sum /boot/vmlinuz

    0f43eccea8f09e0a0b2b5cf1dcf333ba /boot/vmlinuz


    Обратите внимание: утилита cksum выводит контрольную сумму и размер файла в байтах.

    Пример 12-29. Проверка целостности файла

    #!/bin/bash

    # file-integrity.sh: Проверка целостности файлов в заданном каталоге


    E_DIR_NOMATCH=70

    E_BAD_DBFILE=71


    dbfile=File_record.md5

    # Файл для хранения контрольных сумм.


    set_up_database ()

    {

    echo ""$directory"" > "$dbfile"

    # Записать название каталога в первую строку файла.

    md5sum "$directory"/* >> "$dbfile"

    # Записать контрольные суммы md5 и имена файлов.

    }


    check_database ()

    {

    local n=0

    local filename

    local checksum


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

    # Возможно эта проверка и не нужна,

    #+ но лучше перестраховаться сейчас, чем жалеть об этом потом.


    if [ ! -r "$dbfile" ]

    then

    echo "Не могу прочитать файл с контрольными суммами!"

    exit $E_BAD_DBFILE

    fi

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


    while read record[n]

    do


    directory_checked="${record[0]}"

    if [ "$directory_checked" != "$directory" ]

    then

    echo "Имя каталога не совпадает с записаным в файле!"

    # Попытка использовать файл контрольных сумм для другого каталога.

    exit $E_DIR_NOMATCH

    fi


    if [ "$n" -gt 0 ] # Не имя каталога.

    then

    filename[n]=$( echo ${record[$n]} | awk '{ print $2 }' )

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

    #+ сначала контрольную сумму, затем имя файла.

    checksum[n]=$( md5sum "${filename[n]}" )


    if [ "${record[n]}" = "${checksum[n]}" ]

    then

    echo "Файл ${filename[n]} не был изменен."

    else

    echo "ОШИБКА КОНТРОЛЬНОЙ СУММЫ для файла ${filename[n]}!"

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

    fi


    fi


    let "n+=1"

    done <"$dbfile" # Чтение контрольных сумм из файла.


    }


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

    # main ()


    if [ -z "$1" ]

    then

    directory="$PWD" # Если каталог не задан,

    else #+ то используется текущий каталог.

    directory="$1"

    fi


    clear # Очистка экрана.


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

    if [ ! -r "$dbfile" ] # Необходимо создать файл с контрольными суммами?

    then

    echo "Создание файла с контрольными суммами, \""$directory"/"$dbfile"\"."; echo

    set_up_database

    fi

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


    check_database # Выполнить проверку.


    echo


    # Вывод этого сценария можно перенаправить в файл,

    #+ это особенно полезно при проверке большого количества файлов.


    # Более строгая проверка целостности файлов,

    #+ может быть выполнена с помощью пакета "Tripwire",

    #+ http://sourceforge.net/projects/tripwire/.


    exit 0

    Более творческий подход к использованию md5sum вы нйдете в Пример A-21.

    shred

    Надежное, с точки зрения безопасности, стирание файла, посредством предварительной, многократной записи в файл случайной информации, перед тем как удалить его. Эта команда имеет тот же эффект, что и Пример 12-42, но делает это более изящным и безопасным способом.

    Является составной частью пакета GNU fileutils.

    Имеется ряд технологий, с помощью которых все-таки возможно восстановить файлы, удаленные утилитой shred.

    Кодирование и шифрование

    uuencode

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

    uudecode

    Утилита декодирования файлов, прошедших обработку утилитой uuencode.

    Пример 12-30. Декодирование файлов

    #!/bin/bash


    lines=35 # 35 строк для заголовка (более чем достаточно).


    for File in * # Обход всех файлов в текущем каталоге...

    do

    search1=`head -$lines $File | grep begin | wc -w`

    search2=`tail -$lines $File | grep end | wc -w`

    # Закодированные файлы начинаются со слова "begin",

    #+ и заканчиваются словом "end".

    if [ "$search1" -gt 0 ]

    then

    if [ "$search2" -gt 0 ]

    then

    echo "декодируется файл - $File -"

    uudecode $File

    fi

    fi

    done


    # Обратите внимание: если передать сценарию самого себя, для декодирования,

    #+ то это введет его в заблуждение

    #+ поскольку в тексте сценария встречаются слова "begin" и "end".


    exit 0

    При декодировании и выводе длинных текстовых сообщений из новостных групп Usenet, очень нелишним будет передать текст, по конвейеру, команде fold -s.

    mimencode, mmencode

    Утилиты mimencode и mmencode предназначены для обработки закодированных мультимедийных вложений в электронные письма. Хотя почтовые программы (такие как pine или kmail) имеют возможность автоматической обработки таких вложений, тем не менее эти утилиты позволяют обрабатывать вложения вручную, из командной строки или в пакетном режиме, из сценария на языке командной оболочки.

    crypt

    Одно время, это была стандартная, для UNIX, утилита шифрования файлов.[ 31 ] Политически мотивированные, правительственные постановления ряда стран, напрямую запрещают экспорт программного обеспечения для шифрования, что, в результате, привело практически к полному исчезновению crypt из большинства UNIX-систем (в том числе и Linux). К счастью, программистами было разработано множество вполне приличных альтернатив, и среди них cruft (см. Пример A-5).

    Прочее

    mktemp

    Создает временный файл с "уникальным" именем.

    PREFIX=filename

    tempfile=`mktemp $PREFIX.XXXXXX`

    # ^^^^^^ Необходимо по меньшей мере 6 заполнителей

    echo "имя временного файла = $tempfile"

    # имя временного файла = filename.QA2ZpY

    # или нечто подобное...


    make

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

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

    install

    Своего рода -- утилита копирования файлов, похожа на cp, но дополнительно позволяет изменять права доступа и атрибуты копируемых файлов. Напрямую эта команда практически не используется, чаще всего она встречается в Makefile (в разделе make install :). Она может использоваться в сценариях установки ПО.

    dos2unix

    Автор утилиты -- Benjamin Lin со-товарищи. Предназначена для преобразования текстовых файлов из формата DOS (в котором строки завершаются комбинацией символов CR-LF) в формат UNIX (в котором строки завершаются одним символом LF) и обратно.

    ptx

    Команда ptx [targetfile] выводит a упорядоченный предметный указатель для targetfile, который можно обработать, по мере необходимости, какой либо утилитой форматирования, в конвейере.

    more, less

    Команды постраничного просмотра текстовых файлов или потоков на stdout. Могут использоваться в сценариях в качестве фильтров.

    12.6. Команды для работы с сетью

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

    Информация и статистика

    host

    Возвращает информацию об узле Интернета, по заданному имени или IP адресу, выполняя поиск с помощью службы DNS.

    bash$ host surfacemail.com

    surfacemail.com. has address 202.92.42.236


    ipcalc

    Производит поиск IP адреса. С ключом -h, ipcalc выполняет поиск имени хоста в DNS, по заданному IP адресу.

    bash$ ipcalc -h 202.92.42.236

    HOSTNAME=surfacemail.com


    nslookup

    Выполняет "поиск имени узла" Интернета по заданному IP адресу. По сути, эквивалентна командам ipcalc -h и dig -x. Команда может исполняться как в интерактивном, так и в неинтерактивном режиме, т.е. в пределах сценария.

    bash$ nslookup -sil 66.97.104.180

    nslookup kuhleersparnis.ch

    Server: 135.116.137.2

    Address: 135.116.137.2#53


    Non-authoritative answer:

    Name: kuhleersparnis.ch


    dig

    Подобно команде nslookup, выполняет "поиск имени узла" в Интернете.

    Сравните вывод команды dig -x с выводом команд ipcalc -h и nslookup.

    bash$ dig -x 81.9.6.2

    ;; Got answer:

    ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11649

    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0


    ;; QUESTION SECTION:

    ;2.6.9.81.in-addr.arpa. IN PTR


    ;; AUTHORITY SECTION:

    6.9.81.in-addr.arpa. 3600 IN SOA ns.eltel.net. noc.eltel.net.

    2002031705 900 600 86400 3600


    ;; Query time: 537 msec

    ;; SERVER: 135.116.137.2#53(135.116.137.2)

    ;; WHEN: Wed Jun 26 08:35:24 2002

    ;; MSG SIZE rcvd: 91


    traceroute

    Утилита предназначена для исследования топологии сети посредством передачи ICMP пакетов удаленному узлу. Эта программа может работать в LAN, WAN и в Интернет. Удаленный узел может быть указан как по имени, так и по IP адресу. Вывод команды traceroute может быть передан по конвейеру утилитам grep или sed, для дальнейшего анализа.

    bash$ traceroute 81.9.6.2

    traceroute to 81.9.6.2 (81.9.6.2), 30 hops max, 38 byte packets

    1 tc43.xjbnnbrb.com (136.30.178.8) 191.303 ms 179.400 ms 179.767 ms

    2 or0.xjbnnbrb.com (136.30.178.1) 179.536 ms 179.534 ms 169.685 ms

    3 192.168.11.101 (192.168.11.101) 189.471 ms 189.556 ms *

    ...


    ping

    Выполняет передачу пакета "ICMP ECHO_REQUEST" другой системе в сети. Чаще всего служит в качестве инструмента диагностики соединений, должна использоваться с большой осторожностью.

    В случае успеха, ping возвращает код завершения 0, поэтому команда ping может использоваться в условных операторах.

    bash$ ping localhost

    PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.

    Warning: time of day goes back, taking countermeasures.

    64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=0 ttl=255 time=709 usec

    64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=255 time=286 usec


    --- localhost.localdomain ping statistics ---

    2 packets transmitted, 2 packets received, 0% packet loss

    round-trip min/avg/max/mdev = 0.286/0.497/0.709/0.212 ms


    whois

    Выполняет поиск в DNS (Domain Name System). Ключом -h можно указать какой из whois серверов будет запрошен. См. Пример 4-6.

    finger

    Возвращает информацию о пользователях в сети. По желанию, эта команда может выводить содержимое файлов ~/.plan, ~/.project и ~/.forward, указанного пользователя.

    bash$ finger

    Login Name Tty Idle Login Time Office Office Phone

    bozo Bozo Bozeman tty1 8 Jun 25 16:59

    bozo Bozo Bozeman ttyp0 Jun 25 16:59

    bozo Bozo Bozeman ttyp1 Jun 25 17:07


    bash$ finger bozo

    Login: bozo Name: Bozo Bozeman

    Directory: /home/bozo Shell: /bin/bash

    On since Fri Aug 31 20:13 (MST) on tty1 1 hour 38 minutes idle

    On since Fri Aug 31 20:13 (MST) on pts/0 12 seconds idle

    On since Fri Aug 31 20:13 (MST) on pts/1

    On since Fri Aug 31 20:31 (MST) on pts/2 1 hour 16 minutes idle

    No mail.

    No Plan.


    По соображениям безопасности, в большинстве сетей служба finger, и соответствующий демон, отключена[ 32 ].

    vrfy

    Проверка адреса электронной почты.

    Доступ к удаленным системам

    sx, rx

    Команды sx и rx служат для приема/передачи файлов на/из удаленный узел в сети, по протоколу xmodem. Входят в состав пакета minicom.

    sz, rz

    Команды sz и rz служат для приема/передачи файлов на/из удаленный узел в сети, по протоколу zmodem. Протокол zmodem имеет некоторые преимущества перед протоколом xmodem, в качестве такого преимущества можно назвать более высокую скорость передачи и возможность возобновления передачи, в случае ее разрыва. Входят в состав пакета minicom.

    ftp

    Под этим именем подразумевается утилита и протокол передачи файлов. Сеансы ftp могут устанавливаться из сценариев (см. Пример 17-7, Пример A-5 и Пример A-14).

    uucp

    UNIX to UNIX copy. Это коммуникационный пакет для передачи файлов между UNIX серверами. Сценарий на языке командной оболочки -- один из самых эффективных способов автоматизации такого обмена.

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

    cu

    Call Up -- выполняет соединение с удаленной системой, как простой терминал. Эта команда является частью пакета uucp и, своего рода, упрощенным вариантом команды telnet.

    telnet

    Утилита и протокол для подключения к удаленной системе.

    Протокол telnet небезопасен по своей природе, поэтому следует воздерживаться от его использования.

    wget

    wget -- неинтерактивная утилита для скачивания файлов с Web или ftp сайтов.

    wget -p http://www.xyz23.com/file01.html

    wget -r ftp://ftp.xyz24.net/~bozo/project_files/ -o $SAVEFILE


    lynx

    lynx -- Web браузер, внутри сценариев (с ключом -dump) может использоваться для скачивания файлов с Web или ftp сайтов, в неинтерактивном режиме.

    lynx -dump http://www.xyz23.com/file01.html >$SAVEFILE


    rlogin

    Remote login -- инициирует сессию с удаленной системой. Эта команда небезопасна, вместо нее лучше использовать ssh.

    rsh

    Remote shell -- исполняет команду на удаленной системе. Эта команда небезопасна, вместо нее лучше использовать ssh.

    rcp

    Remote copy -- копирование файлов между двумя машинами через сеть. Подобно прочим r* утилитам, команда rcp небезопасна и потому, использовать ее в сценариях нежелательно. В качестве замены можно порекомендовать ssh или expect.

    ssh

    Secure shell -- устанавливает сеанс связи и выполняет команды на удаленной системе. Выступает в качестве защищенной замены для telnet, rlogin, rcp и rsh. Использует идентификацию, аутентификацию и шифрование информации, передаваемой через сеть. Подробности вы найдете в man ssh.

    Локальная сеть

    write

    Эта утилита позволяет передать текст сообщения на другой терминал (console или xterm). Разрешить или запретить доступ к терминалу можно с помощью команды mesg.

    Поскольку команда write работает в интерактивном режиме, то, как правило, она не употребляется в сценариях.

    Mail

    mail

    Чтение или передача электронной почты.

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

    Пример 12-31. Сценарий, отправляющий себя самого по электронной почте

    #!/bin/sh

    # self-mailer.sh: Сценарий отправляет себя самого по электронной почте


    adr=${1:-`whoami`} # Если пользователь не указан, то -- себе самому.

    # Вызов 'self-mailer.sh wiseguy@superdupergenius.com'

    #+ приведет к передаче электронного письма по указанному адресу.

    # Вызов 'self-mailer.sh' (без аргументов) -- отправит письмо

    #+ пользователю, запустившему сценарий, например, bozo@localhost.localdomain.

    #

    # Дополнительно о конструкции ${parameter:-default},

    #+ см. раздел "Подстановка параметров"

    #+ в главе "К вопросу о переменных".


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

    cat $0 | mail -s "Сценарий \"`basename $0`\" отправил себя сам." "$adr"

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


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

    # Поздравляю!

    # Этот сценарий запустила какая-то "редиска",

    #+ и заставила отправить этот текст к Вам.

    # Очевидно кто-то не знает

    #+ куда девать свое время.

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


    echo "`date`, сценарий \"`basename $0`\" отправлен "$adr"."


    exit 0

    mailto

    Команда mailto, похожа на mail, она также отправляет сообщения по электронной почте. Однако, кроме этого, mailto позволяет отправлять MIME (multimedia) сообщения.

    vacation

    Эта утилита предназначена для автоматической передачи ответов на электронные письма, например для того, чтобы уведомить отправителя о том, что получатель временно отсутствует. Работает совместно с sendmail и не может использоваться для передачи сообщений через коммутируемые линии (по модему).


    12.7. Команды управления терминалом

    Команды, имеющие отношение к консоли или терминалу

    tput

    инициализация терминала или выполнение запроса к базе данных терминалов terminfo. С помощью tput можно выполнять различные операции. tput clear -- эквивалентно команде clear. tput reset -- эквивалентно команде reset. tput sgr0 -- так же сбрасывет настройки терминал, но без очистки экрана.

    bash$ tput longname

    xterm terminal emulator (XFree86 4.0 Window System)


    Команда tput cup X Y перемещает курсор в координаты (X,Y). Обычно этой команде предшествует clear, очищающая экран.

    Обратите внимание: stty предлагает более широкий диапазон возможностей.

    infocmp

    Cравнение или печать информации о характеристиках терминалов, хранящейся в базе данных terminfo.

    bash$ infocmp

    # Reconstructed via infocmp from file:

    /usr/share/terminfo/r/rxvt

    rxvt|rxvt terminal emulator (X Window System),

    am, bce, eo, km, mir, msgr, xenl, xon,

    colors#8, cols#80, it#8, lines#24, pairs#64,

    acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,

    bel=^G, blink=\E[5m, bold=\E[1m,

    civis=\E[?25l,

    clear=\E[H\E[2J, cnorm=\E[?25h, cr=^M,

    ...


    reset

    Сбрасывает настройки терминала и очищает экран. Как и в случае команды clear, курсор и приглашение к вводу (prompt) выводятся в верхнем левом углу терминала.

    clear

    Команда clear просто очищает экран терминала или окно xterm. Курсор и приглашение к вводу (prompt) выводятся в верхнем левом углу терминала. Эта команда может запускаться как из командной строки, так и из сценария. См. Пример 10-25.

    script

    Эта утилита позволяет сохранять в файле все символы, введенные пользователем c клавиатуры (вывод тоже). Получая, фактически, подробнейший синхронный протокол сессии.


    12.8. Команды выполнения математических операций

    factor

    Разложение целого числа на простые множители.

    bash$ factor 27417

    27417: 3 13 19 37


    bc

    Bash не в состоянии выполнять действия над числами с плавающей запятой и не содержит многих важных математических функций. К счастью существует bc.

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

    Синтаксис bc немного напоминает язык C.

    Поскольку это утилита UNIX, то она может достаточно широко использоваться в сценариях на языке командной оболочки, в том числе и в конвейерной обработке данных.

    Ниже приводится простой шаблон работы с утилитой bc в сценарии. Здесь используется прием подстановки команд.

    variable=$(echo "OPTIONS; OPERATIONS" | bc)


    Пример 12-32. Ежемесячные выплаты по займу

    #!/bin/bash

    # monthlypmt.sh: Расчет ежемесячных выплат по займу.


    # Это измененный вариант пакета "mcalc" (mortgage calculator),

    #+ написанного Jeff Schmidt и Mendel Cooper (ваш покорный слуга).

    # http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz [15k]


    echo

    echo "Введите сумму займа, процентную ставку и срок займа,"

    echo "для расчета суммы ежемесячных выплат."


    bottom=1.0


    echo

    echo -n "Сумма займа (без запятых -- с точностью до доллара) "

    read principal

    echo -n "Процентная ставка (процент) " # Если 12%, то нужно вводить "12", а не ".12".

    read interest_r

    echo -n "Срок займа (месяцев) "

    read term


    interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # Здесь "scale" -- точность вычислений.


    interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)


    top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)


    echo; echo "Прошу подождать. Вычисления потребуют некоторого времени."


    let "months = $term - 1"

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

    for ((x=$months; x > 0; x--))

    do

    bot=$(echo "scale=9; $interest_rate^$x" | bc)

    bottom=$(echo "scale=9; $bottom+$bot" | bc)

    # bottom = $(($bottom + $bot"))

    done

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

    # Rick Boivie предложил более эффективную реализацию

    #+ цикла вычислений, который дает выигрыш по времени на 2/3.


    # for ((x=1; x <= $months; x++))

    # do

    # bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)

    # done


    # А затем нашел еще более эффективную альтернативу,

    #+ которая выполняется в 20 раз быстрее !!!


    # bottom=`{

    # echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"

    # for ((x=1; x <= $months; x++))

    # do

    # echo 'bottom = bottom * interest_rate + 1'

    # done

    # echo 'bottom'

    # } | bc` # Внедрить цикл 'for' в конструкцию подстановки команд.


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


    # let "payment = $top/$bottom"

    payment=$(echo "scale=2; $top/$bottom" | bc)

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


    echo

    echo "ежемесячные выплаты = \$$payment" # Вывести знак "доллара" перед числом.

    echo


    exit 0


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

    # 1) Добавьте возможность ввода суммы с точностью до цента.

    # 2) Добавьте возможность ввода процентной ставки как в виде процентов, так и в виде десятичного числа -- доли целого.

    # 3) Если вы действительно честолюбивы,

    # добавьте в сценарий вывод полной таблицы помесячных выплат.

    Пример 12-33. Перевод чисел из одной системы счисления в другую

    :

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

    # Shellscript: base.sh - вывод чисел в разных системах счисления (Bourne Shell)

    # Author : Heiner Steven (heiner.steven@odn.de)

    # Date : 07-03-95

    # Category : Desktop

    # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $

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

    # Description

    #

    # Changes

    # 21-03-95 stv исправлена ошибка, возникающая при вводе числа 0xb (0.2)

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


    # ==> Используется в данном документе с разрешения автора.

    # ==> Комментарии добавлены автором документа.


    NOARGS=65

    PN=`basename "$0"` # Имя программы

    VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2


    Usage () {

    echo "$PN - вывод чисел в различных системах счисления, $VER (stv '95)

    Порядок использования: $PN [number ...]


    Если число не задано, то производится ввод со stdin.

    Число может быть:

    двоичное должно начинаться с комбинации символов 0b (например 0b1100)

    восьмеричное должно начинаться с 0 (например 014)

    шестнадцатиричное должно начинаться с комбинации символов 0x (например 0xc)

    десятичное в любом другом случае (например 12)" >&2

    exit $NOARGS

    } # ==> Функция вывода сообщения о порядке использования.


    Msg () {

    for i # ==> [список] параметров опущен.

    do echo "$PN: $i" >&2

    done

    }


    Fatal () { Msg "$@"; exit 66; }


    PrintBases () {

    # Определение системы счисления

    for i # ==> [список] параметров опущен...

    do # ==> поэтому работает с аргументами командной строки.

    case "$i" in

    0b*) ibase=2;; # двоичная

    0x*|[a-f]*|[A-F]*) ibase=16;; # шестнадцатиричная

    0*) ibase=8;; # восьмеричная

    [1-9]*) ibase=10;; # десятичная

    *)

    Msg "Ошибка в числе $i - число проигнорировано"

    continue;;

    esac


    # Удалить префикс и преобразовать шестнадцатиричные цифры в верхний регистр (этого требует bc)

    number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`

    # ==> вместо "/", здесь используется символ ":" как разделитель для sed.


    # Преобразование в десятичную систему счисления

    dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc' используется как калькулятор.

    case "$dec" in

    [0-9]*) ;; # все в порядке

    *) continue;; # ошибка: игнорировать

    esac


    # Напечатать все преобразования в одну строку.

    # ==> 'вложенный документ' -- список команд для 'bc'.

    echo `bc <<!

    obase=16; "hex="; $dec

    obase=10; "dec="; $dec

    obase=8; "oct="; $dec

    obase=2; "bin="; $dec

    !

    ` | sed -e 's: : :g'


    done

    }


    while [ $# -gt 0 ]

    do

    case "$1" in

    --) shift; break;;

    -h) Usage;; # ==> Вывод справочного сообщения.

    -*) Usage;;

    *) break;; # первое число

    esac # ==> Хорошо бы расширить анализ вводимых символов.

    shift

    done


    if [ $# -gt 0 ]

    then

    PrintBases "$@"

    else # чтение со stdin

    while read line

    do

    PrintBases $line

    done

    fi

    Один из вариантов вызова bc -- использование вложенного документа, внедряемого в блок с подстановкой команд. Это особенно актуально, когда сценарий должен передать bc значительный по объему список команд и аргументов.

    variable=`bc << LIMIT_STRING

    options

    statements

    operations

    LIMIT_STRING

    `

    ...или...

    variable=$(bc << LIMIT_STRING

    options

    statements

    operations

    LIMIT_STRING

    )


    Пример 12-34. Пример взаимодействия bc со "встроенным документом"

    #!/bin/bash

    # Комбинирование 'bc' с

    # 'вложенным документом'.


    var1=`bc << EOF

    18.33 * 19.78

    EOF

    `

    echo $var1 # 362.56


    # запись $( ... ) тоже работает.

    v1=23.53

    v2=17.881

    v3=83.501

    v4=171.63


    var2=$(bc << EOF

    scale = 4

    a = ( $v1 + $v2 )

    b = ( $v3 * $v4 )

    a * b + 15.35

    EOF

    )

    echo $var2 # 593487.8452


    var3=$(bc -l << EOF

    scale = 9

    s ( 1.7 )

    EOF

    )

    # Возвращается значение синуса от 1.7 радиана.

    # Ключом "-l" вызывается математическая библиотека 'bc'.

    echo $var3 # .991664810


    # Попробуем функции...

    hyp= # Объявление глобальной переменной.

    hypotenuse () # Расчет гипотенузы прямоугольного треугольника.

    {

    hyp=$(bc -l << EOF

    scale = 9

    sqrt ( $1 * $1 + $2 * $2 )

    EOF

    )

    # К сожалению, функции Bash не могут возвращать числа с плавающей запятой.

    }


    hypotenuse 3.68 7.31

    echo "гипотенуза = $hyp" # 8.184039344


    exit 0

    Пример 12-35. Вычисление числа "пи"

    #!/bin/bash

    # cannon.sh: Аппроксимация числа "пи".


    # Это очень простой вариант реализации метода "Monte Carlo",

    #+ математическое моделирование событий реальной жизни,

    #+ для эмуляции случайного события используются псевдослучайные числа.


    # Допустим, что мы располагаем картой квадратного участка поверхности со стороной квадрата 10000 единиц.

    # На этом участке, в центре, находится совершенно круглое озеро,

    #+ с диаметром в 10000 единиц.

    # Т.е. озеро покрывает почти всю карту, кроме ее углов.

    # (Фактически -- это квадрат со вписанным кругом.)

    #

    # Пусть по этому участку ведется стрельба железными ядрами из древней пушки

    # Все ядра падают где-то в пределах данного участка,

    #+ т.е. либо в озеро, либо на сушу, по углам участка.

    # Поскольку озеро покрывает большую часть участка,

    #+ то большинство ядер будет падать в воду.

    # Незначительная часть ядер будет падать на твердую почву.

    #

    # Если произвести достаточно большое число неприцельных выстрелов по данному участку,

    #+ то отношение попаданий в воду к общему числу выстрелов будет примерно равно

    #+ значению PI/4.

    #

    # По той простой причине, что стрельба фактически ведется только

    #+ по правому верхнему квадранту карты.

    # (Предыдущее описание было несколько упрощено.)

    #

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

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

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

    # К сожалению, это делает вычисления менее точными.


    DIMENSION=10000 # Длина стороны квадратного участка поверхности.

    # Он же -- верхний предел для генератора случайных чисел.


    MAXSHOTS=1000 # Количество выстрелов.

    # 10000 выстрелов (или больше) даст лучший результат,

    # но потребует значительного количества времени.

    PMULTIPLIER=4.0 # Масштабирующий коэффициент.


    get_random ()

    {

    SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')

    RANDOM=$SEED # Из примера "seeding-random.sh"


    let "rnum = $RANDOM % $DIMENSION" # Число не более чем 10000.

    echo $rnum

    }


    distance= # Объявление глобальной переменной.

    hypotenuse () # Расчет гипотенузы прямоугольного треугольника.

    { # Из примера "alt-bc.sh".

    distance=$(bc -l << EOF

    scale = 0

    sqrt ( $1 * $1 + $2 * $2 )

    EOF

    )

    # Установка "scale" в ноль приводит к округлению результата "вниз",

    #+ это и есть то самое ограничение, накладываемое командной оболочкой.

    # Что, к сожалению, снижает точность аппроксимации.

    }


    # main() {


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

    shots=0

    splashes=0

    thuds=0

    Pi=0


    while [ "$shots" -lt "$MAXSHOTS" ] # Главный цикл.

    do


    xCoord=$(get_random) # Получить случайные координаты X и Y.

    yCoord=$(get_random)

    hypotenuse $xCoord $yCoord # Гипотенуза = расстоянию.

    ((shots++))


    printf "#%4d " $shots

    printf "Xc = %4d " $xCoord

    printf "Yc = %4d " $yCoord

    printf "Distance = %5d " $distance # Растояние от

    #+ центра озера,

    #+ с координатами (0,0).


    if [ "$distance" -le "$DIMENSION" ]

    then

    echo -n "ШЛЕП! " # попадание в озеро

    ((splashes++))

    else

    echo -n "БУХ! " # попадание на твердую почву

    ((thuds++))

    fi


    Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)

    # Умножение на коэффициент 4.0.

    echo -n "PI ~ $Pi"

    echo


    done


    echo

    echo "После $shots выстрела, примерное значение числа \"пи\" равно $Pi."

    # Имеет тенденцию к завышению...

    # Вероятно из-за ошибок округления и несовершенства генератора случайных чисел.

    echo


    # }


    exit 0


    # Самое время задуматься над тем, является ли сценарий удобным средством

    #+ для выполнения большого количества столь сложных вычислений.

    #

    # Тем не менее, этот пример может расцениваться как

    # 1) Доказательство возможностей языка командной оболочки.

    # 2) Прототип для "обкатки" алгоритма перед тем как перенести

    #+ его на высокоуровневые языки программирования компилирующего типа.

    dc

    Утилита dc (desk calculator) -- это калькулятор, использующий "Обратную Польскую Нотацию", и ориентированный на работу со стеком.

    Многие стараются избегать испоьзования dc, из-за непривычной формы записи операндов и операций. Однако, dc имеет и своих сторонников.

    Пример 12-36. Преобразование чисел из десятичной в шестнадцатиричную систему счисления

    #!/bin/bash

    # hexconvert.sh: Преобразование чисел из десятичной в шестнадцатиричную систему счисления.


    BASE=16 # Шестнадцатиричная.


    if [ -z "$1" ]

    then

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

    exit $E_NOARGS

    # Необходим аргумент командной строки.

    fi

    # Упражнение: добавьте проверку корректности аргумента.


    hexcvt ()

    {

    if [ -z "$1" ]

    then

    echo 0

    return # "Return" 0, если функции не был передан аргумент.

    fi


    echo ""$1" "$BASE" o p" | dc

    # "o" устанавливает основание системы счисления для вывода.

    # "p" выводит число, находящееся на вершине стека.

    # См. 'man dc'.

    return

    }


    hexcvt "$1"


    exit 0

    Изучение страниц info dc позволит детальнее разобраться с утилитой. Однако, отряд "гуру", которые могут похвастать своим знанием этой мощной, но весьма запутанной утилиты, весьма немногочислен.

    Пример 12-37. Разложение числа на простые множители

    #!/bin/bash

    # factr.sh: Разложение числа на простые множители


    MIN=2 # Не работает с числами меньше 2.

    E_NOARGS=65

    E_TOOSMALL=66


    if [ -z $1 ]

    then

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

    exit $E_NOARGS

    fi


    if [ "$1" -lt "$MIN" ]

    then

    echo "Исходное число должно быть больше или равно $MIN."

    exit $E_TOOSMALL

    fi


    # Упражнение: Добавьте проверку типа числа (не целые числа должны отвергаться).


    echo "Простые множители для числа $1:"

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

    echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc

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

    # Автор вышеприведенной строки: Michel Charpentier <charpov@cs.unh.edu>.

    # Используется с его разрешения (спасибо).


    exit 0

    awk

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

    Пример 12-38. Расчет гипотенузы прямоугольного треугольника

    #!/bin/bash

    # hypotenuse.sh: Возвращает "гипотенузу" прямоугольного треугольника.

    # ( корень квадратный от суммы квадратов катетов)


    ARGS=2 # В сценарий необходимо передать два катета.

    E_BADARGS=65 # Ошибка в аргументах.


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

    then

    echo "Порядок использования: `basename $0` катет_1 катет_2"

    exit $E_BADARGS

    fi


    AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '

    # команды и параметры, передаваемые в awk


    echo -n "Гипотенуза прямоугольного треугольника, с катетами $1 и $2, = "

    echo $1 $2 | awk "$AWKSCRIPT"


    exit 0


    12.9. Прочие команды

    Команды, которые нельзя отнести ни к одной из вышеперечисленных категорий

    jot, seq

    Эти утилиты выводят последовательность целых чисел с шагом, заданным пользователем.

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

    bash$ seq 5

    1

    2

    3

    4

    5


    bash$ seq -s : 5

    1:2:3:4:5


    Обе утилиты, и jot, и seq, очень удобно использовать для генерации списка аргументов в цикле for.

    Пример 12-39. Использование seq для генерации списка аргументов цикла for

    #!/bin/bash

    # Утилита "seq"


    echo


    for a in `seq 80` # или так: for a in $( seq 80 )

    # То же самое, что и for a in 1 2 3 4 5 ... 80 (но как экономит время и силы!).

    # Можно использовать и 'jot' (если эта утилита имеется в системе).

    do

    echo -n "$a "

    done # 1 2 3 4 5 ... 80

    # Пример использования вывода команды для генерации

    # [списка] аргументов цикла "for".


    echo; echo


    COUNT=80 # Да, 'seq' допускает указание переменных в качестве параметра.


    for a in `seq $COUNT` # или так: for a in $( seq $COUNT )

    do

    echo -n "$a "

    done # 1 2 3 4 5 ... 80


    echo; echo


    BEGIN=75

    END=80


    for a in `seq $BEGIN $END`

    # Если "seq" передаются два аргумента, то первый означает начальное число последовательности,

    #+ второй -- последнее,

    do

    echo -n "$a "

    done # 75 76 77 78 79 80


    echo; echo


    BEGIN=45

    INTERVAL=5

    END=80


    for a in `seq $BEGIN $INTERVAL $END`

    # Если "seq" передется три аргумента, то первый аргумент -- начальное число в последовательности,

    #+ второй -- шаг последовательности,

    #+ и третий -- последнее число в последовательности.

    do

    echo -n "$a "

    done # 45 50 55 60 65 70 75 80


    echo; echo


    exit 0

    getopt

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

    Пример 12-40. Использование getopt для разбора аргументов командной строки

    #!/bin/bash

    # ex33a.sh


    # Попробуйте следующие варианты вызова этого сценария.

    # sh ex33a -a

    # sh ex33a -abc

    # sh ex33a -a -b -c

    # sh ex33a -d

    # sh ex33a -dXYZ

    # sh ex33a -d XYZ

    # sh ex33a -abcd

    # sh ex33a -abcdZ

    # sh ex33a -z

    # sh ex33a a

    # Объясните полученные результаты.


    E_OPTERR=65


    if [ "$#" -eq 0 ]

    then # Необходим по меньшей мере один аргумент.

    echo "Порядок использования: $0 -[options a,b,c]"

    exit $E_OPTERR

    fi


    set -- `getopt "abcd:" "$@"`

    # Запись аргументов командной строки в позиционные параметры.

    # Что произойдет, если вместо "$@" указать "$*"?


    while [ ! -z "$1" ]

    do

    case "$1" in

    -a) echo "Опция \"a\"";;

    -b) echo "Опция \"b\"";;

    -c) echo "Опция \"c\"";;

    -d) echo "Опция \"d\" $2";;

    *) break;;

    esac


    shift

    done


    # Вместо 'getopt' лучше использовать встроенную команду 'getopts',

    # См. "ex33.sh".


    exit 0

    run-parts

    Команда run-parts[ 33 ] запускает на исполнение все сценарии, в порядке возрастания имен файлов-сценариев, в заданном каталоге. Естественно, файлы сценариев должны иметь права на исполнение.

    Демон crond вызывает run-parts для запуска сценариев из каталогов /etc/cron.*.

    yes

    По-умолчанию, команда yes выводит на stdout непрерывную последовательность символов y, разделенных символами перевода строки. Исполнение команды можно прервать комбинацией клавиш control-c. Команду yes можно заставить выводить иную последовательность символов. Теперь самое время задаться вопросом о практической пользе этой команды. Основное применение этой команды состоит в том, что вывод от нее может быть передан, через конвейер, другой команде, ожидающей реакции пользователя. В результате получается, своего рода, слабенькая версия команды expect.

    yes | fsck /dev/hda1 запускает fsck в неинтерактивном режиме (будьте осторожны!).

    yes | rm -r dirname имеет тот же эффект, что и rm -rf dirname (будьте осторожны!).

    Внимание! Передача вывода команды yes по конвейеру потенциально опасным командам, таким как fsck или fdisk может дать нежелательные побочные эффекты.

    banner

    Печатает на stdout заданную строку символов (не более 10), рисуя каждый символ строки при помощи символа '#'. Вывод от команды может быть перенаправлен на принтер.

    printenv

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

    bash$ printenv | grep HOME

    HOME=/home/bozo


    lp

    Команды lp и lpr отправляют файлы в очередь печати[ 34 ] для вывода на принтер. Названия этих команд произошли от "line printers".

    bash$ lp file1.txt или bash lp <file1.txt

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

    bash$ pr -options file1.txt | lp

    Программы подготовки текста к печати, такие как groff и Ghostscript, так же могут напрямую взаимодействовать с lp.

    bash$ groff -Tascii file.tr | lp

    bash$ gs -options | lp file.ps

    Команда lpq предназначена для просмотра очереди заданий печати, а lprm -- для удаления заданий из очереди.

    tee

    [UNIX заимствовал эту идею из водопроводного дела.]

    Это опрератор перенаправления, но с некоторыми особенностями. Подобно водопроводным трубам, "tee" позволяет "направить поток" данных в несколько файлов и на stdout одновременно, никак не влияя на сами данные. Эта команда может оказаться очень полезной при отладке.

    tee

    |------> в файл

    |

    ===============|===============

    command--->----|-operator-->---> результат работы команд(ы)

    ===============================

    cat listfile* | sort | tee check.file | uniq > result.file

    (Здесь, в файл check.file будут записаны данные из всех "listfile*", в отсортированном виде до того, как повторяющиеся строки будут удалены командой uniq.)

    mkfifo

    Эта, редко встречающаяся, команда создает именованный канал - очередь, через который производится обмен данными между процессами[ 35 ]. Как правило, один процесс записывает данные в очередь (FIFO), а другой читает данные из очереди. См. Пример A-17.

    pathchk

    Производит проверку полного имени файла -- проверяет, доступны ли на чтение, каталоги в пути к файлу, и не превышает ли длина полного имени файла 255 символов. При несоблюдении одного из условий -- возвращает сообщение об ошибке.

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

    dd

    Эта немного непонятная и "страшная" команда ("data duplicator") изначально использовалась для переноса данных на магнитной ленте между микрокомпьютерами с ОС UNIX и майнфреймами IBM. Команда dd просто создает копию файла (или stdin/stdout), выполняя по пути некоторые преобразования. Один из вариантов: преобразование из ASCII в EBCDIC[ 36 ], dd --help выведет список возможных вариантов преобразований и опций этой мощной утилиты.

    # Изучаем 'dd'.


    n=3

    p=5

    input_file=project.txt

    output_file=log.txt


    dd if=$input_file of=$output_file bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null

    # Извлечет из $input_file символы с n-го по p-й.


    echo -n "hello world" | dd cbs=1 conv=unblock 2> /dev/null

    # Выведет "hello world" вертикально.


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


    Для демонстрации возможностей dd, попробуем перехватить нажатия на клавиши.

    Пример 12-41. Захват нажатых клавиш

    #!/bin/bash

    # Захват нажатых клавиш.


    keypresses=4 # Количество фиксируемых нажатий.


    old_tty_setting=$(stty -g) # Сохранить настройки терминала.


    echo "Нажмите $keypresses клавиши."

    stty -icanon -echo # Запретить канонический режим.

    # Запретить эхо-вывод.

    keys=$(dd bs=1 count=$keypresses 2> /dev/null)

    # 'dd' использует stdin, если "if" не задан.


    stty "$old_tty_setting" # Восстановить настройки терминала.


    echo "Вы нажали клавиши \"$keys\"."


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

    exit 0

    Команда dd имеет возможность произвольного доступа к данным в потоке.

    echo -n . | dd bs=1 seek=4 of=file conv=notrunc

    # Здесь, опция "conv=notrunc" означает, что выходной файлне будет усечен.


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


    Команда dd может использоваться для создания образов дисков, считывая данные прямо с устройств, таких как дискеты, компакт диски, магнитные ленты (Пример A-6). Обычно она используется для создания загрузочных дискет.

    dd if=kernel-image of=/dev/fd0H1440

    Точно так же, dd может скопировать все содержимое дискеты, даже с неизвестной файловой системой, на жесткий диск в виде файла-образа.

    dd if=/dev/fd0 of=/home/bozo/projects/floppy.img

    Еще одно применение dd -- создание временного swap-файла (Пример 28-2) и ram-дисков (Пример 28-3). Она может создавать даже образы целых разделов жесткого диска, хотя и не рекомендуется делать это без особой на то необходимости.

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

    Пример 12-42. Надежное удаление файла

    #!/bin/bash

    # blotout.sh: Надежно удаляет файл.


    # Этот суенарий записывает случайные данные в заданный файл,

    #+ затем записывает туда нули и наконец удаляет файл.

    # После такого удаления даже анализ дисковых секторов

    #+ не даст ровным счетом ничего.


    PASSES=7 # Количество проходов по файлу.

    BLOCKSIZE=1 # операции ввода/вывода в/из /dev/urandom требуют указания размера блока,

    #+ иначе вы не получите желаемого результата.

    E_BADARGS=70

    E_NOT_FOUND=71

    E_CHANGED_MIND=72


    if [ -z "$1" ] # Имя файла не указано.

    then

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

    exit $E_BADARGS

    fi


    file=$1


    if [ ! -e "$file" ]

    then

    echo "Файл \"$file\" не найден."

    exit $E_NOT_FOUND

    fi


    echo; echo -n "Вы совершенно уверены в том, что желаете уничтожить \"$file\" (y/n)? "

    read answer

    case "$answer" in

    [nN]) echo "Передумали? Операция отменена."

    exit $E_CHANGED_MIND

    ;;

    *) echo "Уничтожается файл \"$file\".";;

    esac


    flength=$(ls -l "$file" | awk '{print $5}') # Поле с номером 5 -- это длина файла.


    pass_count=1


    echo


    while [ "$pass_count" -le "$PASSES" ]

    do

    echo "Проход #$pass_count"

    sync # Вытолкнуть буферы.

    dd if=/dev/urandom of=$file bs=$BLOCKSIZE count=$flength

    # Заполнить файл случайными данными.

    sync # Снова вытолкнуть буферы.

    dd if=/dev/zero of=$file bs=$BLOCKSIZE count=$flength

    # Заполнить файл нулями.

    sync # Снова вытолкнуть буферы.

    let "pass_count += 1"

    echo

    done


    rm -f $file # Наконец удалить изрядно "подпорченный" файл.

    sync # Вытолкнуть буферы в последний раз.


    echo "Файл \"$file\" уничтожен."; echo


    # Это довольно надежный, хотя и достаточно медленный способ уничтожения файлов.

    #+ Более эффективно это делает команда "shred",

    #+ входящая в состав пакета GNU "fileutils".


    # Уничтоженный таким образом файл, не сможет быть восстановлен обычными методами.

    # Однако...

    #+ эта метода вероятно НЕ сможет противостоять аналитическим службам

    #+ из СООТВЕТСТВУЮЩИХ ОРГАНОВ


    # Tom Vier разработал пакет "wipe", который более надежно стирает файлы

    #+ чем этот простой сценарий.

    # http://www.ibiblio.org/pub/Linux/utils/file/wipe-2.0.0.tar.bz2


    # Для более глубоко изучения проблемы надежного удаления файлов,

    #+ рекомендую обратиться к cnfnmt Peter Gutmann,

    #+ "Secure Deletion of Data From Magnetic and Solid-State Memory".

    # http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html


    exit 0

    od

    Команда od (octal dump) производит преобразование ввода (или файла) в один или несколько форматов, в соответствии с указанными опциями. При отсутствии опций используется восьмеричный формат (опция -o). Эта команда полезна при просмотре или обработке файлов с двоичными данными, например /dev/urandom. См. Пример 9-26 и Пример 12-10.

    hexdump

    Выводит дамп двоичных данных из файла в восьмеричном, шестнадцатиричном, десятичном виде или в виде ASCII. Эту команду, с массой оговорок, можно назвать эквивалентом команды of od.

    objdump

    Отображает содержимое исполняемого или объектного файла либо в шестнадцатиричной форме, либо в виде дизассемблерного листинга (с ключом -d).

    bash$ objdump -d /bin/ls

    /bin/ls: file format elf32-i386


    Disassembly of section .init:


    080490bc <.init>:

    80490bc: 55 push %ebp

    80490bd: 89 e5 mov %esp,%ebp

    . . .


    mcookie

    Эта команда создает псевдослучайные шестнадцатиричные 128-битные числа, так называемые "magic cookie", обычно используется X-сервером в качестве "сигнатуры" авторизации. В сценариях может использоваться как малоэффективный генератор случайных чисел.

    random000=`mcookie | sed -e '2p'`

    # 'sed' удаляет посторонние символы.


    Конечно, для тех же целей, сценарий может использовать md5.

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

    random001=`md5sum $0 | awk '{print $1}'`

    # 'awk' удаляет имя файла.


    С помощью mcookie можно создавать "уникальные" имена файлов.

    Пример 12-43. Генератор имен файлов

    #!/bin/bash

    # tempfile-name.sh: Генератор имен временных файлов


    BASE_STR=`mcookie` # 32-символьный (128 бит) magic cookie.

    POS=11 # Произвольная позиция в строке magic cookie.

    LEN=5 # $LEN последовательных символов.


    prefix=temp # В конце концов это временный ("temp") файл.


    suffix=${BASE_STR:POS:LEN}

    # Извлечь строку, длиной в 5 символов, начиная с позиции 11.


    temp_filename=$prefix.$suffix

    # Сборка имени файла.


    echo "Имя временного файла = \"$temp_filename\""


    # sh tempfile-name.sh

    # Имя временного файла = temp.e19ea


    exit 0

    units

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

    Пример 12-44. Преобразование метров в мили

    #!/bin/bash

    # unit-conversion.sh


    convert_units () # Принимает в качестве входных параметров единицы измерения.

    {

    cf=$(units "$1" "$2" | sed --silent -e '1p' | awk '{print $2}')

    # Удаляет все кроме коэффициентов преобразования.

    echo "$cf"

    }


    Unit1=miles

    Unit2=meters

    cfactor=`convert_units $Unit1 $Unit2`

    quantity=3.73


    result=$(echo $quantity*$cfactor | bc)


    echo "В $quantity милях $result метров."


    # Что произойдет, если в функцию передать несовместимые единицы измерения,

    #+ например "acres" (акры) and "miles" (мили)?


    exit 0

    m4

    Не команда, а клад, m4 -- это мощный фильтр обработки макроопределений[ 37 ], фактически -- целый язык программирования. Изначально создававшаяся как препроцессор для RatFor, m4 оказалась очень полезной и как самостоятельная утилита. Фактически, m4 сочетает в себе функциональные возможности eval, tr, awk, и дополнительно предоставляет обширные возможности по созданию новых макроопределений.

    В апрельском выпуске, за 2002 год, журнала Linux Journal вы найдете замечательную статью, описывающую возможности утилиты m4.

    Пример 12-45. Пример работы с m4

    #!/bin/bash

    # m4.sh: Демонстрация некоторых возможносией макропроцессора m4


    # Строки

    string=abcdA01

    echo "len($string)" | m4 # 7

    echo "substr($string,4)" | m4 # A01

    echo "regexp($string,[0-1][0-1],\&Z)" | m4 # 01Z


    # Арифметика

    echo "incr(22)" | m4 # 23

    echo "eval(99 / 3)" | m4 # 33


    exit 0

    doexec

    Команда doexec предоставляет возможность передачи произвольного списка аргументов внешней программе. В частности, передавая argv[0] (для сценариев соответствует специальной переменной $0), можно вызвать программу под другим именем, определяя тем самым, ее реакцию.

    Например, Пусть в каталоге /usr/local/bin имеется программа с именем "aaa", которая при вызове doexec /usr/local/bin/aaa list выведет список всех файлов в текущем каталоге, имена которых начинаются с символа "a", а при вызове той же самой программы как doexec /usr/local/bin/aaa delete , она удалит эти файлы.

    Естественно, реакция программы на свое собственное имя должна быть реализована в коде программы, для сценария на языке командной оболочки это может выглядеть примерно так:

    case `basename $0` in

    "name1" ) реакция на вызов под именем name1;;

    "name2" ) реакция на вызов под именем name2;;

    "name3" ) реакция на вызов под именем name3;;

    * ) действия по-умолчанию;;

    esac


    Глава 13. Команды системного администрирования

    Примеры использования большинства этих команд вы найдете в сценариях начальной загрузки и остановки системы, в каталогах /etc/rc.d. Они, обычно, вызываются пользователем root и используются для администрирования системы или восстановления файловой системы. Эти команды должны использоваться с большой осторожностью, так как некоторые из них могут разрушить систему, при неправильном использовании.

    Пользователи и группы

    users

    Выведет список всех зарегистрировавшихся пользователей. Она, до некоторой степени, является эквивалентом команды who -q.

    groups

    Выводит список групп, в состав которых входит текущий пользователь. Эта команда соответствует внутренней переменной $GROUPS, но выводит названия групп, а не их числовые идентификаторы.

    bash$ groups

    bozita cdrom cdwriter audio xgrp


    bash$ echo $GROUPS

    501

    chown, chgrp

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

    root# chown bozo *.txt



    Команда chgrp изменяет группу, которой принадлежит файл или файлы. Чтобы изменить группу, вы должны быть владельцем файла (при этом должны входить в состав указываемой группы) или привилегированным пользователем (root).

    chgrp --recursive dunderheads *.data

    # Группа "dunderheads" станет владельцем всех файлов "*.data"

    #+ во всех подкаталогах текущей директории ($PWD) (благодаря ключу "--recursive").


    useradd, userdel

    Команда useradd добавляет учетную запись нового пользователя в систему и создает домашний каталог для данного пользователя. Противоположная, по смыслу, команда userdel удаляет учетную запись пользователя из системы[ 38 ] и удалит соответствующие файлы.

    Команда adduser является синонимом для useradd и, как правило, является обычной символической ссылкой на useradd.

    id

    Команда id выводит идентификатор пользователя (реальный и эффективный) и идентификаторы групп, в состав которых входит пользователь. По сути -- выводит содержимое переменных $UID, $EUID и $GROUPS.

    bash$ id

    uid=501(bozo) gid=501(bozo) groups=501(bozo),22(cdrom),80(cdwriter),81(audio)


    bash$ echo $UID

    501

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

    who

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

    bash$ who

    bozo tty1 Apr 27 17:45

    bozo pts/0 Apr 27 17:46

    bozo pts/1 Apr 27 17:47

    bozo pts/2 Apr 27 17:49


    С ключом -m -- выводит информацию только о текущем пользователе. Если число аргументов, передаваемых команде, равно двум, то это эквивалентно вызову who -m, например who am i или who The Man.

    bash$ who -m

    localhost.localdomain!bozo pts/2 Apr 27 17:49


    whoami -- похожа на who -m, но выводит только имя пользователя.

    bash$ whoami

    bozo


    w

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

    bash$ w | grep startx

    bozo tty1 - 4:22pm 6:41 4.47s 0.45s startx

    logname

    Выводит имя текущего пользователя (из файла /var/run/utmp). Это довольно близкий эквивалент команды whoami.

    bash$ logname

    bozo


    bash$ whoami

    bozo

    Однако...

    bash$ su

    Password: ......


    bash# whoami

    root

    bash# logname

    bozo

    su

    Команда предназначена для запуска программы или сценария от имени другого пользователя. su rjones -- запускает командную оболочку от имени пользователя rjones. Запуск команды su без параметров означает запуск командной оболочки от имени привилегированного пользователя root. См. Пример A-17.

    sudo

    Исполняет заданную команду от имени пользователя root (или другого пользователя).

    #!/bin/bash


    # Доступ к "секретным" файлам.

    sudo cp /root/secretfile /home/bozo/secret


    Имена пользователей, которым разрешено использовать команду sudo, хранятся в файле /etc/sudoers.

    passwd

    Устанавливает или изменяет пароль пользователя.

    Команда passwd может использоваться в сценариях, но это плохая практика.

    #!/bin/bash

    # set-new-password.sh: Плохая идея.

    # Этот сценарий должен запускаться пользователем root,

    #+ а еще лучше -- не запускать его вообще.


    ROOT_UID=0 # $UID root = 0.

    E_WRONG_USER=65 # Не root?


    if [ "$UID" -ne "$ROOT_UID" ]

    then

    echo; echo "Только root может запускать этот сценарий."; echo

    exit $E_WRONG_USER

    else

    echo; echo "Вам не следовало бы запускать этот сценарий."

    fi


    username=bozo

    NEWPASSWORD=security_violation


    echo "$NEWPASSWORD" | passwd --stdin "$username"

    # Ключ '--stdin' указывает 'passwd'

    #+ получить новый пароль со stdin (или из конвейера).


    echo; echo "Пароль пользователя $username изменен!"


    # Использование команды 'passwd' в сценариях -- опасно.


    exit 0


    ac

    Выводит время работы пользователей, основываясь на записях в файле /var/log/wtmp. Это одна из утилит пакета GNU acct.

    bash$ ac

    total 68.08

    last

    Выводит информацию о последних входах/выходах пользователей в ситему, основываясь на записях в файле /var/log/wtmp. Эта команда может отображать информацию об удаленных (в смысле -- с удаленного терминала) соединениях.

    newgrp

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

    Терминалы

    tty

    Выводит имя терминала текущего пользователя. Обратите внимание: каждое отдельное окно xterm считается отдельным терминалом.

    bash$ tty

    /dev/pts/1

    stty

    Выводит и/или изменяет настройки терминала. Эта сложная команда используется в сценариях для управления поведением терминала.

    Пример 13-1. Установка символа "забоя"

    #!/bin/bash

    # erase.sh: Использование команды "stty" для смены клавиши "забоя" при чтении ввода.


    echo -n "Как Вас зовут? "

    read name # Попробуйте стереть последние символы при вводе.

    # Все работает.

    echo "Вас зовут $name."


    stty erase '#' # Теперь, чтобы стереть символ нужно использовать клавишу "#".

    echo -n "Как Вас зовут? "

    read name # Попробуйте стереть последние символы при вводе с помощью "#".

    echo "Вас зовут $name."


    exit 0

    Пример 13-2. невидимый пароль: Отключение эхо-вывода на терминал

    #!/bin/bash


    echo

    echo -n "Введите пароль "

    read passwd

    echo "Вы ввели пароль: $passwd"

    echo -n "Если кто-нибудь в это время заглядывал Вам через плечо, "

    echo "то теперь он знает Ваш пароль."


    echo && echo # Две пустых строки через "and list".


    stty -echo # Отключить эхо-вывод.


    echo -n "Введите пароль еще раз "

    read passwd

    echo

    echo "Вы ввели пароль: $passwd"

    echo


    stty echo # Восстановить эхо-вывод.


    exit 0

    Перехват нажатия на клавиши с помощью stty.

    Пример 13-3.

    #!/bin/bash

    # keypress.sh: Определение нажатых клавиш.


    echo


    old_tty_settings=$(stty -g) # Сохранить прежние настройки.

    stty -icanon

    Keypress=$(head -c1) # или $(dd bs=1 count=1 2> /dev/null)

    # для других, не GNU, систем


    echo

    echo "Была нажата клавиша \""$Keypress"\"."

    echo


    stty "$old_tty_settings" # Восстановить прежние настройки.


    # Спасибо, Stephane Chazelas.


    exit 0

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

    терминалы и их режимы работы

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

    bash$ stty -a

    speed 9600 baud; rows 36; columns 96; line = 0;

    intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;

    start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O;

    ...

    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt


    В каноническом режиме можно использовать символы редактирования во время ввода.

    bash$ cat > filexxx

    wha<ctl-W>I<ctl-H>foo bar<ctl-U>hello world<ENTER>

    <ctl-D>

    bash$ cat filexxx

    hello world

    bash$ bash$ wc -c < file

    13

    Процесс в терминале получит только 13 символов (12 алфавитных символов и символ перевода строки), хотя пользователь нажал 26 клавиш.

    В неканоническом ("сыром") режиме, каждая нажатая клавиша (включая специальные символы редактирования, такие как ctl-H) сразу же передается исполняющемуся в терминале процессу.

    Под управлением Bash, базовый терминальный редактор заменяется более сложным терминальным редактором Bash. Например, если вы нажмете комбинацию клавиш ctl-A в командной строке Bash, то вы не увидите символов ^A, которые выводит терминал, вместо этого Bash получит символ \1, проанализирует его и переместит курсор в начало строки.

    Stephane Chazelas

    tset

    Выводит или изменяет настройки терминала. Это более слабая версия stty.

    bash$ tset -r

    Terminal type is xterm-xfree86.

    Kill is control-U (^U).

    Interrupt is control-C (^C).


    setserial

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

    # Взято из /etc/pcmcia/serial :


    IRQ=`setserial /dev/$DEVICE | sed -e 's/.*IRQ: //'`

    setserial /dev/$DEVICE irq 0 ; setserial /dev/$DEVICE irq $IRQ


    getty, agetty

    Программа getty или agetty запускается процессом init и обслуживает процедуру входа пользователя в систему. Эти команды не используются в сценариях.

    mesg

    Разрешает или запрещает доступ к терминалу текущего пользователя командой write.

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

    wall

    Имя этой команды -- аббревиатура от "write all", т.е., передать сообщение всем пользователям на все терминалы в сети. Это, в первую очередь, инструмет администратора, который можно использовать, например, для оповещения всех пользователей о предстоящей, в ближайшее время, перезагрузке системы (см. Пример 17-2).

    bash$ wall System going down for maintenance in 5 minutes!

    Broadcast message from bozo (pts/1) Sun Jul 8 13:53:27 2001...


    System going down for maintenance in 5 minutes!


    Если доступ к терминалу был закрыт командой mesg, то сообщение на этом терминале выводиться не будет.

    dmesg

    Выводит все сообщения, выдаваемые системой во время загрузки на stdout. Очень полезная утилита для отладочных целей. Вывод dmesg может анализироваться с помощью grep, sed или awk внутри сценария.

    bash$ dmesg | grep hda

    Kernel command line: ro root=/dev/hda2

    hda: IBM-DLGA-23080, ATA DISK drive

    hda: 6015744 sectors (3080 MB) w/96KiB Cache, CHS=746/128/63

    hda: hda1 hda2 hda3 < hda5 hda6 hda7 > hda4


    Информационные и статистические утилиты

    uname

    Выводит на stdout имя системы. С ключом -a, выводит подробную информацию, содержащую имя системы, имя узла (то есть имя, под которым система известна в сети), версию операционной системы, наименование модификации операционной системы, аппаратную архитектуру (см. Пример 12-4).

    bash$ uname -a

    Linux localhost.localdomain 2.2.15-2.5.0 #1 Sat Feb 5 00:13:43 EST 2000 i686 unknown


    bash$ uname -s

    Linux

    arch

    Выводит тип аппаратной платформы компьютерв. Эквивалентна команде uname -m. См. Пример 10-26.

    bash$ arch

    i686


    bash$ uname -m

    i686

    lastcomm

    Выводит информацию, о ранее выполненных командах, из файла /var/account/pacct. Дополнительно могут указываться команда и пользователь. Это одна из утилит пакета GNU acct.

    lastlog

    Выводит список всех пользователей, с указанием времени последнего входа в систему. Данные берутся из файла /var/log/lastlog.

    bash$ lastlog

    root tty1 Fri Dec 7 18:43:21 -0700 2001

    bin **Never logged in**

    daemon **Never logged in**

    ...

    bozo tty1 Sat Dec 8 21:14:29 -0700 2001


    bash$ lastlog | grep root

    root tty1 Fri Dec 7 18:43:21 -0700 2001


    Исполнение этой команды будет завершаться неудачей, если пользователь, вызвавший утилиту, не имеет прав на чтение файла /var/log/lastlog.

    lsof

    Выводит детальный список открытых, в настоящий момент времени, файлов в виде таблицы. В таблице указаны -- владелец файла, размер файла, тип файла, процесс, открывший файл, и многое другое. Само собой разумеется, что вывод команды lsof может быть обработан, в конвейере, с помощью утилит grep и/или awk.

    bash$ lsof

    COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME

    init 1 root mem REG 3,5 30748 30303 /sbin/init

    init 1 root mem REG 3,5 73120 8069 /lib/ld-2.1.3.so

    init 1 root mem REG 3,5 931668 8075 /lib/libc-2.1.3.so

    cardmgr 213 root mem REG 3,5 36956 30357 /sbin/cardmgr

    ...


    strace

    Диагностическая и отладочная утилита, предназначенная для трассировки системных вызовов и сигналов. В простейшем случае, запускается как: strace COMMAND.

    bash$ strace df

    execve("/bin/df", ["df"], [/* 45 vars */]) = 0

    uname({sys="Linux", node="bozo.localdomain", ...}) = 0

    brk(0) = 0x804f5e4

    ...


    Эквивалентна команде truss.

    nmap

    Сканер сетевых портов. Эта утилита сканирует сервер в поисках открытых портов и сервисов. Это очень важный инструмент, используемый для поиска уязвимостей при настройке системы.

    #!/bin/bash


    SERVER=$HOST # localhost.localdomain (127.0.0.1).

    PORT_NUMBER=25 # порт службы SMTP.


    nmap $SERVER | grep -w "$PORT_NUMBER" # Проверить -- открыт ли данный порт?

    # grep -w -- поиск только целых слов,

    #+ так, например, порт 1025 будет пропущен.


    exit 0


    # 25/tcp open smtp


    free

    Показывает информацию об использовании памяти, в табличной форме. Вывод команды может быть проанализирован с помощью grep, awk или Perl. Команда procinfo тоже выводит эту информацию, среди всего прочего.

    bash$ free

    total used free shared buffers cached

    Mem: 30504 28624 1880 15820 1608 16376

    -/+ buffers/cache: 10640 19864

    Swap: 68540 3128 65412

    Показать размер неиспользуемой памяти RAM:

    bash$ free | grep Mem | awk '{ print $4 }'

    1880

    procinfo

    Извлекает и выводит информацию из файловой системы /proc.

    bash$ procinfo | grep Bootup

    Bootup: Wed Mar 21 15:15:50 2001 Load average: 0.04 0.21 0.34 3/47 6829

    lsdev

    Список аппаратных устройств в системе.

    bash$ lsdev

    Device DMA IRQ I/O Ports

    ------------------------------------------------

    cascade 4 2

    dma 0080-008f

    dma1 0000-001f

    dma2 00c0-00df

    fpu 00f0-00ff

    ide0 14 01f0-01f7 03f6-03f6

    ...


    du

    Выводит сведения о занимаемом дисковом пространстве в каталоге и вложенных подкаталогах. Если каталог не указан, то по-умолчанию выводятся сведения о текущем каталоге.

    bash$ du -ach

    1.0k ./wi.sh

    1.0k ./tst.sh

    1.0k ./random.file

    6.0k .

    6.0k total

    df

    Выводит в табличной форме сведения о смонтированных файловых системах.

    bash$ df

    Filesystem 1k-blocks Used Available Use% Mounted on

    /dev/hda5 273262 92607 166547 36% /

    /dev/hda8 222525 123951 87085 59% /home

    /dev/hda7 1408796 1075744 261488 80% /usr

    stat

    Дает подробную информацию о заданном файле (каталоге или файле устройства) или наборе файлов.

    bash$ stat test.cru

    File: "test.cru"

    Size: 49970 Allocated Blocks: 100 Filetype: Regular File

    Mode: (0664/-rw-rw-r--) Uid: ( 501/ bozo) Gid: ( 501/ bozo)

    Device: 3,8 Inode: 18185 Links: 1

    Access: Sat Jun 2 16:40:24 2001

    Modify: Sat Jun 2 16:40:24 2001

    Change: Sat Jun 2 16:40:24 2001


    Если заданный файл отсутствует, то stat вернет сообщение об ошибке.

    bash$ stat nonexistent-file

    nonexistent-file: No such file or directory


    vmstat

    Выводит информацию о виртуальной памяти.

    bash$ vmstat

    procs memory swap io system cpu

    r b w swpd free buff cache si so bi bo in cs us sy id

    0 0 0 0 11040 2636 38952 0 0 33 7 271 88 8 3 89


    netstat

    Показывает сведения о сетевой подсистеме, такие как: таблицы маршрутизации и активные соединения. Эта утилита получает сведения из /proc/net (Глава 27). См. Пример 27-2.

    netstat -r -- эквивалентна команде route.

    uptime

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

    bash$ uptime

    10:28pm up 1:57, 3 users, load average: 0.17, 0.34, 0.27

    hostname

    Выводит имя узла (сетевое имя системы). С помощью этой команды устанавливается сетевое имя системы в сценарии /etc/rc.d/rc.sysinit. Эквивалентна команде uname -n и внутренней переменной $HOSTNAME.

    bash$ hostname

    localhost.localdomain


    bash$ echo $HOSTNAME

    localhost.localdomain

    hostid

    Выводит 32-битный шестнадцатиричный идентификатор системы.

    bash$ hostid

    7f0100


    Эта команда генерирует "уникальный" числовой идентификатор системы. Некоторые программные продукты используют этот идентификатор в процедуре регистрации. К сожалению, при генерации идентификатора, hostid использует только IP адрес системы, переводя его в шестнадцатиричное представление и переставляя местами пары байт.

    Обычно, IP адрес системы можно найти в файле /etc/hosts.

    bash$ cat /etc/hosts

    127.0.0.1 localhost.localdomain localhost


    Переставив местами байты, попарно, в начальном адресе 127.0.0.1, мы получим 0.127.1.0, в шестнадцатиричном представлении это будет 007f0100, что в точности совпадает с приведенным выше результатом выполнения hostid. Наверняка можно найти несколько миллионов компьютеров с таким же "уникальным" идентификатором.

    sar

    Команда sar (system activity report) выводит очень подробную статистику о функционировании операционной системы. Эту команду можно найти в отдельных коммерческих дистрибутивах UNIX-систем. Она, как правило, не входит в базовый комплект пакетов Linux-систем. Она входит в состав пакета sysstat utilities, автор: Sebastien Godard.

    bash$ sar

    Linux 2.4.7-10 (localhost.localdomain) 12/31/2001


    10:30:01 AM CPU %user %nice %system %idle

    10:40:00 AM all 1.39 0.00 0.77 97.84

    10:50:00 AM all 76.83 0.00 1.45 21.72

    11:00:00 AM all 1.32 0.00 0.69 97.99

    11:10:00 AM all 1.17 0.00 0.30 98.53

    11:20:00 AM all 0.51 0.00 0.30 99.19

    06:30:00 PM all 100.00 0.00 100.01 0.00

    Average: all 1.39 0.00 0.66 97.95

    readelf

    Показывает сведения о заданном бинарном файле формата elf. Входит в состав пакета binutils.

    bash$ readelf -h /bin/bash

    ELF Header:

    Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

    Class: ELF32

    Data: 2's complement, little endian

    Version: 1 (current)

    OS/ABI: UNIX - System V

    ABI Version: 0

    Type: EXEC (Executable file)

    . . .

    size

    Команда size [/path/to/binary] выведет информацию о размерах различных сегментов в исполняемых или библиотечных файлах. В основном используется программистами.

    bash$ size /bin/bash

    text data bss dec hex filename

    495971 22496 17392 535859 82d33 /bin/bash


    Системный журнал

    logger

    Добавляет в системный журнал (/var/log/messages) сообщение от пользователя. Для добавления сообщения пользователь не должен обладать привилегиями суперпользователя.

    logger Experiencing instability in network connection at 23:10, 05/21.

    # Теперь попробуйте дать команду 'tail /var/log/messages'.


    Встраивая вызов logger в сценарии, вы получаете возможность заносить отладочную информацию в системный журнал /var/log/messages.

    logger -t $0 -i Logging at line "$LINENO".

    # Ключ "-t" задает тэг записи в журнале.

    # Ключ "-i" -- записывает ID процесса.


    # tail /var/log/message

    # ...

    # Jul 7 20:48:58 localhost ./test.sh[1712]: Logging at line 3.


    logrotate

    Эта утилита производит манипуляции над системным журналом: ротация, сжатие, удаление и/или отправляет его по электронной почте, по мере необходимости. Как правило, утилита logrotate вызывается демоном crond ежедневно.

    Добавляя соответствующие строки в /etc/logrotate.conf, можно заставить logrotate обрабатывать не только системный журнал, но и ваш личный.

    Управление заданиями

    ps

    Process Statistics: Список исполняющихся в данный момент процессов. Обычно вызывается с ключами ax, вывод команды может быть обработан командами grep или sed, с целью поиска требуемого процесса (см. Пример 11-10 и Пример 27-1).

    bash$ ps ax | grep sendmail

    295 ? S 0:00 sendmail: accepting connections on port 25

    pstree

    Список исполняющихся процессов в виде "дерева". С ключом -p -- вместе с именами процессов отображает их PID.

    top

    Выводит список наиболее активных процессов. С ключом -b -- отображение ведется в обычном текстовом режиме, что дает возможность анализа вывода от команды внутри сценария.

    bash$ top -b

    8:30pm up 3 min, 3 users, load average: 0.49, 0.32, 0.13

    45 processes: 44 sleeping, 1 running, 0 zombie, 0 stopped

    CPU states: 13.6% user, 7.3% system, 0.0% nice, 78.9% idle

    Mem: 78396K av, 65468K used, 12928K free, 0K shrd, 2352K buff

    Swap: 157208K av, 0K used, 157208K free 37244K cached


    PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND

    848 bozo 17 0 996 996 800 R 5.6 1.2 0:00 top

    1 root 8 0 512 512 444 S 0.0 0.6 0:04 init

    2 root 9 0 0 0 0 SW 0.0 0.0 0:00 keventd

    ...


    nice

    Запускает фоновый процесс с заданным приоритетом. Приоритеты могут задаваться числом из диапазона от 19 (низший приоритет) до -20 (высший приоритет). Но только root может указать значение приоритета меньше нуля (отрицательные значения). См. так же команды renice, snice и skill.

    nohup

    Запуск команд в режиме игнорирования сигналов прерывания и завершения, что предотвращает завершение работы команды даже если пользователь, запустивший ее, вышел из системы. Если после команды не указан символ &, то она будет исполняться как процесс "переднего плана". Если вы собираетесь использовать nohup в сценариях, то вам потребуется использовать его в связке с командой wait, чтобы не породить процесс "зомби".

    pidof

    Возвращает идентификатор процесса (pid) по его имени. Поскольку многие команды управления процессами, такие как kill и renice, требуют указать pid процесса, а не его имя, то pidof может сослужить неплохую службу при идентификации процесса по его имени. Эта коменда может рассматриваться как приблизительный эквивалент внутренней переменной $PPID.

    bash$ pidof xclock

    880


    Пример 13-4. Использование команды pidof при остановке процесса

    #!/bin/bash

    # kill-process.sh


    NOPROCESS=2


    process=xxxyyyzzz # Несуществующий процесс.

    # Только в демонстрационных целях...

    # ... чтобы не уничтожить этим сценарием какой-нибудь процесс.

    #

    # Если с помощью этого сценария вы задумаете разрыватть связь с Internet, то

    # process=pppd


    t=`pidof $process` # Поиск pid (process id) процесса $process.

    # pid требует команда 'kill' (невозможно остановить процесс, указав его имя).


    if [ -z "$t" ] # Если процесс с таким именем не найден, то 'pidof' вернет null.

    then

    echo "Процесс $process не найден."

    exit $NOPROCESS

    fi


    kill $t # В некоторых случаях может потребоваться 'kill -9'.


    # Здесь нужно проверить -- был ли уничтожен процесс.

    # Возможно так: " t=`pidof $process` ".


    # Этот сценарий мог бы быть заменен командой

    # kill $(pidof -x process_name)

    # но это было бы не так поучительно.


    exit 0

    fuser

    Возвращает идентификаторы процессов, использующих указанный файл(ы) или каталог. С ключом -k, завершает найденные процессы. Может с успехом использоваться для защиты системы, особенно в сценариях разграничения доступа к системным службам.

    crond

    Планировщик заданий. С его помощью выполняются такие задачи, как очистка и удаление устаревших файлов системных журналов, обновление базы данных slocate. Это суперпользовательская версия команды at (хотя любой пользователь может создать собственную таблицу crontab). Эта утилита запускается как фоновый процесс-daemon и выполняет задания, находящиеся в файле /etc/crontab.

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

    init

    init -- предок (родитель) всех процессов в системе. Вызывается на последнем этапе загрузки системы и определяет уровень загрузки (runlevel) из файла /etc/inittab.

    telinit

    Символическая ссылка на init -- инструмент для смены уровня загрузки (runlevel), как правило используется при обслуживании системы или восстановлении файловой системы. Может быть вызвана только суперпользователем. Эта команда может быть очень опасна, при неумелом обращении -- прежде чем использовать ее, убедитесь в том, что вы совершенно точно понимаете что делаете!

    runlevel

    Выводит предыдущий и текущий уровни загрузки (runlevel). Уровень загрузки может иметь одно из 6 значений: 0 -- остановка системы, 1 -- однопользовательский режим, 2 или 3 -- многопользовательский режим, 5 -- многопользовательский режим и запуск X Window, 6 -- перезагрузка. Уровни загрузки определяются из файла /var/run/utmp.

    halt, shutdown, reboot

    Набор команд для остановки системы, обычно перед выключением питания.

    Команды для работы с сетью

    ifconfig

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

    # Фрагменты кода из /etc/rc.d/init.d/network


    # ...


    # Проверка сетевой полсистемы.

    [ ${NETWORKING} = "no" ] && exit 0


    [ -x /sbin/ifconfig ] || exit 0


    # ...


    for i in $interfaces ; do

    if ifconfig $i 2>/dev/null | grep -q "UP" >/dev/null 2>&1 ; then

    action "Останавливается $i: " ./ifdown $i boot

    fi

    # Ключ "-q", характерный для GNU-версии "grep", означает "quiet" ("молча"), т.е. подавляет вывод.

    # Поэтому нет необходимости переадресовывать вывод на /dev/null.


    # ...


    echo "В настоящее время активны устройства:"

    echo `/sbin/ifconfig | grep ^[a-z] | awk '{print $1}'`

    # ^^^^^ скобки необходимы для предотвращения подстановки имен файлов (globbing).

    # Следующий код делает то же самое.

    # echo $(/sbin/ifconfig | awk '/^[a-z]/ { print $1 })'

    # echo $(/sbin/ifconfig | sed -e 's/ .*//')

    # Спасибо S.C. за комментарии.

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

    route

    Выводит сведения о таблице маршрутизации ядра или вносит туда изменения.

    bash$ route

    Destination Gateway Genmask Flags MSS Window irtt Iface

    pm3-67.bozosisp * 255.255.255.255 UH 40 0 0 ppp0

    127.0.0.0 * 255.0.0.0 U 40 0 0 lo

    default pm3-67.bozosisp 0.0.0.0 UG 40 0 0 ppp0


    chkconfig

    Проверка сетевой конфигурации. Обслуживает список, запускаемых на этапе загрузки, сетевых сервисов, список сервисов хранится в каталогах /etc/rc?.d (строго говоря, chkconfig работает не только с сетевыми сервисами, а с сервисами вообще, не зависимо от того сетевые это службы или нет. прим. перев.).

    Изначально эта утилита была перенесена в Red Hat Linux из ОС IRIX, chkconfig входит в состав далеко не всех дистрибутивов Linux.

    bash$ chkconfig --list

    atd 0:off 1:off 2:off 3:on 4:on 5:on 6:off

    rwhod 0:off 1:off 2:off 3:off 4:off 5:off 6:off

    ...


    tcpdump

    "Сниффер" ("sniffer") сетевых пакетов. Инструмент для перехвата и анализа сетевого трафика по определенным критериям.

    Дамп трафика ip-пакетов между двумя узлами сети -- bozoville и caduceus:

    bash$ tcpdump ip host bozoville and caduceus


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

    Команды для работы с файловыми системами

    mount

    Выполняет монтирование файловой системы, обычно на устройстве со сменными носителями, такими как дискеты или CDROM. Файл /etc/fstab содержит перечень доступных для монтирования файловых систем, разделов и устройств, включая опции монтирования, благодаря этому файлу, монтирование может производиться автоматически или вручеую. Файл /etc/mtab содержит список смонтированных файловых систем и разделов (включая виртуальные, такие как /proc).

    mount -a -- монтирует все (all) файловые системы и разделы, перечисленные в /etc/fstab, за исключением тех, которые имеют флаг noauto. Эту команду можно встретить в сценариях начальной загрузки системы из /etc/rc.d (rc.sysinit или нечто похожее).

    mount -t iso9660 /dev/cdrom /mnt/cdrom

    # Монтирование CDROM-а

    mount /mnt/cdrom

    # Более короткий и удобный вариант, если точка монтирования /mnt/cdrom описана в /etc/fstab


    Эта команда может даже смонтировать обычный файл как блочное устройство. Достигается это за счет связывания файла с loopback-устройством. Эту возможность можно использовать для проверки ISO9660 образа компакт-диска перед его записью на болванку[ 39 ].

    Пример 13-5. Проверка образа CD

    # С правами root...


    mkdir /mnt/cdtest # Подготовка точки монтирования.


    mount -r -t iso9660 -o loop cd-image.iso /mnt/cdtest # Монтирование образа диска.

    # ключ "-o loop" эквивалентен "losetup /dev/loop0"

    cd /mnt/cdtest # Теперь проверим образ диска.

    ls -alR # Вывод списка файлов

    umount

    Отмонтирует смонтированную файловую систему. Перед тем как физически вынуть компакт-диск или дискету из устройства, это устройство должно быть отмонтировано командой umount, иначе файловая система может оказаться поврежденной (особенно это относится к накопителям на гибких магнитных дисках, прим. перев.).

    umount /mnt/cdrom

    # Теперь вы можете извлечь диск из привода.


    Утилита automount, если она установлена, может выполнять атоматическое монтирование/размонтирование устройств со сменными носителями, такие как дискеты и компакт-диски. На ноутбуках со сменными устройствами FDD и CDROM, такой подход может привести к возникновению определенных проблем.

    sync

    Принудительный сброс содержимого буферов на жесткий диск (синхронизация содержимого буферов ввода-вывода и устройства-носителя). Несмотря на то, что нет такой уж острой необходимости в этой утилите, тем не менее sync придает уверенности системным администраторам или пользователям в том, что их данные будут сохранены на жестком диске, и не будут потеряны в случае какого-либо сбоя. В былые дни, команда sync; sync (дважды -- для абсолютной уверенности) была упреждающей мерой перед перезагрузкой системы.

    Иногда возникает необходимость принудительной синхронизации буферов ввода-вывода с содержимым на магнитном носителе, как, например, при надежном удалении файла (см. Пример 12-42) или когда наблюдаются скачки напряжения в сети электроснабжения.

    losetup

    Устанавливает и конфигурирует loopback-устройства.

    Пример 13-6. Создание файловой системы в обычном файле

    SIZE=1048576 # 1 Мб


    head -c $SIZE < /dev/zero > file # Создается файл нужного размера.

    losetup /dev/loop0 file # Файл назначается как loopback-устройство.

    mke2fs /dev/loop0 # Создание файловой системы.

    mount -o loop /dev/loop0 /mnt # Монтирование только что созданной файловой системы.


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

    mkswap

    Создание swap-раздела или swap-файла. Созданный swap-раздел (файл) нужно затем подключить командой swapon.

    swapon, swapoff

    Разрешает/запрещает использование swap-раздела (файла). Эта команда обычно используется во время загрузки системы или во время остановки.

    mke2fs

    Создает файловую систему ext2. Должна вызываться с правами суперпользователя.

    Пример 13-7. Добавление нового жесткого диска

    #!/bin/bash


    # Добавление в систему второго жесткого диска.

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

    # Взято из статьи автора документа.

    # "Linux Gazette", выпуск #38, http://www.linuxgazette.com.


    ROOT_UID=0 # Этот сценарий должен запускать только root.

    E_NOTROOT=67 # Код ошибки, если сценарий запущен простым пользователем.


    if [ "$UID" -ne "$ROOT_UID" ]

    then

    echo "Для запуска этого сценария вы должны обладать правами root."

    exit $E_NOTROOT

    fi


    # Будьте крайне осторожны!

    # Если что-то пойдет не так, то вы можете потерять текущую файловую систему.


    NEWDISK=/dev/hdb # Предполагается, что /dev/hdb -- это новое устройство. Проверьте!

    MOUNTPOINT=/mnt/newdisk # Или выберите иное устройство для монтирования.


    fdisk $NEWDISK

    mke2fs -cv $NEWDISK1 # Проверка на "плохие" блоки (bad blocks) и подробный вывод.

    # Обратите внимание: /dev/hdb1, *не* то же самое, что /dev/hdb!

    mkdir $MOUNTPOINT

    chmod 777 $MOUNTPOINT # Сделать новое устройство доступным для всех пользователей.


    # Теперь проаерим...

    # mount -t ext2 /dev/hdb1 /mnt/newdisk

    # Попробуйте создать каталог.

    # Если получилось -- отмонтируйте устройство и продолжим.


    # Последний штрих:

    # Добавьте следующую строку в /etc/fstab.

    # /dev/hdb1 /mnt/newdisk ext2 defaults 1 1


    exit 0

    См. также Пример 13-6 и Пример 28-3.

    tune2fs

    Настройка отдельных параметров файловой системы ext2, например счетчик максимального количества монтирований без проверки. Должна вызываться с привилегиями пользователя root.

    Очень опасная утилита. Вы можете использовать ее только на свой страх и риск, поскольку, по неосторожности, вы запросто можете разрушить файловую систему.

    dumpe2fs

    Выводит на stdout очень подробную информацию о файловой системе. Должна вызываться с привилегиями пользователя root.

    root# dumpe2fs /dev/hda7 | grep 'ount count'

    dumpe2fs 1.19, 13-Jul-2000 for EXT2 FS 0.5b, 95/08/09

    Mount count: 6

    Maximum mount count: 20

    hdparm

    Выводит или изменяет параметры настройки жесткого диска. Должна вызываться с привилегиями пользователя root. Потенциально опасна при неправильном использовании.

    fdisk

    Создание или изменение таблицы разделов на устройствах хранения информации, обычно -- жестких дисках. Должна вызываться с привилегиями пользователя root.

    Пользуйтесь этой утилитой с особой осторожностью, т.к. при неправильном использовании можно легко разрушить существующую файловую систему.

    fsck, e2fsck, debugfs

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

    fsck: интерфейсная утилита для проверки файловых систем в UNIX (может вызывать другие утилиты проверки).

    e2fsck: проверка файловой системы ext2.

    debugfs: отладчик файловой системы ext2. Одно из применений этой универсальной (и опасной) утилиты -- это восстановление удаленных файлов. Только для опытных пользователей!

    Все эти утилиты должны вызываться с привилегиями пользователя root. При неправильном использовании, любая из них может разрушить файловую систему.

    badblocks

    Выполняет поиск плохих блоков (физические повреждения носителей) на устройствах хранения информации. Эта команда может использоваться для поиска плохих блоков при форматировании вновь устанавливаемых жестких дисков или для проверки устройств резервного копирования[ 40 ]. Например, badblocks /dev/fd0, проверит дискету на наличие поврежденных блоков.

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

    mkbootdisk

    Создание загрузочной дискеты, которая может быть использована для загрузки системы, если, например, была повреждена MBR (master boot record -- главная загрузочная запись). Команда mkbootdisk -- это сценарий на языке командной оболочки Bash, автор: Erik Troan, располагается в каталоге /sbin.

    chroot

    CHange ROOT -- смена корневого каталога. Обычно, команды и утилиты ориентируются в файловой системе посредством переменной $PATH, относительно корневого каталога /. Команда chroot изменяет корневой каталог по-умолчанию на другой (рабочий каталог также изменяется). Эта утилита, как правило, используется с целью защиты системы, например, с ее помощью можно ограничить доступ к разделам файловой системы для пользователей, подключающихся к системе с помощью telnet (это называется -- "поместить пользователя в chroot окружение"). Обратите внимание: после выполнения команды chroot изменяется путь к исполняемым файлам системы.

    Команда chroot /opt приведет к тому, что все обращения к каталогу /usr/bin будут переводиться на каталог /opt/usr/bin. Аналогично, chroot /aaa/bbb /bin/ls будет пытаться вызвать команду ls из каталога /aaa/bbb/bin, при этом, корневым каталогом для ls станет каталог /aaa/bbb. Поместив строчку alias XX 'chroot /aaa/bbb ls' в пользовательский ~/.bashrc, можно эффективно ограничить доступ команде "XX", запускаемой пользователем, к разделам файловой системы.

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

    lockfile

    Эта утилита входит в состав пакета procmail (www.procmail.org). Она создает lock file, файл-семафор (или, если угодно, файл блокировки), который управляет доступом к заданному файлу, устройству или ресурсу. Lock file служит признаком того, что данный файл, устройство или ресурс "занят" некоторым процессом, и ограничивает (или вообще запрещает) доступ к ресурсу другим процессам.

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

    Как правило, файлы блокировки создаются в каталоге /var/lock. Проверка наличия файла блокировки может быть проверена примерно таким образом:.

    appname=xyzip

    # Приложение "xyzip" создает файл блокировки "/var/lock/xyzip.lock".


    if [ -e "/var/lock/$appname.lock ]

    then

    ...


    mknod

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

    tmpwatch

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

    MAKEDEV

    Утилита предназначена для создания файлов-устройств. Должна запускаться с привилегиями пользователя root, в каталоге /dev.

    root# ./MAKEDEV

    Это своего рода расширенная версия утилиты mknod.

    Команды резервного копирования

    dump, restore

    Команда dump создает резервные копии целых файловых систем, обычно используется в крупных системах и сетях[ 41 ]. Она считывает дисковые разделы и сохраняет их в файле, в двоичном формате. Созданные таким образом файлы, могут быть сохранены на каком-либо носителе -- на жестком диске или магнитной ленте. Команда restore -- "разворачивает" файлы, созданные утилитой dump.

    fdformat

    Выполняет низкоуровневое форматирование дискет.

    Команды управления системными ресурсами

    ulimit

    Устанавливает верхний предел для системных ресурсов. Как правило вызывается с ключом -f, что означает наложение ограничений на размер файлов (ulimit -f 1000 ограничит размер вновь создаваемых файлов одним мегабайтом). Ключ -c ограничивает размер файлов coredump (ulimit -c 0 запретит создание coredump-файлов). Обычно, все ограничения прописываются в файле /etc/profile и/или ~/.bash_profile (см. Глава 26).

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

    #!/bin/bash


    while 1 # Бесконечный цикл.

    do

    $0 & # Этот сценарий вызывает сам себя . . .

    #+ порождая дочерние процессы бесконечное число раз . . .

    #+ точнее -- до тех пор, пока не иссякнут системные ресурсы.

    done # Это печально известный сценарий "sorcerer's appentice".


    exit 0 # Сценарий никогда не завершит свою работу.


    Команда ulimit -Hu XX (где XX -- это верхний предел количества процессов, которые может запустить пользователь одновременно) в /etc/profile вызовет аварийное завершение этого сценария, когда количество процессов превысит установленный предел.

    umask

    Установка маски режима создания файлов. Накладывает ограничения на атрибуты по-умлчанию для создаваемых файлов. Маска представляет собой восьмеричное значение и определяет запрещенные атрибуты файла. Например, umask 022 удаляет права на запись для группы и прочих пользователей (у файлов, создававшихся с режимом 777, он оказывается равным 755; а режим 666 преобразуется в 644, т.е. 777 NAND 022 = 755, 666 NAND 022 = 644)[ 42 ]. Конечно же, впоследствие, пользователь может откорректировать права доступа к своему файлу с помощью команды chmod. Как правило, значение umask устанавливается в файле /etc/profile и/или ~/.bash_profile (см. Глава 26).

    rdev

    Выводит или изменяет корневое устройство, размер RAM-диска или видео режим. Функциональные возможности утилиты rdev вообще повторяются загрузчиком lilo, но rdev по прежнему остается востребованной, например, при установке электронного диска (RAM-диск). Это еще одна потенциально опасная, при неумелом использовании, утилита.

    Команды для работы с модулями ядра

    lsmod

    Выводит список загруженных модулей.

    bash$ lsmod

    Module Size Used by

    autofs 9456 2 (autoclean)

    opl3 11376 0

    serial_cs 5456 0 (unused)

    sb 34752 0

    uart401 6384 0 [sb]

    sound 58368 0 [opl3 sb uart401]

    soundlow 464 0 [sound]

    soundcore 2800 6 [sb sound]

    ds 6448 2 [serial_cs]

    i82365 22928 2

    pcmcia_core 45984 0 [serial_cs ds i82365]


    Команда cat /proc/modules выведет на экран эту же информацию.

    insmod

    Принудительная загрузка модуля ядра (старайтесь вместо insmod использовать команду modprobe). Должна вызываться с привилегиями пользователя root.

    rmmod

    Выгружает модуль ядра. Должна вызываться с привилегиями пользователя root.

    modprobe

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

    depmod

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

    Прочие команды

    env

    Запускает указанную программу или сценарий с модифицированными переменными окружения (не изменяя среду системы в целом, изменения касаются только окружения запускаемой программы/сценария). Посредством [varname=xxx], устанавливает значение переменной окружения varname, которая будет доступна из запускаемой программы/сценария. Без параметров -- просто выводит список переменных окружения с их значениями.

    В Bash, и других производных от Bourne shell, имеется возможность установки переменных окружения и запуска программы (или сценария) одной командной строкой.

    var1=value1 var2=value2 commandXXX

    # $var1 и $var2 -- будут определены только в окружении для 'commandXXX'.


    В первой строке сценария ("sha-bang") можно указать команду env, если путь к командному интерпретатору не известен.

    #! /usr/bin/env perl


    print "Этот сценарий, на языке программирования Perl, будет запущен,\n";

    print "даже если я не знаю где в системе находится Perl.\n";


    # Прекрасно подходит для написания кросс-платформенных сценариев,

    # когда Perl может находиться совсем не там, где вы ожидаете.

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


    ldd

    Выводит список разделяемых библиотек, необходимых для исполняемого файла.

    bash$ ldd /bin/ls

    libc.so.6 => /lib/libc.so.6 (0x4000c000)

    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)

    watch

    Периодически запускает указанную программу с заданным интервалом времени.

    По-умолчанию интервал между запусками принимается равным 2 секундам, но может быть изменен ключом -n.

    watch -n 5 tail /var/log/messages

    # Выводит последние 10 строк из системного журнала, /var/log/messages, каждые пять секунд.


    strip

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

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

    nm

    Выводит список символов (используемых в целях отладки), содержащихся в откомпилированном двоичном файле.

    rdist

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

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

    Пример 13-8. Сценарий killall, из каталога /etc/rc.d/init.d

    #!/bin/sh


    # --> Комментарии, начинающиеся с "# -->", добавлены автором документа.


    # --> Этот сценарий является частью пакета 'rc'-сценариев

    # --> Автор: Miquel van Smoorenburg, <miquels@drinkel.nl.mugnet.org>


    # --> Этот сценарий характерен для дистрибутива Red Hat

    # --> (в других дистрибутивах может отсутствовать).


    # Остановить все ненужные сервисы которые еще работают (собственно,

    # их уже не должно быть, это лишь формальная проверка, на всякий случай)


    for i in /var/lock/subsys/*; do

    # --> Стандартный заголовок цикла for/in, но, поскольку "do"

    # --> находится в той же самой строке, что и for,

    # --> необходимо разделить их символом ";".

    # Проверяется наличие сценария.

    [ ! -f $i ] && continue

    # --> Очень интересное использование "И-списка", эквивалентно:

    # --> if [ ! -f "$i" ]; then continue


    # Получить имя подсистемы.

    subsys=${i#/var/lock/subsys/}

    # --> В данном случае совпадает с именем файла.

    # --> Это точный эквивалент subsys=`basename $i`.


    # --> Таким образом получается имя файла блокировки (если он присутствует,

    # -->+ то это означает, что процесс запущен).

    # --> См. описание команды "lockfile" выше.


    # Остановить службу.

    if [ -f /etc/rc.d/init.d/$subsys.init ]; then

    /etc/rc.d/init.d/$subsys.init stop

    else

    /etc/rc.d/init.d/$subsys stop

    # --> Останавливает задачу или демона

    # --> посредством встроенной команды 'stop'.

    fi

    done

    Вобщем все довольно понятно. Кроме хитрого манипулирования с переменными, при определении имени подсистемы (службы), здесь нет ничего нового.

    Упражнение 1. Просмотрите сценарий halt в каталоге /etc/rc.d/init.d. Он по размеру немного больше, чем killall, но придерживается той же концепции. Создайте копию этого сценария в своем домашнем каталоге и поэкспериментируйте с ним (НЕ запускайте его с привилегиями суперпользователя). Попробуйте запустить его с ключами -vn (sh -vn scriptname). Добавьте свои комментарии. Замените действующие команды на "echo".

    Упражнение 2. Просмотрите другие, более сложные сценарии из /etc/rc.d/init.d. Попробуйте разобраться в их работе. Проверьте их работу, следуя рекомендациям, приведенным выше. За дополнительной информацией вы можете обратиться к документу sysvinitfiles в каталоге /usr/share/doc/initscripts-?.??, который входит в пакет документации к "initscripts".


    Глава 14. Подстановка команд

    Подстановка команд -- это подстановка результатов выполнения команды[ 43 ] или даже серии команд; буквально, эта операция позволяет вызвать команду в другом окружении.

    Классический пример подстановки команд -- использование обратных одиночных кавычек (`...`). Команды внутри этих кавычек представляют собой текст командной строки.

    script_name=`basename $0`

    echo "Имя этого файла-сценария: $script_name."


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

    rm `cat filename` # здесь "filename" содержит список удаляемых файлов.

    #

    # S. C. предупреждает, что в данном случае может возникнуть ошибка "arg list too long".

    # Такой вариант будет лучше: xargs rm -- < filename

    # ( -- подходит для случая, когда "filename" начинается с символа "-" )


    textfile_listing=`ls *.txt`

    # Переменная содержит имена всех файлов *.txt в текущем каталоге.

    echo $textfile_listing


    textfile_listing2=$(ls *.txt) # Альтернативный вариант.

    echo $textfile_listing2

    # Результат будет тем же самым.


    # Проблема записи списка файлов в строковую переменную состоит в том,

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

    #

    # Как вариант решения проблемы -- записывать список файлов в массив.

    # shopt -s nullglob # При несоответствии, имя файла игнорируется.

    # textfile_listing=( *.txt )

    #

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


    Подстанавливаемая команда может получиться разбитой на отдельные слова.

    COMMAND `echo a b` # 2 аргумента: a и b


    COMMAND "`echo a b`" # 1 аргумент: "a b"


    COMMAND `echo` # без аргументов


    COMMAND "`echo`" # один пустой аргумент


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


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

    # cd "`pwd`" # Должна выполняться всегда.

    # Однако...


    mkdir 'dir with trailing newline

    '


    cd 'dir with trailing newline

    '


    cd "`pwd`" # Ошибка:

    # bash: cd: /tmp/dir with trailing newline: No such file or directory


    cd "$PWD" # Выполняется без ошибки.


    old_tty_setting=$(stty -g) # Сохранить настройки терминала.

    echo "Нажмите клавишу "

    stty -icanon -echo # Запретить "канонический" режим терминала.

    # Также запрещает эхо-вывод.

    key=$(dd bs=1 count=1 2> /dev/null) # Поймать нажатие на клавишу.

    stty "$old_tty_setting" # Восстановить настройки терминала.

    echo "Количество нажатых клавиш = ${#key}." # ${#variable} = количество символов в переменной $variable

    #

    # Нажмите любую клавишу, кроме RETURN, на экране появится "Количество нажатых клавиш = 1."

    # Нажмите RETURN, и получите: "Количество нажатых клавиш = 0."

    # Символ перевода строки будет "съеден" операцией подстановки команды.


    Спасибо S.C.


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

    dir_listing=`ls -l`

    echo $dir_listing # без кавычек


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


    # Однако, вы получите:

    # total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo

    # bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh


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


    echo "$dir_listing" # в кавычках

    # -rw-rw-r-- 1 bozo 30 May 13 17:15 1.txt

    # -rw-rw-r-- 1 bozo 51 May 15 20:57 t2.sh

    # -rwxr-xr-x 1 bozo 217 Mar 5 21:13 wi.sh


    Подстановка команд позволяет даже записывать в переменные содержимое целых файлов, с помощью перенаправления или команды cat.

    variable1=`<file1` # Записать в переменную "variable1" содержимое файла "file1".

    variable2=`cat file2` # Записать в переменную "variable2" содержимое файла "file2".


    # Замечание 1:

    # Удаляются символы перевода строки.

    #

    # Замечание 2:

    # В переменные можно записать даже управляющие символы.


    # Выдержки из системного файла /etc/rc.d/rc.sysinit

    #+ (Red Hat Linux)


    if [ -f /fsckoptions ]; then

    fsckoptions=`cat /fsckoptions`

    ...

    fi

    #

    #

    if [ -e "/proc/ide/${disk[$device]}/media" ] ; then

    hdmedia=`cat /proc/ide/${disk[$device]}/media`

    ...

    fi

    #

    #

    if [ ! -n "`uname -r | grep -- "-"`" ]; then

    ktag="`cat /proc/version`"

    ...

    fi

    #

    #

    if [ $usb = "1" ]; then

    sleep 5

    mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`

    kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`

    ...

    fi


    Не используйте переменные для хранения содержимого текстовых файлов большого объема, без веских на то оснований. Не записывайте в переменные содержимое бинарных файлов, даже шутки ради.

    Пример 14-1. Глупая выходка

    #!/bin/bash

    # stupid-script-tricks.sh: Люди! Будьте благоразумны!

    # Из "Глупые выходки", том I.


    dangerous_variable=`cat /boot/vmlinuz` # Сжатое ядро Linux.


    echo "длина строки \$dangerous_variable = ${#dangerous_variable}"

    # длина строки $dangerous_variable = 794151

    # ('wc -c /boot/vmlinuz' даст другой результат.)


    # echo "$dangerous_variable"

    # Даже не пробуйте раскомментарить эту строку! Это приведет к зависанию сценария.


    # Автор этого документа не знает, где можно было бы использовать

    #+ запись содержимого двоичных файлов в переменные.


    exit 0

    Обратите внимание: в данной ситуации не возникает ошибки переполнения буфера. Этот пример показывает превосходство защищенности интерпретирующих языков, таких как Bash, от ошибок программиста, над компилирующими языками программирования.

    Подстановка команд, позволяет записать в переменную результаты выполнения цикла. Ключевым моментом здесь является команда echo, в теле цикла.

    Пример 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 допускает и такую запись.


    Примеры подстановки команд в сценариях:

    1. Пример 10-7

    2. Пример 10-26

    3. Пример 9-26

    4. Пример 12-2

    5. Пример 12-15

    6. Пример 12-12

    7. Пример 12-39

    8. Пример 10-13

    9. Пример 10-10

    10. Пример 12-24

    11. Пример 16-7

    12. Пример A-19

    13. Пример 27-1

    14. Пример 12-32

    15. Пример 12-33

    16. Пример 12-34


    Глава 15. Арифметические подстановки

    Арифметические подстановки -- это мощный инструмент, предназначенный для выполнения арифметических операций в сценариях. Перевод строки в числовое выражение производится с помощью обратных одиночных кавычек, двойных круглых скобок или предложения let.

    Вариации

    Арифметические подстановки в обратных одиночных кавычках (часто используются совместно с командой expr)

    z=`expr $z + 3` # Команда 'expr' вычисляет значение выражения.


    Арифметические подстановки в двойных круглых скобках, и предложение let

    В арифметических подстановках, обратные одиночные кавычки могут быть заменены на двойные круглые скобки $((...)) или очень удобной конструкцией, с применением предложения let.

    z=$(($z+3))

    # $((EXPRESSION)) -- это подстановка арифметического выражения. # Не путайте с

    #+ подстановкой команд.


    let z=z+3

    let "z += 3" # Кавычки позволяют вставляьб пробелы и специальные операторы.

    # Оператор 'let' вычисляет арифметическое выражение,

    #+ это не подстановка арифметического выражения.

    Все вышеприведенные примеры эквивалентны. Вы можете использовать любую из этих форм записи "по своему вкусу".

    Примеры арифметических подстановок в сценариях:

    1. Пример 12-6

    2. Пример 10-14

    3. Пример 25-1

    4. Пример 25-6

    5. Пример 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

    Подобную технику можно использовать для создания "блочных комментариев".

    Пример 17-10. Блочный комментарий

    #!/bin/bash

    # commentblock.sh


    : << COMMENTBLOCK

    echo "Эта строка не будет выведена."

    Эта строка комментария не начинается с символа "#".

    Это еще одна строка комментария, которая начинается не с символа "#".


    &*@!!++=

    Эта строка не вызовет ошибки,

    поскольку Bash проигнорирует ее.

    COMMENTBLOCK


    echo "Код завершения \"COMMENTBLOCK\" = $?." # 0

    # Показывает, что ошибок не возникало.


    # Такая методика создания блочных комментариев

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

    # Это экономит силы и время, т.к. не нужно втавлять символ "#" в начале каждой строки,

    #+ а затем удалять их.


    : << DEBUGXXX

    for file in *

    do

    cat "$file"

    done

    DEBUGXXX


    exit 0

    Еще одно остроумное применение встроенных документов -- встроенная справка к сценарию.

    Пример 17-11. Встроенная справка к сценарию

    #!/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)


    Некоторые утилиты не могут работать внутри встроенных документов.

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


    Примечания:



    1

    Их так же называют встроенными конструкциями языка командной оболочки shell.



    2

    Многие особенности ksh88 и даже ksh93 перекочевали в Bash.



    3

    В соответствии с соглашениями, имена файлов с shell-скриптами, такими как Bourne shell и совместимыми, имеют расширение .sh. Все стартовые скрипты, которые вы найдете в /etc/rc.d, следуют этому соглашению.



    4

    Некоторые разновидности UNIX (основанные на 4.2BSD) требуют, чтобы эта последовательность состояла из 4-х байт, за счет добавления пробела после !, #! /bin/sh.



    19

    PID текущего процесса хранится в переменной $$.



    20

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



    21

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



    22

    Если $parameter "пустой",в неинтерактивных сценариях, то это будет приводить к завершению с кодом возврата 127 ("command not found").



    23

    Эти команды являются встроенными командами языка сценариев командной оболочки (shell), в то время как while, case и т.п. -- являются зарезервированными словами.



    24

    Исключение из правил -- команда time, которая в официальной документации к Bash называется ключевым словом.



    25

    Опция -- это аргумент, который управляет поведением сценария и может быть либо включен, либо выключен. Аргумент, который объединяет в себе несколько опций (ключей), определяет поведение сценария в соответствии с отдельными опциями, объединенными в данном аргументе..



    26

    Как правило, исходные тексты подобных библиотек, на языке C, располагаются в каталоге /usr/share/doc/bash-?.??/functions.

    Обратите внимание: ключ -f команды enable может отсутствовать в некоторых системах.



    27

    Тот же эффект можно получить с помощью typeset -fu.



    28

    Скрытыми считаются файлы, имена которых начинаются с точки, например, ~/.Xdefaults. Такие файлы не выводятся простой командой ls, и не могут быть удалены командой rm -rf *. Как правило, скрытыми делаются конфигурационные файлы в домашнем каталоге пользователя.



    29

    Это верно только для GNU-версии команды tr, поведение этой команды, в коммерческих UNIX-системах, может несколько отличаться.



    30

    Команда tar czvf archive_name.tar.gz * включит в архив все скрытые файлы (имена которых начинаются с точки) из вложенных подкаталогов. Это недокументированная "особенность" GNU-версии tar.



    31

    Она реализует алгоритм симметричного блочного шифрования, в противоположность алгоритмам шифрования с "открытым ключом", из которых широко известен pgp.



    32

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

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



    33

    Фактически -- это сценарий, заимствованный из дистрибутива Debian Linux.



    34

    Очередь печати -- это группа заданий "ожидающих вывода" на принтер.



    35

    Эта тема прекрасно освещена в статье, которую написал Andy Vaught, Introduction to Named Pipes, в сентябре 1997 для Linux Journal.



    36

    EBCDIC (произносится как "ebb-sid-ic") -- это аббревиатура от Extended Binary Coded Decimal Interchange Code (Расширенный Двоично-Десятичный Код Обмена Информацией). Это формат представления данных от IBM, не нашедший широкого применения. Не совсем обычное применение опции conv=ebcdic -- это использовать dd для быстрого и легкого, но слабого, шифрования текстовых файлов.

    cat $file | dd conv=swab,ebcdic > $file_encrypted

    # Зашифрованный файл будет выглядеть как "абракадабра".

    # опция swab добавлена для внесения большей неразберихи.


    cat $file_encrypted | dd conv=swab,ascii > $file_plaintext

    # Декодирование.



    37

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



    38

    Команда userdel завершится неудачей, если удаляемый пользователь в этот момент работает с системой



    39

    Дополнительную информацию по записи компакт-дисков, вы найдете в статье Алекса Уизера (Alex Wither): Creating CDs, в октябрьском выпуске журнала Linux Journal за 1999 год.



    40

    Утилита mke2fs, с ключом -c, так же производит поиск поврежденных блоков.



    41

    Пользователи небольших, десктопных Linux-систем предпочитают утилиты попроще, например tar.



    42

    NAND -- логическая операция "И-НЕ". В общих чертах она напоминает вычитание.



    43

    Замещающая команда может быть внешней системной командой, внутренней (встроенной) командой или даже функцией в сценарии.



    44

    дескриптор файла -- это просто число, по которому система идентифицирует открытые файлы. Рассматривайте его как упрощенную версию указателя на файл.



    45

    При использрвании дескриптора с номером 5 могут возникать проблемы. Когда Bash порождает дочерний процесс, например командой exec, то дочерний процесс наследует дескриптор 5 как "открытый" (см. архив почты Чета Рамея (Chet Ramey), SUBJECT: RE: File descriptor 5 is held open) Поэтому, лучше не использовать этот дескриптор.







     


    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Другие сайты | Наверх