Advanced Bash-Scripting Guide RU
Advanced Bash-Scripting Guide RU
<[Link]@[Link]>
История проверок
Revision 6.5 05 Апреля 2012 Проверено: mc
'TUNGSTENBERRY' release
Revision 6.6 27 Ноября 2012 Проверено: mc
'YTTERBIUMBERRY' release
Revision 10 10 Марта 2014 Проверено: mc
'PUBLICDOMAIN' release
1
Посвящение
Аните, не иссякаемому источнику волшебства
Содержание
Часть 1. Введение
1. Программирование в оболочке!
2. Начнем с Sha-Bang
Часть 2. Основы
3. Специальные символы
4. Введение в переменные и параметры
5. Кавычки
6. Выход и статус выхода
7. Проверки
8. Операции и смежные темы
Часть 3. За пределами основ
9. Другой взгляд на переменные
10. Управление переменными
11. Циклы и ветвления
12. Подстановка команд
13. Арифметические расширения
14. Время перерыва
Часть 4. Команды
15. Внутренние и встроенные команды
16. Внешние фильтры, программы и команды
17. Системные команды и команды управления
Часть 5. Дополнительные темы
18. Регулярные выражения
19. Here Documents
20. Перенаправление Ввода/Вывода
21. Подоболочка
22. Ограниченные оболочки
23. Подстановка процесса
24. Функции
25. Псевдонимы
26. Конструкции list
27. Массивы
28. Косвенные ссылки
29. /dev и /proc
30. Сетевое программирование
31. Ноль и Null
32. Отладка
33. Опции
34. Gotchas
35. Стильное написание сценария
36. Разное
37. Bash, версии 2, 3 и 4
38. Заключение
38.1. Примечания автора
38.2. Об авторе
2
38.3. Куда обращаться за помощью
38.4. Инструменты, использованные при написании этой книги
38.5. Благодарности
38.6. Правовая оговорка
Библиография
A. Внесенные сценарии
B. Типы ссылок
C. Sed и Awk
C.1. Sed
C.2. Awk
D. Анализ и управление путями имен (Pathnames)
E. Коды выхода, специальные значения
F. Детальное введение в I/O и перенаправление I/O
G. Опции командной строки
G.1. Стандартные параметры командной строки
G.2. Параметры командной строки Bash
H. Важные файлы
I. Важнейшие системные директории
J. Введение в Программное Завершение
K. Локализация
L. История команд
M. Образцы файлов .bashrc и .bash_profile
N. Преобразование пакетных файлов DOS в сценарии Shell
O. Упражнения
O.1. Анализ сценариев
O.2. Написание сценариев
P. История изменений
Q. Сайты зеркал и загрузки
R. Список To Do (Что еще надо сделать)
S. Авторские права
T. Таблица ASCII
Указатель
Список таблиц
8-1. Приоритет операторов
15-1. Идентификаторы заданий
33-1. Опции Bash
36-1. Числа представляющие цвета в управляющей последовательности
B-1. Специальные переменные оболочки
B-2. Операторы ПРОВЕРКИ: Двоичное сравнение
B-3. Операторы ПРОВЕРКИ: Файлы
B-4. Параметры подстановки и расширения
B-5. Строковые операции
B-6. Различные конструкции
C-1. Основные операторы sed
C-2. Примеры операций sed
E-1. Зарезервированные Коды выхода
N-1. Ключевые слова/переменные/операторы пакетных файлов и их эквиваленты оболочки
N-2. Команды DOS и их эквиваленты в UNIX
P-1. История изменений
3
Список Примеров
2-1. cleanup: Сценарий очищающий лог-файлы в /var/log
2-2. cleanup: Улучшенный сценарий очистки
2-3. cleanup: Расширенный и обобщенный вариант сценариев выше.
3-1. Блок кода и перенаправление I/O
3-2. Сохранение вывода блока кода в файл
3-3. Запуск цикла в фоновом режиме
3-4. Резервное копирование всех файлов, измененных в прошедший день
4-1. Присваивание переменных и подстановка
4-2. Обычное присваивание переменной
4-3. Присваивание переменной, обычное и не обычное
4-4. Целые числа или строки?
4-5. Позиционные параметры
4-6. Поиск доменного имени wh, whois
4-7. Использование shift
5-1. Вывод на экран необычных переменных
5-2. Экранированные символы
5-3. Определение нажатой клавиши
6-1. Выход/статус выхода
6-2. Отрицание условия с помощью!
7-1. Что такое истина?
7-2. Эквивалентность test, /usr/bin/test, [ ], и /usr/bin/[
7-3. Арифметическая проверка с помощью (( ))
7-4. Проверка на мертвые ссылки
7-5. Числовые и строковые сравнения
7-6. Проверка, имеет ли строка значение null
7-7. zmore
8-1. Наибольший общий делитель
8-2. Использование арифметических операций
8-3. Соединение проверки условий с помощью && и ||
8-4. Представление числовых констант
8-5. Управление переменными в стиле Си
9-1. $IFS и пробелы
9-2. Ввод по времени
9-3. Еще раз, ввод по времени
9-4. Ограничение по времени read
9-5. Я root?
9-6. arglist: Листинг аргументов с $* и $@
9-7. Не последовательное поведение $* и $@
9-8. $* и $@ при пустом $IFS
9-9. Подчеркнутая переменная
9-10. Использование declare для вывода переменных
9-11. Генерация случайных чисел
9-12. Выбор случайной карты из колоды
9-13. Моделирование броуновского движения
9-14. Случайное из значений
9-15. Бросание одной кости с RANDOM
4
9-16. Смена источника RANDOM
9-17. Псевдослучайные числа, используя awk
10-1. Вставка пустой строки между абзацами в текстовом файле
10-2. Создание 8-символьной "случайной" строки
10-3. Преобразование форматов графических файлов, с изменением имени файла
10-4. Конвертирование потоковых файлов аудио в ogg
10-5. Эмуляция getopt
10-6. Альтернативные способы извлечения и поиска substring
10-7. Использование подстановки параметров и сообщений об ошибках
10-8. Подстановка параметров и сообщение "usage"
10-9. Размер переменной
10-10. Соответствие шаблону при подстановке параметров
10-11. Переименование расширений файлов
10-12. Анализ произвольных строк с помощью соответствия шаблону
10-13. Соответствие шаблонов префиксу или суффиксу строки
11-1. Простые циклы for
11-2. Цикл for с двумя параметрами в каждом элементе [list]
11-3. Fileinfo: обработка списка файлов, находящегося в переменной
11-4. Обработка параметризированного списка фала
11-5. Обработка файлов циклом for
11-6. Отсутствие in [list] в цикле for
11-7. Создание [list] в цикле for командой подстановки
11-8. Замена двоичных файлов grep
11-9. Список всех системных пользователей
11-10. Проверка всех бинарных файлов в директории для авторства
11-11. Список символических ссылок в директории
11-12. Символические ссылки директории, сохраняемые в файл
11-13. Цикл for в стиле Си
11-14. Использование efax в пакетном режиме
11-15. Простой цикл while
11-16. Другой цикл while
11-17. Цикл while с несколькими условиями
11-18. Синтаксис Си в цикле while
11-19. Цикл until
11-20. Вложенный цикл
11-21. Эффекты break и continue в цикле
11-22. Прерывание нескольких уровней цикла
11-23. Продолжение более высокого уровня цикла
11-24. Использование continue N в реальных задачах
11-25. Применение case
11-26. Создание меню с помощью case
11-27. Использование подстановки команд для создания переменной case
11-28. Простой поиск совпадающих строк
11-29. Проверка вводимых букв
11-30. Создание меню с помощью select
11-31. Создание меню с помощью select в функции
12-1. Идиотские трюки сценария
12-2. Создание переменной из цикла
12-3. Поиск анаграмм
15-1. Сценарий, который порождает несколько своих экземпляров
5
15-2. printf в действии
15-3. Присваивание переменной с помощью read
15-4. Что случится, если у read не будет переменной
15-5. Многострочный ввод в read
15-6. Обнаружение нажатия клавиш со стрелками
15-7. Применение read с перенаправлением файла
15-8. Проблемы при чтении из конвейера
15-9. Изменение текущей рабочей директории
15-10. Давайте посчитаем с let.
15-11. Демонстрация эффекта eval
15-12. Выбор между переменными с помощью eval
15-13. Вывод параметров командной строки
15-14. Принудительный выход
15-15. Версия rot13
15-16. Использование set позиционными параметрами
15-17. Реверс позиционных параметров
15-18. Переназначение позиционных параметров
15-19. "Сброс" переменной
15-20. Применение export для передачи переменной во встроенный сценарий awk
15-21. Чтение опций/аргументов передаваемых в сценарий с помощью getopts.
15-22. "Включение" файла данных
15-23. Пример (бесполезный) сценария, который сам является источником
15-24. Эффект exec
15-25. Сценарий который порождает сам себя
15-26. Ожидание завершения перед продолжением
15-27. Сценарий, который убивает сам себя
16-1. Создание оглавления для записи CDR диска с помощью ls
16-2. Hello или Good-bye
16-3. Badname, исключение имен файлов в текущей директории содержащих плохие
символы и пробелы.
16-4. Удаление файла по номеру его inode
16-5. Logfile: Использование xargs для мониторинга системного журнала
16-6. Копирование файлов текущей директории в другую
16-7. Уничтожение процесса по имени
16-8. Частотный анализ слов с помощью xargs
16-9. Использование expr
16-10. Использование date
16-11. Расчет дат
16-12. Анализ частоты встречаемости слова
16-13. Какие файлы являются сценариями?
16-14. Генерация случайных 10-значных чисел
16-15. Проверка системного журнала с помощью tail
16-16. Вывод строк From из сохраненных сообщений e-mail
16-17. Эмуляция grep в сценарии
16-18. Решатель кроссворда
16-19. Поиск определений в Словаре Webster 1913
16-20. Проверка на правильность слов в списке
16-21. toupper: Преобразование файла в верхний регистр.
16-22. lowercase: Изменение всех имен файлов в рабочей директории в нижний регистр.
16-23. du: преобразование текстового файла DOS в UNIX.
6
16-24. rot13: сверхслабое шифрование.
16-25. Создание головоломки "Crypto-Quote"
16-26. Форматированный вывод файла.
16-27. Использование column для форматирования листинга директории
16-28. nl: Самонумерующийся сценарий
16-29. manview: Просмотр форматированных справочных страниц
16-30. Перемещение дерева директории с помощью cpio
16-31. Распаковка архива rpm
16-32. Удаление комментариев из файлов программы Cи
16-33. Знакомство с /usr/X11R6/bin
16-34. "Улучшения" команды strings
16-35. Сравнение двух файлов сценария с помощью cmp.
16-36. basename и dirname
16-37. Сценарий копирующий себя частями
16-38. Проверка целостности файлов
16-39. Шифрование файлов Uudecoding
16-40. Узнаем откуда спамер
16-41. Анализ спам домена
16-42. Получение котировок акций
16-43. Обновление FC4
16-44. Использование ssh
16-45. Сценарий, самостоятельно рассылающий почту
16-46. Создание простых чисел
16-47. Ежемесячный платеж по ипотечному кредиту
16-48. Преобразование основания
16-49. Вызов bc с помощью here document
16-50. Подсчет числа Пи
16-51. Преобразование десятичного числа в шестнадцатеричное
16-52. Факторинг
16-53. Вычисление гипотенузы треугольника
16-54. Создание аргументов цикла с помощью seq
16-55. Подсчет букв
16-56. Анализ опций командной строки с помощью getopt
16-57. Сценарий, который копирует себя
16-58. Работа dd
16-59. Захват нажатий клавиш
16-60. Подготовка загрузочной SD карты для Raspberry Pi
16-61. Безопасное удаление файла
16-62. Генератор имен файлов
16-63. Преобразование метров в мили
16-64. Использование m4
17-1. Установка нового пароля
17-2. Настройка удаления символа
17-3. Безопасность пароля: отключение вывода в терминале
17-4. Определение нажатой клавиши
17-5. Проверка identd удаленного сервера
17-6. pidof помогает убить процесс
17-7. Проверка образа на CD
17-8. Создание файловой системы в файле
17-9. Добавление нового жесткого диска
7
17-10. Укрытие от посторонних глаз выходного файла с помощью umask
17-11. Backlight: Изменение яркости подсветки (ноутбук)
17-12. killall из /etc/rc.d/init.d
19-1. broadcast: Отправка сообщения всем зарегистрированным пользователям
19-2. dummyfile: Создание пустого 2-строчного файла
19-3. Многострочные сообщения с помощью cat
19-4. Многострочные сообщения с подавлением табуляций
19-5. Here document с изменяемыми параметрами
19-6. Загрузка пары файлов Sunsite находящихся в директории
19-7. Выключение подстановки параметров
19-8. Сценарий, который создает другой сценарий
19-9. Here document и функции
19-10. "Анонимный" Here Document
19-11. Закомментирование блока кода
19-12. Самодокументируемый сценарий
19-13. Добавление строки в файл
19-14. Разборка почтового ящика
20-1. Перенаправление stdin используя exec
20-2. Перенаправление stdout используя exec
20-3. Перенаправление вместе stdin и stdout в тот же сценарий с exec
20-4. Избегаем subshell
20-5. Перенаправление цикла while
20-6. Другая форма перенаправления цикла while
20-7. Перенаправление цикла until
20-8. Перенаправление цикла for
20-9. Перенаправление цикла for (перенаправление обоих stdin и stdout)
20-10. Перенаправление сравнения if/then
20-11. Данные файла [Link] для примеров выше
20-12. Регистрация событий
21-1. Область видимости переменной в подоболочке
21-2. Список профилей пользователей
21-3. Запуск параллельных процессов в subshells
22-1. Запуск сценария в ограниченном режиме
23-1. Перенаправление блока кода без порождения потомка (форка)
23-2. Перенаправление вывода подстановки процесса в цикл.
24-1. Простые функции
24-2. Функция принимающая параметры
24-3. Функции и аргументы командной строки передающиеся в сценарий
24-4. Передача косвенной ссылки функции
24-5. Разыменование параметра переданного функции
24-6. Снова, разыменование параметра переданного функции
24-7. Максимальное из двух чисел
24-8. Преобразование чисел в римские цифры
24-9. Тестирование наибольшего возвращаемого значения функции
24-10. Сравнение двух больших целых чисел
24-11. Настоящее имя из имени пользователя
24-12. Видимость локальной переменной
24-13. Демонстрация простой рекурсивной функции
24-14. Еще одна простая демонстрация
24-15. Рекурсия, использующая локальную переменную
8
24-16. Последовательность Фибоначчи
24-17. Ханойская пагода
25-1. Псевдонимы в сценарии
25-2. unalias: Установка и сброс alias
26-1. Использование and list для проверки аргументов командной строки
26-2. Еще одна проверка аргументов командной строки с помощью and list
26-3. Использование or lists в комбинации с and list
27-1. Простое использование массива
27-2. Форматирование стихотворения
27-3. Различные операции с массивами
27-4. Операции string массива
27-5. Загрузка содержимого сценария в массив
27-6. Некоторые специальные свойства массивов
27-7. Пустые массивы и пустые элементы
27-8. Инициализация массивов
27-9. Копирование и объединение массивов
27-10. Подробнее об объединении массивов
27-11. Сортировка пузырей
27-12. Встроенные массивы и косвенные ссылки
27-13. Сито Эратосфена
27-14. Сито Эратосфена, оптимизированое
27-15. Эмуляция стека push-down
27-16. Комплексное применение массива: Изучение странных математических серий
27-17. Имитация двумерного массива
28-1. Косвенные ссылки на переменные
28-2. Передача косвенных ссылок в awk
29-1. Использование /dev/tcp для устранения неполадок
29-2. Проигрывание музыки
29-3. Поиск процесса, связанного с заданным PID
29-4. Статус он-лайн подключения
30-1. Распечатка серверной среды
30-2. IP адреса
31-1. Скрытие cookie jar
31-2. Настройка файла подкачки с помощью /dev/zero
31-3. Создание ramdisk
32-1. Сценарий с ошибками
32-2. Отсутствующее ключевое слово
32-3. test24: другой сценарий с ошибками
32-4. Проверка условий при помощи assert
32-5. Перехват exit
32-6. Очистка после Control-C
32-7. Простая реализация индикатора
32-8. Трассировка переменной
32-9. Выполнение нескольких процессов (в SMP box)
34-1. Сравнение чисел и строк не эквивалентны
34-2. Ловушки Subshell
34-3. Передача вывода echo в read
36-1. Обертка оболочки
36-2. Слегка усложненная обертка оболочки
36-3. Создание обертки оболочки, которая пишет в логфайл
9
36-4. Обертка оболочки вокруг сценария awk
36-5. Обертка оболочки вокруг другого сценария awk
36-6. Perl встроенный в сценарий Bash
36-7. Комбинация сценариев Bash и Perl
36-8. Python встроенный в сценарий Bash
36-9. Говорящий сценарий
36-10. Сценарий (полезный), который вызывает себя
36-11. Сценарий (полезный), который вызывает себя
36-12. Другой сценарий (полезный) рекурсивно вызывающий сам себя
36-13. "Раскрашивание" адресной базы данных
36-14. Рисование полей
36-15. Вывод на экран цветного текста
36-16. Игра "скачки"
36-17. Индикатор выполнения
36-18. Трюк с возвращаемым значением
36-19. Еще более трюковое возвращаемое значение
36-20. Передача и возвращение массива
36-21. Забава с анаграммами
36-22. Виджеты, вызываемые из сценария оболочки
36-23. Набор тестов
37-1. Расширение строки
37-2. Косвенные ссылки на переменную - новый вариант
37-3. Приложение простой база данных, использующее косвенные ссылки на переменные.
37-4. Использование массивов и других фокусов для раздачи четырех случайных рук из
колоды карт
37-5. Простая адресная база данных
37-6. Несколько более сложная адресная база данных
37-7. Проверка символов
37-8. Чтение N символов
37-9. Присваивание переменной с помощью here document
37-10. Передача ввода на read
37-11. Отрицательные индексы массива
37-12. Отрицательный параметр в конструкции извлечения string
A-1. mailformat: Форматирование сообщений e-mail
A-2. rn: Бесхитростная утилита для переименования файла
A-3. blank-rename: Переименование имен файлов содержащих пробелы
A-4. encryptedpw: Загрузка FTP-сайте, с помощью локально зашифрованного пароля
A-5. copy-cd: Копирование CD с данными
A-6. Ряды Collatz
A-7. days-between: Количество дней между двумя датами
A-8. Создаем словарь
A-9. Преобразование Soundex
A-10. Game of Life
A-11. Файл данных для Game of Life
A-12. behead: Удаление почты и заголовков сообщений новостей
A-13. password: Создание случайного 8-символьного пароля
A-14. fifo: Производство ежедневного резервного копирования с помощью именованных
каналов
A-15. Создание простых чисел с помощью оператора модуль
A-16. tree: Вывод на экран дерева директории
10
A-17. tree2: Альтернативный сценарий дерева директории
A-18. string functions: Функции обработки строк в стиле Cи
A-19. Информация о директории
A-20. Библиотека хэш функций
A-21. Раскрашивание текста с помощью хэш-функции
A-22. Большие возможности хеш-функций
A-23. Монтирование USB оборудования хранения данных
A-24. Преобразование в HTML
A-25. Сохранение веблогов
A-26. Защита буквенных строк
A-27. Снятие защиты буквальности строки
A-28. Определение спамеров
A-29. Spammer Hunt
A-30. Делаем wget легким в использовании
A-31. Сценарий podcasting
A-32. Ночной бэкап firewire HD
A-33. Расширение команды cd
A-34. Сценарий настройки звуковой карты
A-35. Положение отдельных абзацев в текстовом файле
A-36. Вставка сортировки
A-37. Общее отклонение
A-38. Файловый генератор pad для условно-бесплатных продуктов
A-39. Редактор man page
A-40. Лепестки розы
A-41. Quacky: игра слов Perquackey
A-42. Nim
A-43. Секундомер в командной строке
A-44. Универсальные оболочки сценариев для решений домашних заданий
A-45. Рыцарский турнир
A-46. Волшебные квадраты
A-47. «Пятнашки»
A-48. Ханойские пагоды, графическая версия
A-49. Ханойские пагоды, другая графическая версия
A-50. Альтернативная версия сценария [Link]
A-51. Версия примера [Link], использующего приложение Табличные расширения
A-52. Перебор всех возможных цветов фона
A-53. Упражнение в Азбуке Морзе
A-54. Кодирование/декодирование Base64
A-55. Вставка текста в файл с помощью sed
A-56. Шифр Gronsfeld
A-57. Генератор чисел Bingo
A-58. Рассмотренные основы
A-59. Проверка времени выполнения различных команд
A-60. Ассоциативные массивы против обычных массивов (время выполнения)
C-1. Подсчет вхождений буквы
J-1. Завершающий сценарий для [Link]
M-1. Образец файла .bashrc
M-2. Файл .bash_profile
N-1. [Link]: Пакетный файл DOS
N-2. [Link]: Конвертация [Link] в Shell Script
11
T-1. Сценарий создающий таблицу ASCII
T-2. Другой сценарий таблицы ASCII
T-3. Третий сценарий таблицы ASCII, использующий awk
Часть 1. Введение
Оболочка это интерпретатор команд. Больше, чем просто изолирующий слой между ядром
операционной системы и пользователем, это довольно мощный язык программирования.
Программная оболочка, которая называется сценарий, является, простым в использовании,
инструментом для создания приложений с помощью «склеивания» системных вызовов,
инструментов, утилит и скомпилированных двоичных файлов. Практически весь набор
команд, утилит и инструментов UNIX доступен для вызова с помощью сценария оболочки.
Если этого не достаточно, то внутренние команды оболочки, такие как проверка и
конструкции циклов, придают сценариям дополнительную мощь и гибкость. Сценарии
оболочки особенно хорошо подходят для решения задач управления системой и других
рутинных, повторяющихся, задач, не требующих колокольчиков и свистков
полномасштабных, хорошо структурированных, языков программирования.
Содержание
1. Программирование в оболочке!
12
2. Начнем с Sha-Bang
2.1. Запуск сценария
2.2. Предварительные упражнения
--Herbert Mayer
Практическое знание сценариев оболочки важно для тех, кто хочет стать достаточно сильным
системным администратором, даже если они не собираются в будущем писать сценарии.
Если посмотреть, как загружается Linux машина, то мы увидим, что она выполняет сценарии
/etc/rc.d, восстанавливая конфигурацию системы и настраивая сервисы. Детальное
понимание этих стартовых сценариев важно для анализа поведения системы, и, возможно,
его изменения.
Ремесло сценариев не трудно освоить, потому что сценарии могут создаваться частями и
содержать только достаточно небольшой набор операторов и опций, [1] известных
конкретной оболочке. Синтаксис прост - даже строг - похож на вызов и объединение вместе
утилит командной строки, и существует всего несколько «правил», регулирующих их
использование. Большинство коротких сценариев правильно работают с первого раза, а
отладка даже более объемных - проста.
В первые дни персональных компьютеров, язык BASIC был установлен на
компьютеры для того, чтобы писать программы для первого поколения
микрокомпьютеров. Десятилетия спустя язык сценариев Bash позволяет любому,
знающему Linux или UNIX, делать то же самое для современных машин.
Теперь у нас есть миниатюрные одноплатные компьютеры, такие как Raspberry Pi,
с удивительными возможностями. Сценарии Bash дают возможность исследовать
возможности этих увлекательных устройств.
Сценарий является быстрым и черновым способом создания прототипов сложных
приложений. Иметь даже ограниченное подмножество функциональности сценария,
зачастую достаточно на первом этапе в развитии проекта. Таким образом, структура
приложения может быть проверена, исправлена и найдены основные подводные камни до
13
начала окончательного кодирования на C, C, Java, Perl или Python .
Написание сценариев оболочки соотносится с классической философией UNIX разделять
сложные проекты на более простые подзадачи, объединяя вместе компоненты и утилиты.
Многие считают это лучшим, или, по крайней мере, очень эстетическим, подходом к
решению проблем, в отличии от использования одного из мощных, все-в-одном, языков
нового поколения, таких как Perl, которые пытаются быть заменой для всех, но мыслительная
деятельность вашей головы должна соответствовать этому инструменту.
По Herbert Mayer, "полезный язык нуждается в массивах, указателях и общем механизме для
создания структур данных". По этим критериям, сценарии оболочки менее «полезны». Или,
может быть, нет...
14
• Нужны порты или сокеты I/O
Мы будем использовать Bash, акроним [3] для «Bourne-Again shell» и игру слов, теперь
классического Bourne shell, написанного Stephen Bourne. Bash является стандартом для
сценариев оболочки на большинстве UNIX систем. Эта книга охватывает, в равной степени
хорошо, большинство принципов сценариев других оболочек, таких как Korn Shell, C Shell и
их вариантов, из которых Bash наследует некоторые из их особенностей [4]. (Обратите
внимание, что программирование на C Shell не рекомендуется из-за присущих ему
определенных проблем, указанных Tom Christiansen в октябре 1993г. ).
Ниже приводится руководство по написанию сценариев оболочки. Оно опирается на
иллюстрации примеров различных особенностей оболочки. Все примеры сценариев рабочие
- они были проверены, насколько это возможно - а некоторые из них даже полезны в
реальной жизни. Читатель может найти исходные коды работающих примеров в архиве
([Link] или [Link]), [5] дать им права на выполнение (chmod
u+rx имя_сценария), а затем запустить их, чтобы увидеть, что произойдет. Если архив
исходных кодов не доступен, то вырежьте-и-вставьте в исполняемые версии HTML или pdf.
Имейте в виду, что некоторые из сценариев, представленных здесь, содержат функции до их
разъяснения, а это может потребовать от читателя временно пропустить этот материал до
изучения этих функций.
Если не указано иное, примеры сценариев написаны автором этой книги.
--Edmund Spenser
15
Примечания
[1] Называются встроенными, особенность внутренней оболочки.
[2] Хотя рекурсия возможна в сценарии оболочки, она, как правило, медленна и ее
осуществление, зачастую, это уродливые ляп.
[3] Акроним — эрзац слово, формируемое путем объединения вместе начальных букв слов
фразы.
--Larry Wall
Содержание
16
2.1. Вызов сценария
2.2. Предварительные упражнения
cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Лог-файлы очищены."
Ничего необычного здесь нет, только набор команд, которые могли бы так же легко
вызываться по одной, из командной строки в консоли или в окне терминала. Преимущества
размещения команд в сценарии велики, не надо вводить их каждый раз заново. Сценарий
становится программой - инструментом - и он может быть легко модифицирован или
изменен для конкретного приложения.
Пример 2-2. cleanup: Улучшенный сценарий очистки
#!/bin/bash
# Правильный заголовок для сценария Bash.
# Очистка, версия 2
LOG_DIR=/var/log
# Переменные лучше, чем жестко закодированные значения.
cd $LOG_DIR
Теперь это начинает выглядеть как настоящий сценарий. Но мы пойдем еще дальше...
17
Пример 2-3. cleanup: Расширенный и обобщенный вариант сценариев выше.
#!/bin/bash
# Cleanup, version 3
# Внимание:
# -------
# Этот сценарий использует ряд особенностей, которые будут объяснены позже
# К тому времени, как вы закончите первую половину книги,
#+ в нем не будет ничего загадочного.
LOG_DIR=/var/log
ROOT_UID=0 # Только пользователь с $UID 0 имеет права root.
LINES=50 # Число сохраняемых строк по умолчанию.
E_XCD=86 # Невозможно изменить директорию?
E_NOTROOT=87 # Выход с ошибкой не root.
if [ -n "$1" ]
# Проверка наличия аргумента командной строки (наличия).
then
lines=$1
else
lines=$LINES # По умолчанию, если не задано в командной строке.
fi
cd $LOG_DIR
18
# Гораздо более эффективным будет:
#
# cd /var/log || {
# echo "Невозможно перейти в необходимую директорию." >&2
# exit $E_XCD;
# }
cat /dev/null > wtmp # ': > wtmp' и '> wtmp' имеют различный эффект.
echo "Лог-файлы очищены."
# Обратите внимание, что другие лог-файлы, кроме /var/log, не зависят
#+ от этого сценария.
exit 0
# Возвращаемое из сценария значение ноль, после выхода, указывает на успех
оболочки.
Поскольку вы не можете очистить весь системный журнал, эта версия сценария сохраняет
последний раздел журнала сообщений нетронутыми. Далее вы откроете для себя способы
тонкой настройки ранее написанных сценариев, для повышения их эффективности.
***
Заголовок сценария sha-bang (#!) [1] говорит системе, что этот файл является набором
команд, которые будут подаваться на указанный командный интерпретатор. #! это, на самом
деле, двух байтовое [2] магическое число, специальный маркер, обозначающий тип файла,
или, в данном случае, что это исполняемый сценарий оболочки (распечатайте мануал magic
для получения более подробной информации по этой увлекательной теме). Сразу же после
sha-bang - идет имя пути. Это путь к программе, которая интерпретирует команды сценария,
будь то оболочка, язык программирования или утилита. Затем этот командный интерпретатор
выполняет команды сценария, начиная с верхней части (строка после строки sha-bang), и
игнорирует комментарии.[3]
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/bin/awk -f
19
#!/bin/sh, оболочки Bourne в большинстве коммерческих вариантов UNIX по умолчанию,
делает сценарий переносимым на не Linux машины, хотя бы пожертвовав и Bash-
специфическими чертами. Сценарий, однако, будет соответствовать sh стандарту POSIX [5].
Обратите внимание, что путь задаваемый «sha-bang» должен быть правильным, в противном
случае выводится сообщение об ошибке - обычно "команда не найдена " - будет результатом
выполнения сценария. [6]
#! может быть опущен, если сценарий состоит только из набора общих системных команд,
не использующих никаких директив внутренней оболочки. Второй пример, выше, должен
начинаться с #!, т.к. строка присваивания переменной, строка 50, использует конструкцию
конкретной оболочки. [7] Обратите внимание, что #!/bin/sh вызывает интерпретатор
оболочки по умолчанию, которая на машине Linux, по умолчанию, /bin/bash
E_WRONG_ARGS=85
параметры_сценария="-a -h -m -z"
# -a = все, -h = помощь, и т.д.
if [ $# -ne $Число_ожидаемых_аргументов ]
then
echo "Используйте: `basename $0` $параметры_сценария"
# `basename $0` это имя сценария.
exit $E_WRONG_ARGS
fi
Примечания
[1] Более часто встречается в литературе, как she-bang или sh-bang. Это вытекает из
объединения лексем sharp (#) и bang (!).
[2] Некоторые системы UNIX (основанные на BSD 4.2), якобы берут четырех байтное
магическое число, оставляя пустое место после ! - #! /bin/sh. Но Sven Mascheck
поясняет, что это, вероятно, миф.
20
[3] Строка #! в сценарии оболочки будет первой, которую увидит интерпретатор команд
(sh или bash). Так как эта строка начинается с #, то она будет правильно
интерпретироваться, как комментарий, когда интерпретатор команд запустит
исполнение сценария. Строка уже выполнила свою задачу - вызвала командный
интерпретатор.
#!/bin/bash
# Она не запускает новый сценарий.
#!/bin/rm
# Самоудаляющийся сценарий.
# Кажется ничего не происходит при запуске ... кроме того, что файл
#+ исчезает.
WHATEVER=85
[6] Чтобы избежать этого, сценарий может начинаться со строки sha-bang #!/bin/env
bash . Это может быть полезно на UNIX-машинах, где bash находится не в /bin
21
2.1. Вызов сценария
Написав сценарий, вы можете вызвать его набрав sh scriptname, [1] или, альтернативно,
bash scriptname (где scriptname - это название сценария). (Не рекомендуется
использовать sh <scriptname, так как это отключает чтение из stdin сценария.) Но
гораздо удобнее сделать сам сценарий исполняемым, при помощи chmod.
Например:
или
Примечания
[1] Предупреждение: при вызове сценария Bash sh scriptname выключается
специфичность Bash, и поэтому сценарий может не выполняться.
[2] Сценарию необходим read, также как и право исполнения на запуск, ведь оболочка
должна прочитать его (сценарий).
22
2.2. Предварительные упражнения
1. Системные администраторы часто пишут сценарии для автоматизации общих задач.
Приведите несколько случаев, когда такие сценарии полезны.
2. Напишите сценарий, который при вызове показывает время и дату, перечисляет всех
зарегистрированных пользователей и дает системный uptime. Сценарий должен
сохранять эту информацию в лог-файл.
Часть 2. Основы
23
Содержание
3. Специальные символы
4. Введение в переменные и параметры
4.1. Подстановка переменных
4.2. Присваивание переменной
4.3. Не типизированные переменные Bash
4.4. Специальные типы переменных
5. Кавычки
5.1. Переменные, заключенные в кавычки
5.2. Экранирование
6. Выход и статус выхода
7. Проверки
7.1. Конструкции проверки
7.2. Операторы проверки файлов
7.3. Другие операторы сравнения
7.4. Вложенные условия проверок if/then
7.5. Проверим ваши знания о проверках
8. Операции и смежные темы
8.1. Операторы
8.2. Числовые константы
8.3. Конструкция двойных круглых скобок
8.4. Приоритет операторов
24
echo "Комментарий будет следом." # Здесь какой-то комментарий.
# ^ Обратите внимание, перед # пробел
# Спасибо S.C.
Разделитель команд [точка с запятой]. Разрешает поместить две или более команд на
одной строке.
25
echo "File $filename exists."; cp $filename $[Link]
else # ^^
echo "File $filename not found."; touch $filename
fi; echo "File test complete."
;;
case "$variable" in
abc) echo "\$variable = abc" ;;
xyz) echo "\$variable = xyz" ;;
esac
;;&, ;&
"точка", как элемент имени файла. В именах файлов, точка вначале имени файла
является префиксом «скрытого» файла, который обычно не выводится ls.
bash$ touch .скрытый-файл
bash$ ls -l
total 10
-rw-r--r-- 1 bozo 4034 Jul 18 22:04 [Link]
-rw-r--r-- 1 bozo 4602 May 25 13:58 [Link]
-rw-r--r-- 1 bozo 877 Dec 17 2000 [Link]
bash$ ls -al
total 14
drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 [Link]
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 [Link]
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 [Link]
-rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .скрытый-файл
26
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
bash$ cp /home/bozo/current_work/junk/* .
Копирование всех файлов "junk" в $PWD.
"
'
27
done
# /bin/ipcalc
# /usr/bin/kcalc
# /usr/bin/oidcalc
# /usr/bin/oocalc
,, ,
\ может применяться для кавычек " и ', так что они (кавычки) будут
интерпретироваться в буквальном смысле.
команда null [двоеточие]. Это эквивалент оболочки "NOP" (no op, ничего не
делающая операция). Она может считаться синонимом для встроенного true
оболочки. Команда ':' сама встраивается в Bash, а статус ее выхода равен true (0).
:
echo $? # 0
28
Бесконечный цикл
while :
do
операция-1
операция-2
...
операция-n
done
# То же, что и:
# while true
# do
# ...
# done
if условие
then : # Ничего не делает и передает управление далее
else # Или иначе ...
производятся-какие-то-действия
fi
: ${username=`whoami`}
# ${username=`whoami`} Без : в начале, выдаст ошибку
# 'имя пользователя' это команда или builtin...
Заполнитель, где команда это ожидаемый here document. См. Пример 19-10.
#+ установлена.
29
В сочетании с оператором перенаправления >, усекает заданный файл до нулевой
длины, без изменения его прав. Если файл ранее не существовал, создает его.
: > [Link] # Файл "[Link]" теперь пуст.
not_empty ()
{
:
} # Содержит : (команду null), а поэтому не является пустой.
30
!
В еще одном контексте, !, из командной строки, вызывает механизм Истории Bash (см.
Приложение L). Обратите внимание, что в сценарии механизм истории отключен.
bash$ echo *
[Link] [Link] [Link] [Link]
условие?результат-если-true:результат-если-false
(( var0 = var1<98?9:21 ))
# ^ ^
# if [ "$var1" -lt 98 ]
# then
31
# var0=9
# else
# var0=21
# fi
echo $var1 # 5
echo $var2 # 23skidoo
${}
подстановка параметра
$'... '
$*, $@
позиционные параметры
$?
$$
32
()
группа команд
(a=hello; echo $a)
Инициализация массива
Array=(элемент1 элемент2 элемент3)
{xxx,yyy,zzz,...}
cp file22.{txt,backup}
# Копирование "[Link]" в "[Link]"
{a..z}
33
Расширенные расширения в фигурных скобках.
echo {a..z} # a b c d e f g h i j k l m n o p q r s t u v w x y z
# выводятся символы между a и z.
echo {0..3} # 0 1 2 3
# выводятся символы между 0 и 3.
{}
Блок кода [фигурные скобки]. Также упоминается, как встроенная группа, эта
конструкция, по сути, создает анонимную функцию (функцию без имени). Однако в
отличие от «обычной» функции, переменные, внутри блока кода, остаются видимыми
для оставшейся части сценария.
bash$ { local a;
a=123; }
bash: local: can only be used in a
function
a=123
{ a=321; }
echo "a = $a" # a = 321 (значение внутри блока кода)
# Спасибо S.C.
Блок кода, заключенный в фигурные скобки может быть перенаправлен в I/O и из него.
#!/bin/bash
# Чтение строк из /etc/fstab.
File=/etc/fstab
{
read line1
read line2
} < $File
exit 0
34
# Теперь, как вы разделите отдельные поля каждой строки?
# Подсказка: с помощью awk, или ...
# ... Hans-Joerg Diers предполагает использовать «set» встроенное в
#+ Bash.
#!/bin/bash
# [Link]
SUCCESS=0
E_NOARGS=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` rpm-file"
exit $E_NOARGS
fi
exit 0
35
запускает подоболочку. [6]
{}
ls . | xargs -i -t cp ./{} $1
# ^^ ^^
{} \;
Определение: Pathname это имя файла, включающее в себя полный путь. Например
/home/bozo/Notes/Thursday/[Link]. Иногда называется
абсолютный путь.
[ ]
проверка.
[[ ]]
проверка.
Проверка выражения между [[ ]]. Более гибкая, чем проверка в одинарных скобках
36
[ ], это ключевое слово (keyword) оболочки.
[ ]
элемент массива
[ ]
диапазон символов
$[ ... ]
Целочисленное расширение.
echo $[$a+$b] # 10
echo $[$a*$b] # 21
(( ))
Целочисленное расширение.
перенаправление.
37
команда &>filename перенаправляет оба stdout и stderr команды в
filename.
bash$ echo $?
1
Или в сценарии
(команда)>
<(команда)
В еще одном контексте символы < и > могут выступать в качестве операторов
сравнения целых чисел. См. Пример 16-9.
<<
38
Перенаправление используемое here document.
<<<
<, >
Сравнение ASCII.
veg1=carrots
veg2=tomatoes
\<, \>
echo ls -l | sh
# передает вывод "echo ls -l" оболочке,
#+ с тем же результатом, как просто "ls -l".
#+ повторяющиеся строки.
39
#!/bin/bash
# [Link] : Изменение ввода на верхний регистр.
tr 'a-z' 'A-Z'
# Диапазон букв должен быть помещен в кавычки
#+ для предотвращения создания файла из однобуквенных файлов.
exit 0
>|
||
&
запуск задания в фоновом режиме. Команда, за которой следует &, будет запущена в
40
фоновом режиме.
#!/bin/bash
# [Link]
# ======================================================
# Иногда так:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# (Первое 'echo' не выполнилось. Почему?)
exit 0
41
&&
опция, префикс. Флаг опции для команды или фильтра. Префикс оператора. Префикс
для параметра по умолчанию в подстановке параметров.
КОМАНДА -[Опция1][Опция2][...]
ls -al
param2=${param1:-$DEFAULTVAL}
# ^
--
sort --ignore-leading-blanks
Используется Bash builtin, означает окончание опций для этой конкретной команды.
42
bash$ rm -- -badname
bash$ ls -l
total 0
bash$ cat -
abc
abc
...
Ctl-D
# 1) cd /source/directory
# Переходим в директорию источника с файлами для перемещения.
# 2) &&
# "оператор И": если операция 'cd' удачна,
# то управление передается следующей команде.
# 3) tar cf - .
# Опция 'c', команды архивирования 'tar', создает новый архив,
# опция 'f' (файл), следующая '-' обозначает целевым файлом
# stdout, и происходит это в текущем дереве директории ('.').
# 4) |
# Конвейер в ...
# 5) ( ... )
# дочернюю оболочку
# 6) cd /dest/directory
# Переходим в директорию назначения.
# 7) &&
# "оператор И", как и выше
# 8) tar xpvf -
# Разархивация ('x'),с сохранением прав и владения файлов ('p'),
# и отсылает подробное сообщение в stdout ('v'),
# считывает данные из stdin (за 'f' следует '-').
#
# Обратите внимание, что 'x' это команда, а 'p', 'v', 'f' - опции.
#
# Вот так!
43
# Равнозначно, но более элегантно:
# cd source/directory
# tar cf - . | (cd ../dest/directory; tar xpvf -)
#
# Имеют такой же эффект:
# cp -a /source/directory/* /dest/directory
# Или:
# cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
# Если существуют скрытые файлы в /source/directory.
Команда file ожидает, пока "-" перенаправит вывод в stdout (иногда можно
увидеть tar cf), или примет ввод от stdin, а уж потом file. Это способ
использования файло-ориентированной утилиты - конвейера фильтров.
bash$ file
Usage: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
bash$ file -
#!/bin/bash
standard input: Bourne-Again shell script text executable
Теперь команда принимает ввод из stdin и анализирует его.
44
Сравнение файла с другим с помощью diff:
#!/bin/bash
BACKUPFILE=backup-$(date +%m-%d-%Y)
# Встраивание даты в имя файла резервной копии.
# Спасибо за идею, Joshua Tschida.
archive=${1:-$BACKUPFILE}
# Если название архива резервного копирования файла не указано в
#+ командной строке, то по умолчанию будет "[Link]."
exit 0
45
# Имеет эффект "echo -n" и ничего не выведет.
46
echo $z # 2
bash$ echo ~
/home/bozo
bash$ echo ~/
/home/bozo/
bash$ echo ~:
/home/bozo:
~+
~-
=~
^, ^^
Управляющие символы
47
Изменяют поведение терминала или вывода текста. Управляющие символы это
комбинации CONTROL + клавиша (нажатые одновременно). Символы управления
могут быть написаны в восьмеричной и шестнадцатеричной форме и предварены
управляющим символом (\).
При вводе текста в консоли или в окне Xterm, CTL-D удаляет символ под
курсором. Когда нет символов, CTL-D производит выход из сессии, как и
ожидалось. В окне XTerm, это эффект закрытия окна.
#!/bin/bash
# Встраивание Ctl-H в строку.
# Constantin Hagemeier:
# a=$'\010\010'
48
# a=$'\b\b'
# a=$'\x08\x08'
# Но это ничего не меняет.
########################################
rubout="^H^H^H^H^H" # 5 x Ctl-H.
echo -n "12345678"
sleep 2
echo -n "$rubout"
sleep 2
При вводе текста в консоле или в окне xterm, Ctl-K стирает все от символа
позиции курсора до конца строки. Внутри сценария Ctl-K может вести себя по-
разному, как в примере Lee Maschmeyer, ниже.
#!/bin/bash
# Благодарность за этот пример Lee Maschmeyer.
read -n 1 -s -p \
$'Control-M отведет курсор в начало строки. Нажмите Enter. \x0d'
# '0d' это шестнадцатеричный эквивалент Control-M.
# '-s', когда что-нибудь набрано, то она необходима для
#+ явного перехода к новой строке.
echo >&2
###
49
var=$'\x0aЭто нижняя строка\x0bЭто верхняя строка\x0a'
echo "$var"
# Это работает так же, как в приведенном выше примере. Но:
echo "$var" | col
# Правое окончание строки получается выше, чем левое окончание.
# Это также объясняет, почему мы начали и закончили
#+ переводом строки – чтобы избежать искажения на экране.
exit 0
50
echo -e '\x0a'
echo <Ctl-V><Ctl-J>
• Ctl-W При вводе текста в консоли или в окне xterm, Ctl-W стирает символ
под курсором и сзади до первого пробела. В некоторых ситуациях Ctl-W
стирает сзади первый не алфавитно-цифровой символ.
Примечания
[1] Оператор — это агент, который выполняет операцию. Примерами являются обычные
арифметические операторы + - * /. В Bash существует некоторое дублирование
51
между понятиями оператор и ключевое слово (keyword).
[2] Более известно, как троичный оператор. К сожалению, троичный уродливое слово. Оно
не скатывается с языка и он не проливает свет. Оно запутывает. Гораздо более
элегантнее - тройной.
[3] American Standard Code for Information Interchange. Это система для кодирования
текстовых символов (букв, чисел и ограниченного набора символов), как 7-битных
чисел, которые могут храниться и управляться компьютерами. Многие из символов
ASCII представлены на стандартной клавиатуре.
[4] PID или идентификатор процесса - это число назначаемое выполняющемуся процессу.
С помощью команды ps может просмотреть PID запущенных процессов.
[6] Исключение: блок кода в фигурных скобках, как часть конвейера может выполняться
как дочерняя оболочка (subshell).
# Спасибо S.C.
[7] Как и в былые времена, philtre обозначает зелье обладающее волшебной превращающей
силой, вот и UNIX фильтры преобразуют (примерно) аналогичным образом. (Кодер,
имеющий«любимый philtre», работающий на Linux машине, имеет все шансы выиграть
награды и почести).
[8] Bash сохраняет список ранее запущенных команд командной строки в буфере, или
пространстве памяти, для запоминания во встроенной истории команд.
[9] Перевод строки (новая строка) является также пробелом. Это объясняет, почему
пустая строка, состоящая только из перевода строки, является пробелом.
52
Глава 4. Введение в переменные и
параметры
Содержание
4.1. Подстановка переменных
4.2. Присваивание переменной
4.3. Не типизированные переменные Bash
4.4. Специальные типы переменных
53
компьютера, где находятся элементы данных.
Переменные появляются в арифметических операциях, и при изменении количества, и при
разборе строк.
bash$ variable1=23
#!/bin/bash
# [Link]
54
a=375
hello=$a # Нет пробелов!
# ^ ^
#-------------------------------------------------------------------------
# При инициализации переменных не допускаются пробелы по обе стороны от
#+ знака =.
# Что случиться, если будут пробелы?
# "VARIABLE =value"
# ^
#% Сценарий попытается запустить команду "VARIABLE" с одним аргументом,
#+ "=value".
# "VARIABLE= value"
# ^
#% Сценарий попытается запустить команду "value" с
#+установкой переменной окружения "VARIABLE" "".
#-------------------------------------------------------------------------
echo
hello="A B C D"
echo $hello # A B C D
echo "$hello" # A B C D
# Как видим, echo $hello и echo "$hello" выдают различные результаты.
# =======================================
# Переменная заключенная в кавычки сохраняет пробелы.
# =======================================
echo
# --------------------------------------------------------------
55
# Допустимо задание нескольких переменных в одной строке, если они
#+ разделяются пробелами.
# Осторожно, это может ухудшить читабельность и не может быть
#+ портировано.
# --------------------------------------------------------------
echo; echo
echo; echo
exit 0
56
Использование переменной до присвоения ей значения может вызвать
проблемы. Тем не менее, над не инициализированной переменной можно
выполнять арифметические операции.
echo "$uninitialized" # (пустая строка)
let "uninitialized += 5" # Увеличиваем ее на 5.
echo "$uninitialized" # 5
# Объяснение:
# Не инициализированная переменная не имеет значения, однако
#+ ее выражение в арифметической операции оценивается как 0.
Примечания
[1] Технически, имя переменной вызывается lvalue, означающее, что оно (имя) находится в
левой части оператора присваивания, как в VARIABLE=23. Значение переменной это
rvalue, означающее, что оно (значение) находится справа от оператора присваивания,
как в VAR2=$VARIABLE.
Имя переменной является, по сути, ссылкой, указателем на место в памяти, где хранятся
фактические данные, связанные с этой переменной.
#!/bin/bash
# Голые переменные
57
echo
# Присваивание
a=879
echo "Значение \"a\" это $a."
echo
echo
echo
echo
exit 0
#!/bin/bash
58
exit 0
# Из /etc/rc.d/[Link]
R=$(cat /etc/redhat-release)
arch=$(uname -m)
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # Подставляем "23" вместо "BB".
# Это делает $d целым числом.
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1
59
echo "d = $d" # d = 2335
echo
exit $?
Локальные переменные
Переменные, видимые только в пределах блока кода или функции (см. Локальные
60
переменные в функциях)
Переменные окружения
bash$ du
bash: /usr/bin/du: Список аргументов очень велик
Позиционные параметры
61
Аргументы, передаваемые в сценарий из командной строки [1] : $0, $1, $2, $3 . . .
$0 это название самого сценария, $1 это первый аргумент, $2 второй, $3 третий, и так
же четвертый и т.д. . [2] после аргумента $9, должны заключаться в фигурные скобки,
например, ${10}, ${11}, ${12}.
#!/bin/bash
echo
echo
if [ -n "$2" ]
then
echo "Параметр #2 это $2"
fi
if [ -n "$3" ]
then
echo "Параметр #3 это $3"
fi
# ...
echo "-----------------------------------"
echo "Все параметры командной строки: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "Этому сценарию нужно хотя бы $MINPARAMS аргументов командной
строки!"
fi
62
echo
exit 0
critical_argument01=$variable1_
63
# ${1:-$DefaultVal}
# См. раздел "Подстановка параметров"
#+ главе "Возвращения переменных".
#!/bin/bash
# [Link]
E_NOARGS=75
if [ -z "$1" ]
then
echo "Usage: `basename $0` [доменное_имя]"
exit $E_NOARGS
fi
exit $?
---
64
#!/bin/bash
# [Link]: Использование 'shift' для сдвига всех позиционных
#+ параметров.
exit
#+ параметров.
#!/bin/bash
# [Link]
echo "$1"
exit 0
# ======================== #
$ sh [Link] 1 2 3 4 5
4
65
# do
# echo -n "$1 "
# shift 20 # Если менее 20 позиционных параметров,
# done #+ то цикл никогда не закончится!
#
# Если вы сомневаетесь, добавьте проверку вменяемости....
# shift 20 || break
# ^^^^^^^^
Примечания
[1] Обратите внимание, что функции также принимают позиционные параметры.
tcsh% echo $0
tcsh
[3] Если сценарий это источник или символьная ссылка, то не сработает. Безопаснее
проверить $BASH_Source.
66
Глава 5. Кавычки
Содержание
5.1. Переменные, заключенные в кавычки
5.2. Экранирование
bash$ ls -l '[Vv]*'
ls: [Vv]*: Нет такого файла или директории
67
строку в кавычки, мы отделяем ее и защищаем ее буквальное значение.
Обратите внимание, что не заключенная в кавычки grep [Ff]irst *.txt , работает под
оболочкой Bash. [1]
Заключение в кавычки может подавлять "аппетит" (развернутый вывод) новых строк echo.
bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bo bo 13 Aug 21 12:57 [Link] -rw-rw-r-- 1 bo bo 78 Aug 21
12:57 [Link]
Примечания
[1] Если нет файла с именем first в текущей рабочей директории. Есть еще и другая
причина, чтобы заключить в кавычки. (Спасибо за разъяснение Harald Koenig.)
68
заключенный в двойные кавычки позиционирует себя как одно слово, даже если он содержит
разделительные пробелы.
echo "---"
variable2="" # Пустая.
echo
69
var="'(]\\{}\$\""
echo $var # '(]\{}$"
echo "$var" # '(]\{}$" Без разницы.
echo
IFS='\'
echo $var # '(] {}$" \ преобразуется в пробел. Почему?
echo "$var" # '(]\{}$"
echo
var2="\\\\\""
echo $var2 # "
echo "$var2" # \\"
echo
# Но ... var2="\\\\"" является правильным. Почему?
var3='\\\\'
echo "$var3" # \\\\
# Хотя жесткие кавычки работают.
# ************************************************************ #
# В первом примере выше показано разрешаемое в кавычках.
var1="Two bits"
echo "\$var1 = "$var1"" # $var1 = Two bits
# ^ ^
70
echo "Why can't I write 's between single quotes"
echo
# Окольный метод.
echo 'Why can'\''t I write '"'"'s between single quotes'
# |-------| |----------| |-----------------------|
# Три одиночных строки в кавычках, с экранированием и кавычками
#+ между одинарными кавычками.
Примечания
[1] Инкапсуляция '!' внутри двойных кавычек при использовании в командной строке
выдаст ошибку. Он будет интерпретироваться как команда истории. Хотя внутри
сценария эта проблема не возникает, поскольку механизм истории Bash там отключен.
bash$ echo \
>
bash$ echo "\"
>
bash$ echo \a
a
bash$ echo "\a"
\a
Двойные кавычки, после echo, иногда экранируют \. Кроме того, опция -e в echo,
при повторении вызывает «\t», интерпретируемое как табуляция
71
[2] «Разделение слова» в данном контексте означает разделение символьной строки на
отдельные и дискретные аргументы.
5.2. Экранирование
Экранирование — это способ заключения в кавычки одиночных символов. Предшествующий
символ экранирования (\) сообщает оболочке, что интерпретировать данный символ нужно
буквально.
\r значение возвращения
\t значение табуляции
\b начение забоя
#!/bin/bash
72
# [Link]: экранированные символы
#############################################################
### Сначала покажем некоторые основы использования ###
### экранированных символов. ###
#############################################################
Экранирование
# ------------------
echo ""
echo; echo
echo "============="
# =================================================================== #
# Конструкция расширения строки $'\nnn' была введена во 2 версии Bash.
# =================================================================== #
73
# Спасибо Greg Keraunen, за разъяснение.
# Ранние версии Bash допускали '\x022'.
echo
echo
echo
echo
echo
exit 0
#!/bin/bash
# Автор: Sigurd Solaas, 20 Apr 2011
# Используется в ABS Guide с разрешения.
# Используйте версию 4.2+ Bash.
key="еще не назначено"
while true; do
clear
echo "Bash Extra Keys Demo. Клавиши для определения:"
echo
echo "* Insert, Delete, Home, End, Page_Up and Page_Down"
echo "* Четыре клавиши со стрелками"
echo "* Клавиши Tab, enter, escape, и space"
echo "* Клавиши букв и цифр, и т.д."
echo
echo " d = показать дату/время"
echo " q = выход"
echo "================================"
echo
74
Преобразование отдельной клавиши home в home-key_num_7
if [ "$key" = $'\x1b\x4f\x48' ]; then
key=$'\x1b\x5b\x31\x7e'
# Конструкция расширения строки в кавычках.
fi
case "$key" in
$'\x1b\x5b\x32\x7e') # Клавиши Insert
echo Клавиша Insert
;;
$'\x1b\x5b\x33\x7e') # Delete
echo Клавиша Key
;;
$'\x1b\x5b\x31\x7e') # Home_key_num_7
echo Клавиша Home
;;
$'\x1b\x5b\x34\x7e') # End_key_num_1
echo Клавиша End
;;
$'\x1b\x5b\x35\x7e') # Page_Up
echo Клавиша Page_Up
;;
$'\x1b\x5b\x36\x7e') # Page_Down
echo Клавиша Page_Down
;;
$'\x1b\x5b\x41') # Up_arrow
echo Up arrow
;;
$'\x1b\x5b\x42') # Down_arrow
echo Клавиша Down arrow
;;
$'\x1b\x5b\x43') # Right_arrow
echo Клавиша Right arrow
;;
$'\x1b\x5b\x44') # Left_arrow
echo Клавиша Left arrow
;;
$'\x09') # Tab
echo Tab Key
;;
$'\x0a') # Enter
echo Клавиша Enter
;;
$'\x1b') # Escape
echo Клавиша Escape
;;
$'\x20') # Space
echo Клавиша Space
;;
d)
date
;;
q)
echo Время вышло...
echo
75
exit 0
;;
*)
echo Вы нажали: \'"$key"\'
;;
esac
echo
echo "================================"
unset K1 K2 K3
read -s -N1 -p "Нажмите клавишу: "
K1="$REPLY"
read -s -N2 -t 0.001
K2="$REPLY"
read -s -N1 -t 0.001
K3="$REPLY"
key="$K1$K2$K3"
done
exit $?
# Однако ...
76
Поведение \ зависит от экранирования, сильных кавычек, слабых кавычек, или
появления в подстановке команд или в here document.
# Простое экранирование и кавычки
echo \z # z
echo \\z # \z
echo '\z' # \z
echo '\\z' # \\z
echo "\z" # \z
echo "\\z" # \z
# Подстановка команд
echo `echo \z` # z
echo `echo \\z` # z
echo `echo \\\z` # \z
echo `echo \\\\z` # \z
echo `echo \\\\\\z` # \z
echo `echo \\\\\\\z` # \\z
echo `echo "\z"` # \z
echo `echo "\\z"` # \z
# Here document
cat <<EOF
\z
EOF # \z
cat <<EOF
\\z
EOF # \z
variable=\
23skidoo
echo "$variable" # 23skidoo
# Это работает, поскольку вторая строка
#+ является допустимой для присваивания
#+ переменным.
variable=\
# \^ Экранирование следующего за ним пробела
echo "$variable" # пробел
variable=\\
77
echo "$variable" # \
variable=\\\
echo "$variable"
# Не будет работать, выдаст сообщение об ошибке:
# [Link]: \: команда не найдена
#
# Первый escape экранирует второй, а третий "пустоту",
#+ одинаковый результат как в первом примере, выше.
variable=\\\\
echo "$variable" # \\
# Второй и четвертый escape заэкранированы.
# Это нормально.
echo
"-------------------------------------------------------------------------"
#+ аргумента (слова).
# Как альтернатива:
tar cf - -C /source/directory . |
tar xpvf - -C /dest/directory
# Смотрите примечание ниже.
# (Спасибо Stéphane Chazelas.)
78
продолжается на следующей строке.
echo "foo
bar"
#foo
#bar
echo
echo 'foo
bar' # Нет никакого различия.
#foo
#bar
echo
echo foo\
bar # Символ новой строки заэкранирован.
#foobar
echo
echo "foo\
bar" # Здесь \ по прежнему интерпретируется как экранирование в слабых
#+ кавычках.
#foobar
echo
echo 'foo\
bar' # Символ \ воспринимается буквально из-за строгих кавычек.
#foo\
#bar
79
Глава 6. Выход и статус выхода
... это «темный угол» Bourne shell, а все люди
пользуются им
--Chet Ramey
Команда exit, как и в программе Cи, завершает сценарий. Она возвращает значение
предоставляемое родительским процессом сценария.
Каждая команда возвращает статус выхода (иногда упоминаемый, как возвращаемый
статус или код выхода). Успешно завершившаяся команда возвращает 0, в то время как при
неудаче возвращается ненулевое значение, которое обычно интерпретируется, как код
ошибки. Правильно завершаемые команды, программы и утилиты UNIX, после успешного
завершения, возвращают код выхода 0, хотя есть некоторые исключения.
Точно так же функционирует сценарий, и сам сценарий возвращает статус выхода. Статус
выхода определяет последняя команда, выполняемая в функции или в сценарии. Внутри
сценария, команда exit nnn может быть использована для доставки статуса выхода nnn в
оболочку (nnn должно быть целым числом в диапазоне от 0 - 255 ).
#!/bin/bash
КОМАНДА_1
...
ПОСЛЕДНЯЯ_КОМАНДА
exit
#!/bin/bash
КОМАНДА_1
80
...
ПОСЛЕДНЯЯ_КОМАНДА
exit $?
#!/bin/bash
КОМАНДА_1
...
ПОСЛЕДНЯЯ_КОМАНДА
После завершения сценария, $?, в командной строке, возвращает статус выхода сценария,
которым, по соглашению, является 0 при успешном завершении, или целым числом, в
диапазоне 1-255, в случае ошибки, то есть статус выхода, последней, выполненной в
сценарии, команды,.
Пример 6-1. Выход/статус выхода
#!/bin/bash
echo hello
echo $? # Статус выхода 0, возвращаемый в случае успешного выполнения
#+ команды.
echo
$? особенно полезна для проверки результата выполнения команды в сценарии (см. Пример
81
16-35 и Пример 16-20).
! true
echo "статус выхода \"! true\" = $?" # 1
# Обратите внимание, что между "!" и командой должен быть пробел.
# !true приведет к сообщению об ошибке «команда не найдена»
#
# Префиксный оператор команды '!' вызывает механизм истории Bash.
true
!true
# В этот раз нет ошибки, но нет и отрицания чего-либо.
# Только повторение предыдущей команды (true).
# =========================================================== #
# Предшествующий ! _конвейеру_ инвертирует возвращаемый статус
выхода.
ls | bogus_command # bash: bogus_command: команда не найдена
echo $? # 127
Примечания
[1] В тех случаях, когда нет кода возврата прерывания функции.
82
Глава 7. Проверки
Содержание
7.1. Конструкции проверки
7.2. Операторы проверки файлов
7.3. Другие операторы сравнения
7.4. Вложенные условия проверок if/then
7.5. Проверим ваши знания о проверках
83
Таким образом, конструкции арифметических расширений могут использоваться
для выполнения арифметических сравнений.
(( 0 && 1 )) # Логическое И
echo $? # 1 ***
# И так ...
let "num = (( 0 && 1 ))"
echo $num # 0
# Но ...
let "num = (( 0 && 1 ))"
echo $? # 1 ***
84
word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# Опция grep "-q" подавляет вывод.
then
echo "$letter_sequence находится в $word"
else
echo "$letter_sequence не находится в $word"
fi
if КОММАНДА_ЧЬИМ_СТАТУСОМ_ВЫХОДА_ЯВЛЯЕТСЯ_0_ЕСЛИ_НЕ_ПРОИЗОШЛА_ОШИБКА
then echo "Команда правильна."
else echo "Команда не правильна."
fi
# Совет:
# Если вы не уверены, что можно оценить определенное условие, проверяйте
#+ его проверкой if.
echo
echo
echo
echo
85
echo "Проверка \"NULL\""
if [ ] # NULL (пустое условие)
then
echo "NULL это true."
else
echo "NULL это false."
fi # NULL это false.
echo
echo
echo
echo
echo
86
echo "\"false\" это true." #+ и эта проверка true.
else
echo "\"false\" это false."
fi # "false" это true.
echo
echo
exit 0
if [ условие-правдиво ]
then
команда1
команда2
...
else # Или else ...
# Добавляется выполнение блока кода по умолчанию, если проверка
#+ условие-правдиво является ложным.
команда3
команда4
...
fi
if [ -x "$filename" ]; then
Else if и elif
elif
elif это сокращение для else if. Эффект заключается во встраивании конструкции
внутрь внешней конструкции if/then.
87
if [ условие1 ]
then
команда1
команда2
команда3
elif [ условие2 ]
# То же, что и else if
then
команда4
команда5
else
команда_по_умолчанию
fi
echo
if test -z "$1"
then
echo "Нет аргументов командной строки."
88
else
echo "Первый аргумент командной строки это $1."
fi
echo
echo
echo
echo
exit 0
***
if [[ -e $file ]]
then
echo "Файл пароля существует."
fi
89
Использование конструкции проверки [[...]], в отличии от [...], предотвращает
множество логических ошибок в сценариях. Например, операторы &&, ||, < и > работают в
[[ ]], но выдают ошибку внутри конструкции [ ] .
decimal=15
octal=017 # = 15 (десятичное)
hex=0x0f # = 15 (десятичное)
90
самостоятельно, без if, когда используется в комбинации с конструкцией list
(список).
var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 не равно $var2"
home=/home/bozo
[ -d "$home" ] || echo "Директория $home отсутствует."
(( 0 ))
echo "Статус выхода \"(( 0 ))\" это $?." # 1
(( 1 ))
echo "Статус выхода \"(( 1 ))\" это $?." # 0
(( 5 > 4 )) # истинно
echo "Статус выхода \"(( 5 > 4 ))\" это $?." # 0
(( 5 > 9 )) # ложно
echo "Статус выхода \"(( 5 > 9 ))\" это $?." # 1
(( 5 == 5 )) # истинно
echo "Статус выхода \"(( 5 == 5 ))\" это $?." # 0
# (( 5 = 5 )) выдаст сообщение об ошибке.
(( 5 - 5 )) # 0
echo "Статус выхода \"(( 5 - 5 ))\" это $?." # 1
(( 5 / 4 )) # Деление, отлично
echo "Статус выхода \"(( 5 / 4 ))\" это $?." # 0
91
# ======================================= #
var1=5
var2=4
exit 0
Примечания
[1] Токен — это символ или короткая строка с прилагаемым к нему специальным значением
(мета смыслом). В Bash, определенные токены, такие как [ и . (команда точка ), могут
расширять ключевые слова и команды.
-a файл существует
92
fi
function show_input_type()
{
[ -p /dev/fd/0 ] && echo PIPE || echo STDIN
}
93
-u файл с установленным флагом (suid)
Флаг сохранения текстового режима, известный как sticky бит, представляет собой
особый тип прав файла. Если файл имеет этот установленный флаг, то файл, для
быстрого доступа к нему, будет храниться в кэш-памяти. [3] Если установлен для
директории, то он ограничивает право записи. Установка sticky бита добавляет t в
листинге прав файла или директории. Он не дает владельцам файлов изменять или
удалять отдельные файлы в данной директории.
-O вы собственник файла
! "нет" -- меняет смысл указанных выше проверок (возвращает true, если условие
отсутствует).
94
#!/bin/bash
# [Link]
# Написан Lee bigelow <ligelowbee@[Link]>
# Используется в ABS Guide с разрешения.
linkchk () {
for element in $1/*; do
[ -h "$element" -a ! -e "$element" ] && echo \"$element\"
[ -d "$element" ] && linkchk $element
# Ну да, '-h' проверка символических ссылок, '-d' директории.
done
}
exit $?
Пример 31-1, Пример 11-8, Пример 11-3, Пример 31-3 и пример A-1 также показывают
95
примеры операторов проверки файла.
Примечания
[1] Издание 1913 года Словаря Webster:
Deprecate
...
[2] Имейте в виду, что бинарные файлы с suid могут открыть дыры в безопасности. Флаг
suid не оказывает влияния на сценарий оболочки.
[3] На системах Linux sticky bit для файлов больше не используется, но только для
директорий
-ne не равно
96
if [ "$a" -lt "$b" ]
сравнения СТРОК
= равно
if [ "$a" = "$b" ]
== равно
if [ "$a" == "$b" ]
Это синоним =.
97
# Спасибо Stéphane Chazelas
!= не равно
if [ "$a" != "$b" ]
if [ -z "$String" ]
then
echo "\$String является null."
else
echo "\$String НЕ является null."
fi # $String является null.
-n строка не null.
98
Пример 7-5. Числовые и строковые сравнения
#!/bin/bash
a=4
b=5
# Здесь «а» и «b» могут рассматриваться либо как целые числа, либо как строки.
# Есть некоторые размытия между числовыми и строковыми сравнениями,
#+ поскольку переменные Bash не являются строго типизированными.
echo
echo
if [ "$a" != "$b" ]
then
echo "$a не равно $b."
echo "(сравнение строк)"
# "4" != "5"
# ASCII 52 != ASCII 53
fi
echo
exit 0
# С помощью if [ ... ]
99
#+ она не инициализирована.
echo
# Попробуем снова.
echo
echo
string1=initialized
string1="a = b"
100
# zmore
E_NOARGS=85
E_NOTFOUND=86
E_NOTGZIP=87
filename=$1
if [ ${filename##*.} != "gz" ]
# Используются скобки для подстановки переменной.
then
echo "Файл $1 не является сжатым!"
exit $E_NOTGZIP
fi
zcat $1 | more
Составные сравнения
-a логическое И
-o логическое ИЛИ
exp1 -o exp2 возвращает истину, если одно exp1 или exp2 истинно
Они похожи на операторы сравнения Bash && и ||, используемые в двойных скобках.
[[ условие1 && условие2 ]]
101
Операторы -o и -a применяются с командой test или в одинарных скобках проверки.
if [ "$expr1" -a "$expr2" ]
then
echo "Оба expr1 и expr2 истинны."
else
echo "Одно, expr1 или expr2, ложно."
fi
# Однако ...
[ 1 -eq 2 -a -n "`echo true 1>&2`" ] # истинно
# ^^^^^^^ Ложное условие. Так почему вывод "истинно"?
#+ нет.
Обратитесь к Примеру 8-3, Примеру 27-17, и Примеру А-29, для того, что бы увидеть
составные операторы сравнения в действии.
Примечания
[1] Как указывает S.C., в составной проверке не может быть достаточным даже заключение
строки переменной в кавычки. [ -n "$string" -o "$a" = "$b" ] может
привести к ошибке в некоторых версиях Bash, если $string пустая. Безопасный
способ состоит в добавлении дополнительного символа, в, возможно, пустую
переменную, [ "x$string" != x -o "x$a" = "x$b" ] ("x" отменяют).
if [ "$a" -gt 0 ]
then
if [ "$a" -lt 5 ]
then
102
echo "Значение \"a\" находится где-то между 0 и 5."
fi
fi
103
Глава 8. Операции и смежные темы
Содержание
8.1. Операторы
8.2. Числовые константы
8.3. Конструкция двойных круглых скобок
8.4. Приоритет операторов
8.1. Операторы
Присваивание
Присваиваивают значения переменной Инициализация или изменение значения переменной
var=27
category=minerals # Без пробелов спереди и сзади "=".
if [ "$строка1" = "$строка2" ]
then
команда
fi
числовые операторы
+ плюс
- минус
* умножение
/ деление
** возведение в степень
104
let "z=5**3" # 5 * 5 * 5
echo "z = $z" # z = 125
bash$ expr 5 % 3
2
5/3 = 1, с остатком 2
Среди прочего, этот оператор находит использование для создания чисел в пределах
определенного диапазона (см. Пример 9-11 и Пример 9-15) и форматирования вывода
программы (см. Пример 27-16 и Пример A-6). Он даже может использоваться для
создания простых чисел, (см. Пример A-15). Модуль удивительно часто появляется в
числовых выражениях.
#!/bin/bash
# [Link]: Наибольший общий делитель
# Используется алгоритм Евклида
# ------------------------------------------------------
# Проверка аргументов
ARGS=2
E_BADARGS=85
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` первое-число второе-число"
exit $E_BADARGS
fi
# ------------------------------------------------------
105
#+ переменная, то это приведет к сообщению
#+ об ошибке.
gcd $1 $2
# Упражнения:
# ---------
# 1) Проверьте аргументы командной строки, чтобы убедиться, что они
#+ являются целыми числами и завершите сценарий с соответствующим
#+ сообщением об ошибке, если не числа не целые.
# 2) Перепишите функцию gcd() используя локальные переменные.
exit 0
#!/bin/bash
# Считаем до 11 10-ю различными способами.
106
: $((n = $n + 1))
# ":" необходимо, потому что в противном случае Bash пытается
#+ интерпретировать "$((n = $n + 1))" как команду.
echo -n "$n "
(( n = n + 1 ))
# Более простая альтернатива способу выше.
# Спасибо за это указание David Lombard.
echo -n "$n "
n=$(($n + 1))
echo -n "$n "
: $[ n = $n + 1 ]
# ":" необходимо, потому что в противном случае Bash пытается
#+ интерпретировать "$[ n = $n + 1 ]" как команду.
# Работает, даже если "n" была объявлена как строка.
echo -n "$n "
n=$[ $n + 1 ]
# Работает, даже если "n" была объявлена как строка.
#* Избегайте конструкции этого типа, т.к. она устарела и не переносима.
# Спасибо, Stephane Chazelas.
echo -n "$n "
echo
exit 0
a=2147483646
echo "a = $a" # a = 2147483646
let "a+=1" # Увеличивается "a".
echo "a = $a" # a = 2147483647
let "a+=1" # Снова увеличивается "a", после предела.
echo "a = $a" # a = -2147483648
# ОШИБКА: вне диапазона,
#+ и устанавливается самый левый бит, бит
107
#+ знака, делая результат
#+ отрицательным.
a=1.5
<<= сдвиг-влево-равно
let "var <<= 2" результат var смещается влево на 2 бита (умножается на 4)
& битовое И
| битовое ИЛИ
|= битовое ИЛИ-равно
~ битовое НЕ
108
^= битовое ИЛИ-равно
if [ ! -f $FILENAME ]
then
...
&& И
|| ИЛИ
if [ $условие1 ] || [ $условие2 ]
# То же: if [ $условие1 -o $условие2 ]
# Возвращает истинно, если одно из условие1 или условие2 истинно...
#!/bin/bash
109
a=24
b=47
a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
echo "Проверка #5 успешна."
else
echo "Проверка #5 провалена."
fi
exit 0
110
Операторы && и || также находят применение в арифметическом контексте.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0
прочие операторы
, Оператор запятая
Оператор запятая связывает вместе две или более арифметические операции. Все
операции обрабатываются (возможно с побочными эффектами). [2]
Оператор запятая находит применение, главным образом, в циклах. См. Пример 11-13.
Примечания
[1] В другом контексте, += может служить в качестве оператора объединения строк. Это
может быть полезно при изменении переменных среды.
111
Пример 8-4. Представление числовых констант
#!/bin/bash
# [Link]: Представление чисел по различным основаниям.
# Десятичное: по умолчанию
let "dec = 32"
echo "десятичное число = $dec" # 32
# Здесь нет ничего не обычного.
echo
112
8.3. Конструкция двойных круглых скобок
Подобно команде let, конструкция ((...)) допускает арифметические расширения и
оценку. В простой форме, a=$(( 5 + 3 )), a присваивается 5 + 3, или 8. Хотя,
конструкция двойных скобок, это так же механизм Cи-стиля, позволяющий управлять
переменными в Bash, например (( var++ )).
echo
echo
########################################################
# Обратите внимание, что, как и в Си, пре- и пост-декрементные операторы
#+ имеют различные эффекты.
echo
echo
# -----------------
# Предупреждение Пасхальное яйцо!
# -----------------
113
# Chet Ramey, кажется, разобрался в куче недокументированных конструкций
#+ Cи-стиля в Bash (на самом деле, взятых в большом количестве из ksh).
# В документации Bash, Ramey называет ((...)) арифметической оболочкой,
#+ а это далеко выходит за рамки обсуждаемого.
# Извините, Chet, за раскрытие Вашего секрета.
exit
114
Оператор Значение Пояснение
& И битовое
^ XOR исключающее ИЛИ, битовое
| ИЛИ битовое
Теперь давайте попытаемся использовать наши знания приоритета операторов для анализа
пары строк из файла /etc/init.d/functions, находящегося в дистрибутиве Fedora
Core Linux.
while [ -n "$remaining" -a "$retry" -gt 0 ]; do
# Разобьем условия:
while [ -n "$remaining" -a "$retry" -gt 0 ]; do
# --условие 1---- ^^ -условие 2----
#################################################################
115
if [ -f /etc/sysconfig/i18n -a -z "${NOLOCALE:-}" ] ; then
Примечания
[1] Приоритет (Precedence), в этом контексте, имеет примерно тот же смысл, что и
приоритет (priority).
116
Часть 3. За пределами основ
Содержание
9. Другой взгляд на переменные
9.1. Внутренние переменные
9.2. Ввод переменных: declare или typeset
9.3. $RANDOM: генерация случайных целых чисел
10. Управление переменными
10.1. Управление строками
10.2. Подстановка параметров
11. Циклы и ветвления
11.1. Циклы
11.2. Вложенные циклы
11.3. Управление циклом
11.4. Проверки и ветвления
12. Подстановка команд
13. Арифметические расширения
14. Время перерыва
117
9.1. Внутренние переменные
Встроенные (Builtin) переменные:
bash4$ echo $$
11015
Но ...
#!/bin/bash4
echo
118
$BASH_VERSINFO[n] 6-элементный массив, содержащий сведения об установленной
версии Bash. Это похоже на $BASH_VERSION, ниже, но более
подробно.
for n in 0 1 2 3 4 5
do
echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done
$CDPATH Разделенный двоеточиями список путей поиска, доступных для команды cd,
аналогично действию переменной $PATH для бинарных файлов. Переменная
$CDPATH может находиться в локальном файле ~/.bashrc.
bash$ cd bash-doc
bash: cd: bash-doc: Нет такого файла или директории
bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc
119
$DIRSTACK Верхнее значение стека директории [1] (управляемого pushd и popd).
xyz23 ()
{
echo "Сейчас выполняется $FUNCNAME." # сейчас выполняется xyz23.
}
xyz23
120
root# echo ${GROUPS[5]}
6
$HOSTNAME Команда hostname присваивает имя хосту системы при загрузке сценария
init. Однако функция gethostname() устанавливает $HOSTNAME
внутренней переменной Bash. См. Пример 10-7.
Эта переменная позволяет Bash распознавать поля или границы слов, когда он
интерпретирует символы строками.
121
IFS="$(printf '\n\t')" # Из David Wheeler.
#!/bin/bash
# [Link]
var1="a+b+c"
var2="d-e-f"
var3="g,h,i"
IFS=+
# Знак '+' будет интерпретироваться как разделитель.
echo $var1 # a b c
# ^ ^
echo $var2 # d-e-f
echo $var3 # g,h,i
echo
IFS="-"
# Возвращаем знак «+» к интерпретации по умолчанию.
# Теперь как разделитель будет интерпретироваться знак '-'.
echo $var1 # a+b+c
echo $var2 # d e f
# ^ ^
echo $var3 # g,h,i
echo
IFS=","
# Запятая ',' будет интерпретироваться как разделитель.
# Знак «-» возвращается к интерпретации по умолчанию.
echo $var1 # a+b+c
echo $var2 # d-e-f
echo $var3 # g h i
# ^ ^
echo
IFS=" "
# Символ пустого места будет интерпретироваться как разделитель.
# Запятая «,» возвращается к интерпретации по умолчанию.
echo $var1 # a+b+c
echo $var2 # d-e-f
echo $var3 # g,h,i
# ======================================================== #
# Однако ...
# $IFS обрабатывает пробелы иначе, чем другие знаки.
вывод_каждого_аргумента_в_отдельной_строке()
{
122
for arg
do
echo "[$arg]"
done # ^ ^ Находится внутри скобок, для лучшего восприятия.
}
IFS=" "
var=" a b c "
# ^ ^^ ^^^
вывод_каждого_аргумента_в_отдельной_строке $var
# вывод_каждого_аргумента_в_отдельной_строке `echo " a b c "`
# [a]
# [b]
# [c]
IFS=:
var=":a::b:c:::" # Шаблон, как выше,
# ^ ^^ ^^^ #+ но с заменой" " на ":" ...
вывод_каждого_аргумента_в_отдельной_строке $var
# []
# [a]
# []
# [b]
# [c]
# []
# []
echo
exit
См. поучительные примеры использования $IFS: Пример 16-41, Пример 11-8 и Пример
19-14.
123
Начиная с версии 2.05, Bash, в подстановке шаблонов, больше не
делается различий между строчными и прописными буквами в диапазоне
символов указанных в скобках. Например, ls [A-M]* будет
соответствовать обоим [Link] и [Link]. Чтобы вернуться к
общепринятому поведению скобок соответствия, установите
LC_COLLATE в C командой export LC_COLLATE=C в
/etc/profile и/или ~/.bashrc.
124
директорий списка путей исполнения. Путь хранится в переменной среды, $PATH,
списке директорий, разделенных двоеточиями. Как правило система хранит
определенные $PATH в /etc/profile или ~/.bashrc (см. Приложение H).
tcsh% bash
125
Строки выше, содержащиеся в сценарии произведут ожидаемый вывод 0
1 0.
bash$ $ ls | bogus_command | wc
bash: bogus_command: команда не найдена
0 0 0
bash$ $ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0
126
приглашения, на экран выводится $PS1.
$PS2 Второй элемент приглашения, выглядит, как приглашение к вводу. На экране ">".
$PS3 Третий элемент приглашения, выводимый в цикле select (см. Пример 11-30).
$PS4 Четвертый запрос, появляющийся в начале каждой строки вывода, при вызове
сценария с опцией -x [подробная трассировка]. Он отображается как "+".
#!/bin/bash
E_WRONG_DIRECTORY=85
TargetDirectory=/home/bozo/projects/GreatAmericanNovel
cd $TargetDirectory
echo "Удаление неиспользуемых файлов в $TargetDirectory."
if [ "$PWD" != "$TargetDirectory" ]
then # Берегитесь случайного удаления в не верно указанной директории.
echo "Не верная директория!"
echo "$PWD, а не $TargetDirectory!"
echo "Выручило!"
exit $E_WRONG_DIRECTORY
fi
rm -rf *
rm .[A-Za-z0-9]* # Удаление файлов, начинающихся с точки (скрытых).
# rm -f .[^.]* ..?* удаляет файлы, начинающиеся с нескольких точек.
# (shopt -s dotglob; rm -f *) так же работает.
# Спасибо, S.C. за разъяснение.
127
echo
ls -al # Еще файлы остались?
echo "Сделано."
echo "Старые файлы в $TargetDirectory удалены."
echo
exit $result
#!/bin/bash
# [Link]
echo
echo -n "Какие ваши любимые овощи? "
read
echo
echo -n "Какие ваши любимые фрукты? "
read fruit
echo "Ваш любимый фрукт это $fruit."
echo "но..."
echo "Значение \$REPLY это все еще $REPLY."
# $REPLY по-прежнему установлена в прежнем значении, поскольку
#+ переменная $fruit была передана как новое значение "read".
echo
exit 0
#!/bin/bash
TIME_LIMIT=10
INTERVAL=1
echo
echo "Нажмите Control-C для выхода через $TIME_LIMIT секунд."
echo
128
do # $SECONDS это внутренняя переменная оболочки.
if [ "$SECONDS" -eq 1 ]
then
units=second
else
units=seconds
fi
exit 0
$SHLVL Уровень глубины вложения оболочки Bash. [3] Если в командной строке,
$SHLVL это 1, то в сценарии он вырастает до 2.
if [ -z "$song" ]
129
then
song="(нет ответа)"
# Ответ по умолчанию.
fi
#!/bin/bash
# [Link]
TIMER_INTERRUPT=14
TIMELIMIT=3 # В данном случае три секунды.
# Можно установить другое значение.
PrintAnswer()
{
if [ "$answer" = TIMEOUT ]
then
echo $answer
else # В данном случае не хочется смешивать.
echo "Ваше любимое вегетарианское это $answer"
kill $! # Убивает больше не нужную функцию TimerOn,
#+ запущенную в фоновом режиме.
# $! это PID последнего запущенного задания в фоновом режиме.
fi
TimerOn()
{
sleep $TIMELIMIT && kill -s 14 $$ &
# Ждет 3 секунды, затем посылает сигнал сценарию.
}
Int14Vector()
{
answer="TIMEOUT"
PrintAnswer
exit $TIMER_INTERRUPT
}
130
TimerOn
read answer
PrintAnswer
exit 0
#!/bin/bash
# [Link]
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"
# См. Справочные страницы "stty."
}
echo
echo
131
# Поведение этого сценария несколько отличается от "[Link]."
# При любом нажатии клавиши, счетчик сбрасывается.
exit 0
#!/bin/bash
# [Link] [время ожидания]
# Вдохновлено предложением "syngin seven" (спасибо).
TIMELIMIT=4 # 4 секунды
echo
exit 0
$UID ID пользователя
#!/bin/bash
# [Link]: Я root или нет?
132
echo "Вы root."
else
echo "Вы просто обыкновенный пользователь (но мама все равно вас любит)."
fi
exit 0
# ============================================================= #
# Код ниже не будет выполняться, потому что сценарий уже завершен.
ROOTUSER_NAME=root
Позиционные параметры
$0, $1, $2, и т. д. Позиционные параметры передаваемые из командной строки
сценарию, передаваемые функции или присваиваемые
переменной (см. Пример 4-5 и Пример 15-16).
133
$* Все позиционные параметры, рассматриваемые как одно слово
#!/bin/bash
# [Link]
# Сценарий вызывается несколькими аргументами, типа "one two three" ...
E_BADARGS=85
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` аргумент1 аргумент2 т.д."
exit $E_BADARGS
fi
echo
echo
echo
134
index=1 # Сброс счетчика.
exit 0
После shift, $@ содержит оставшиеся параметры командной строки, но без $1, который
утрачивается.
#!/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
#!/bin/bash
echo
135
do echo "$((c+=1)): [$i]" # Эта строка остается в каждом случае
#+ неизменной.
# Выводятся аргументы.
done
echo ---
IFS=:
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 ---
var="$*"
echo 'IFS=":", используем $var (var="$*")'
c=0
for i in $var
136
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 ---
::var="$@"
echo 'IFS=":", используем "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---
echo
exit 0
137
#+ и слегка изменен автором документа.
#!/bin/bash
mecho $@ # a,b,c
mecho "$@" # a,b,c
exit
138
LOG=$[Link]
КОМАНДА1="sleep 100"
echo "Запись в журнал PID команд работающих в фоновом режиме сценария: $0" >>
"$LOG"
# Таким образом, они могут быть проверены, и, при необходимости,
#+ уничтожены.
echo >> "$LOG"
Или по другому:
possibly_hanging_job & {
while ((count < TIMEOUT )); do
eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))'
# в /proc ищется информация о запущенных процессах.
# "-d" проверка существования (существует ли директория).
# Поэтому ожидается вывод проблем выполняемого задания.
((count++))
sleep 1
done
eval '[ -d "/proc/$!" ] && kill -15 $!'
# Если запущенное задание зависает, то оно убивается.
}
# -------------------------------------------------------------- #
# Тем не менее, это может не сработать как положено, если после «hanging_job'
#+ запустится другой процесс ...
# В таком случае, неправильно выполняемое задание может быть убито.
# Ariel Meragelman предлагает следующие исправления.
139
TIMEOUT=30
count=0
# Время ожидания в секундах
possibly_hanging_job & {
exit
#!/bin/bash
echo $_ # /bin/bash
# Только вызов /bin/bash запускает сценарий.
# Обратите внимание, что будет варьироваться в
#+ зависимости от того, как вызывается сценарий
:
echo $_ # :
$? Статус выхода команды, функции или самого сценария (см. Пример 24-7)
Примечания
[1] Регистр стека представляет собой набор последовательной памяти, сохраняющей
(pushed) и извлекающей (popped) значения в обратном порядке. Последнее, сохраненное
140
значение будет являться первым для извлечения. Иногда это называют LIFO
(последнее-выходит-первым ) или стеком pushdown.
Опции declare/typeset
Это примерный эквивалент квалификатора типа const в Cи. При попытке изменения
значения переменной readonly, происходит сбой с выводом сообщения об ошибке.
declare -r var1=1
echo "var1 = $var1" # var1 = 1
141
-i integer(целое число)
declare -i number
# Сценарий будет обрабатывать последующие вхождения «number» как целого числа.
number=3
echo "Number = $number" # Number = 3
number=three
echo "Number = $number" # Number = 0
# Пытается вычислить строку «three» как целое число.
n=6/3
echo "n = $n" # n = 6/3
declare -i n
n=6/3
echo "n = $n" # n = 2
-a array(массив)
declare -a indices
-f function(s)(функция(и))
declare -f
Строка в сценарии declare -f без аргументов, вызывает список всех функций, ранее
объявленных в этом сценарии.
declare -f function_name
-x export (экспорт)
declare -x var3
Объявляет переменную, как доступную для экспорта за пределы среды самого сценария.
-x var=$value
142
declare -x var3=373
func1 ()
{
echo Это функция.
}
echo
echo
bar ()
{
foo
echo $FOO
}
Однако . . .
143
foo (){
declare FOO="bar"
}
bar ()
{
foo
echo $FOO
}
bash$ zzy=68
bash$ declare | grep zzy
zzy=68
Примечания
[1] В этом контексте ввод переменной означает ее классифицирование и ограничение ее
свойств. Например, если переменная объявлена или введена как целое число, то ей
больше не доступны операции со строками.
declare -i intvar
intvar=23
echo "$intvar" # 23
intvar=stringval
echo "$intvar" # 0
144
9.3. $RANDOM: генерация случайных
целых чисел
Грешен тот, кто пытается создавать
случайные числа путем уменьшения.
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
145
# Если вам нужно случайное целое число больше, чем определенная нижняя
#+ граница, то настройте проверку отбрасывающую все числа ниже этого значения.
FLOOR=200
number=0 # Инициализация
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
done
echo "Случайное число больше, чем $FLOOR --- $number"
echo
# Объединим оба метода, выше, для получения случайного числа между двумя
#+ пределами.
Number=0 # Инициализация
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # $number нижний предел $RANGE.
done
echo "Случайное число между $FLOOR и $RANGE --- $number"
echo
echo
146
let "die1 = $RANDOM % $SPOTS +1" # Бросаем первую кость.
let "die2 = $RANDOM % $SPOTS +1" # Бросаем вторую кость.
# Какие арифметические операции выше, имеют больший приоритет --
#+ модуль (%) или сложение (+)?
exit 0
Suites="Пики
Буби
Черви
Крести"
Denominations="2
3
4
5
6
7
8
9
10
Валет
Дама
Король
Туз"
# $bozo sh [Link]
# Валет пик
147
# Спасибо, "jipe," за указание использования этого $RANDOM.
exit 0
# ----------------------------------------------------------------
# Этот сценарий моделирует броуновское движение: случайное перемещение
#+ взвешенных частиц в жидкости, зависимость их движения от случайных течений
#+ и столкновений. Известное как «Движение пьяного», разговорно-народное.
Show_Slots () {
echo; echo
echo -n " "
for i in $( seq $NUMSLOTS ) # Достаточность выводимых элементов массива.
do
printf "%3d" ${Slots[$i]} # Выделяем три места на каждый результат.
done
148
echo # Ряд луз:
echo " |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|"
echo " ||"
echo # Обратите внимание, что если счет в любой лузе превысит 99,
#+ то это испортит вывод.
# Запускаем только (!) 500 проходов, как правило, этого хватает.
}
# --------------
# main ()
Initialize_Slots
Run
Show_Slots
# --------------
exit $?
# Упражнения:
149
# ---------
# 1) Покажите результаты в виде вертикальной диаграммы, или, как альтернативы,
#+ скейтеграммы.
# 2) Измените сценарий для использования /dev/urandom вместо $RANDOM.
# Сделается ли результат более случайным?
# 3) Обеспечьте какую-нибудь 'анимацию' или графический вывод для каждого
# шарика в игре.
Bill Gradwohl привел улучшенную формулу, которая работает для положительных чисел.
rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))
randomBetween() {
# Генерация положительных или отрицательных случайных чисел
#+ между $min и $max
#+ и делимых на $divisibleBy.
# Дает «достаточно случайное» распределение возвращаемых значений.
#
# Bill Gradwohl - Oct 1, 2003
syntax() {
# Функции включенные в функцию.
echo
echo "Синтаксис: randomBetween [min] [max] [multiple]"
echo
echo -n "Ожидает до 3 передаваемых параметров, "
echo "но все они совершенно не обязательны."
150
echo "min это минимальное значение"
echo "max это максимальное значение"
echo -n "multiple указывает какой должен быть ответ "
echo "множества этого значения."
echo "т.е. ответ должен без остатка делится на это число."
echo
echo "Если какое-то значение отсутствует, по умолчанию выводится как "
echo -n "области: 0 32767 1 "
echo -n "Успешное завершение возвращает 0, "
echo "не успешное завершение возвращает"
echo "функцию синтаксиса и 1."
echo -n "Ответом является возвращаемый шаблон подстановки переменной "
echo "randomBetweenAnswer"
echo -n "Отрицательные значения для любого переданного параметра "
echo "обрабатываются корректно."
}
local min=${1:-0}
local max=${2:-32767}
local divisibleBy=${3:-1}
# Значения по умолчанию, в случае, если параметры не переданы функции.
local x
local spread
# Санитарная проверка.
if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
syntax
return 1
fi
151
# ---------------------------------------------------------------------
# Теперь сделаем настоящую работу.
spread=$((max-min))
# Omair Eshkenazi указывает, что эта проверка необходима, поскольку max и
#+ min, уже были заменены друг другом.
[ ${spread} -lt 0 ] && spread=$((0-spread))
let spread+=divisibleBy
randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
return 0
152
fi
disp=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+disp]=0
done
exit 0
Насколько случаен $RANDOM? Лучший способ проверить это - написать сценарий, который
отслеживает распределения «случайных» чисел порожденных $RANDOM. Давайте бросим
кость $RANDOM раз...
153
PIPS=6 # Кость имеет 6 плоскостей.
MAXTHROWS=600 # Увеличьте, если у вас много свободного времени.
throw=0 # Количество бросков кости.
print_result ()
{
echo
echo "ones = $ones"
echo "twos = $twos"
echo "threes = $threes"
echo "fours = $fours"
echo "fives = $fives"
echo "sixes = $sixes"
echo
}
update_count()
{
case "$1" in
0) ((ones++));; # Так как бросок кости это не «ноль», то соответствует 1.
1) ((twos++));; # А это 2.
2) ((threes++));; # Так далее.
3) ((fours++));;
4) ((fives++));;
5) ((sixes++));;
esac
}
echo
print_result
exit $?
# Упражнение (легкое):
# ---------------
154
# Перепишите этот сценарий, для подбрасывания монеты 1000 раз.
# Выберите "ОРЕЛ" и "РЕШКА."
Как мы видели в предыдущем примере, при вызове, лучше каждый раз перезапускать
генератор RANDOM. Используя один и тот же источник для RANDOM он повторяет тот же ряд
цифр. [2] (Это совпадает с поведением функции random() в Си)
Пример 9-16. Смена источника RANDOM
#!/bin/bash
# [Link]: Seeding the RANDOM variable.
# v 1.1, reldate 09 Feb 2013
random_numbers ()
{
local count=0
local number
echo; echo
SEED=1
RANDOM=$SEED # Устанавливаем начальным RANDOM случайно созданное число.
echo "Случайное начальное число = $SEED"
random_numbers
echo; echo
SEED=2
RANDOM=$SEED # Пробуем снова, но с другим начальным числом ...
echo "Случайный начальное число = $SEED"
random_numbers # ... дает отличающиеся серии чисел.
echo; echo
# Пофантазируем...
155
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }'| sed s/^0*//)
# Псевдо-случайный вывод извлекается из /dev/urandom
#+ (файла устройства системы псевдо-случайных чисел), затем
#+ "od" преобразует в строку чисел для вывода (восьмеричное представление),
#+ затем "awk" извлекает только одно число для начального числа, и,
#+ наконец, "sed" удаляет все начальные нули.
RANDOM=$SEED
echo "Случайное начальное число = $SEED"
random_numbers
echo; echo
exit 0
#!/bin/bash
# [Link]: Возвращение псевдослучайных чисел в диапазоне 0 - 1,
#+ до 6 десятичных. Например: 0.822725
# С помощью функции awk rand().
exit 0
# Упражнения:
# ---------
156
#+ числа.
Примечания
[1] Истинная 'случайность', если она вообще существует, может быть найдена только с
помощью некоторых, полностью понятых, природных явлений, таких как
радиоактивный распад. Компьютеры только имитируют случайность, а создание
компьютером последовательности 'случайных' чисел поэтому и называют -
псевдослучайными.
157
Глава 10. Управление переменными
Содержание
10.1. Управление строками
10.1.1. Обработка строк с помощью awk
10.1.2. Дополнительные ссылки
10.2. Подстановка параметров
stringZ=abcABC123ABCabc
while read line # Все строки, сколько имеет входной файл ...
do
echo "$line" # Вывод самой строки.
158
len=${#line}
if [[ "$len" -lt "$MINLEN" && "$line" =~ [*{\.}]$ ]]
# if [[ "$len" -lt "$MINLEN" && "$line" =~ \[*\.\] ]]
# Обновление Bash испортило предыдущую версию этого сценария. Ой!
# Спасибо, Halim Srama, указавшему на это и предложившему исправление.
then echo # Добавляем пустую строку сразу после короткой строки,
fi #+ уничтожая точки.
done
exit
# Упражнения:
# ---------
# 1) Сценарий обычно вставляет пустую строку в конце
#+ целевого файла. Исправьте это.
# 2) Строка 17 только рассматривает точки как предложение уничтожить.
# Измените ее, чтобы включать другие общие символы окончания предложения,
#+ такие как ?, !, и ".
stringZ=abcABC123ABCabc
# |------|
# 12345678
index (Индекс)
expr index $string $substring
stringZ=abcABC123ABCabc
# 123456 ...
echo `expr index "$stringZ" C12` # 6
# Позиция C (первый совпадающий
#+ символ)/
159
Извлечение Substring
${string:position}
${string:position:length}
stringZ=abcABC123ABCabc
# 0123456789.....
# Отсчет с 0.
#!/bin/bash
# [Link]
# Создание 8-символьной "случайной" строки.
160
else # Иначе, как начало строки, используем PID сценария.
str0="$$"
fi
randstring="${str1:$POS:$LEN}"
# ^^^^ ^^^^ Можно параметризовать
echo "$randstring"
exit $?
Если параметром $string является "*" или "@", то извлекается максимально $length
позиционных параметров, начиная с $position.
stringZ=abcABC123ABCabc
# 123456789......
# Отсчет с 1.
161
Извлекает $substring от начала $string, где $substring это регулярное
выражение.
stringZ=abcABC123ABCabc
# =======
stringZ=abcABC123ABCabc
# ======
Удаление Substring
${string#substring}
${string##substring}
stringZ=abcABC123ABCabc
# |----| короткое
# |----------| длинное
162
X='a*C'
${string%substring}
Например:
# Переименование всех файлов в $PWD с суффиксом "TXT" в суффикс "txt".
# Например, "[Link]" станет "[Link]" ...
SUFF=TXT
suff=txt
${string%%substring}
stringZ=abcABC123ABCabc
# || короткое
# |------------| длинное
echo ${stringZ%%b*c} # a
# Вырезает длинные соответствия между 'b' и 'c', от окончания $stringZ..
#!/bin/bash
# [Link]:
# Конвертирование всех файлов изображений MacPaint директории
#+ в формат "pbm".
163
# Используем бинарные файлы "macptopbm" из пакета "netpbm",
#+ сопровождаемого Brian Henderson (bryanh@[Link]).
# Netpbm это обычная часть большинства дистрибутивов Linux.
OPERATION=macptopbm
SUFFIX=pbm # Новый суффикс имен файлов.
if [ -n "$1" ]
then
directory=$1 # Имя директории задается как аргумент сценария...
else
directory=$PWD # Иначе используется текущая рабочая директория.
fi
exit 0
# Упражнение:
# --------
# Как можно заметить, этот сценарий преобразует *все* файлы в
#+ текущей рабочей директории.
# Измените работу на *только* файлы с суффиксом ".mac".
#!/bin/bash
# Пакетное преобразование в различные графические форматы.
# Предполагается, что imagemagick установлен (стандартно в Linux).
exit $?
164
#!/bin/bash
# [Link]: Конвертирование потоковых файлов аудио (*.ra) в ogg.
##########################################################################
mplayer "$1" -ao pcm:file=$OUTFILE
oggenc "$OUTFILE" # Автоматически добавляется правильное расширение файла
#+ oggenc.
##########################################################################
exit $?
# Примечание:
# ----
# На веб-сайте, просто нажав на *.ram, потоковые аудио файлы, обычно
#+ загружается только URL фактического *.ra аудио файла.
# Можно использовать "wget" или что-то похожее для скачивания
#+ самого файла *.ra.
# Упражнения:
# ---------
# Этот сценарий преобразует только *.ra файлы.
# Добавьте гибкости, позволяя использовать *.ram и другие имена файлов.
#
# Если вы действительно амбициозны, разверните сценарий для автоматической
#+ загрузки и преобразования потоковых аудио файлов.
# Задайте URL-адрес пакетной загрузки потоковых аудио файлов (с помощью "wget')
#+ и конвертируйте их на лету.
165
Пример 10-5. Эмуляция getopt
#!/bin/bash
# [Link]
# Автор: Chris Morgan
# Используется в ABS Guide с разрешения.
getopt_simple()
{
echo "getopt_simple()"
echo "Параметры '$*'"
until [ -z "$1" ]
do
echo "Обрабатываемые параметры: '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1} # Вырезаем ведущие '/' ...
parameter=${tmp%%=*} # Извлекаем имя.
value=${tmp##*=} # Извлекаем значение.
echo "Параметр: '$parameter', значение: '$value'"
eval $parameter=$value
fi
shift
done
}
---
Замена Substring
${string/substring/replacement}
${string//substring/replacement}
166
stringZ=abcABC123ABCabc
echo ---------------
echo "$stringZ" # abcABC123ABCabc
echo ---------------
# Сама строка не изменяется!
echo
${string/#substring/replacement}
${string/%substring/replacement}
stringZ=abcABC123ABCabc
167
Пример 10-6. Альтернативные способы извлечения и поиска substring
#!/bin/bash
# [Link]
String=23skidoo1
# 012345678 Bash
# 123456789 awk
# Обратите внимание на отличие систем индексации строк:
# В Bash номером первого символа строки является 0.
# В Awk номером первого символа строки является 1.
echo "----"
# И аналогично:
exit 0
Примеры сценариев:
1. Пример 16-9
2. Пример 10-9
3. Пример 10-10
4. Пример 10-11
5. Пример 10-13
6. Пример А-36
7. Пример А-41
168
Примечания
[1] Это относится к аргументам командной строки или параметрам, передаваемым
функциb.
your_id=${USER}-on-${HOSTNAME}
echo "$your_id"
#
echo "Old \$PATH = $PATH"
PATH=${PATH}:/opt/bin # Добавляет /opt/bin в $PATH расширяя сценарий.
echo "New \$PATH = $PATH"
${parameter-default}, ${parameter:-default}
var1=1
var2=2
# var3 не установлена.
echo ${var1-$var2} # 1
echo ${var3-$var2} # 2
# ^ Обратите внимание на префикс $.
echo ${username-`whoami`}
169
# Выводит на экран `whoami`, если переменная $username еще не установлена.
#!/bin/bash
# [Link]
username0=
echo "username0 объявлено, но ей присвоено значение null."
echo "username0 = ${username0-`whoami`}"
# Не выводится.
echo
username2=
echo "username2 объявлена, но присвоено значение null."
echo "username2 = ${username2:-`whoami`}"
# ^
# Будет выведено, потому что в условии проверки : стоит перед -.
# Сравните с первым примером, выше.
# Еще раз:
variable=
# Переменная объявлена, но присвоено значение null.
unset variable
echo "${variable-2}" # 2
echo "${variable:-3}" # 3
exit 0
DEFAULT_FILENAME=[Link]
filename=${1:-$DEFAULT_FILENAME}
170
# Если не указано иное, работает следующий блок команд
#+ файла "[Link]".
# Начало-блока-команд
# ...
# ...
# ...
# Окончание-блока-команд
# Из примера "[Link]":
DISKS=${1:-E_NOPARAM} # Необходимо указать количество дисков.
# Присваиваем $DISKS параметр командной строки $1,
#+ или $E_NOPARAM, если параметр не присвоен.
Сравните этот способ с использованием and list для расширения аргумента командной
строки по умолчанию.
${parameter=default}, ${parameter:=default}
Обе формы почти эквивалентны. : делает различие только тогда, когда $parameter был
объявлен и его значение null, [1], как и выше.
${parameter+alt_value}, ${parameter:+alt_value}
Обе формы почти эквивалентны. : делает различие только тогда, когда $parameter был
объявлен и его значение null, как и выше.
a=${param1+xyz}
echo "a = $a" # a =
param2=
a=${param2+xyz}
echo "a = $a" # a = xyz
param3=123
171
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 был
объявлен и его значение null, как и выше.
# ------------------------------------------------------
ThisVariable=Value-of-ThisVariable
# Обратите внимание, что именам строковых переменных
#+ могут быть присвоены символы.
172
: ${ThisVariable?}
echo "Значение ThisVariable это $ThisVariable".
echo; echo
: ${ZZXy23AB?"ZZXy23AB не присвоено."}
# Так как ZZXy23AB не присвоен, то сценарий
#+ завершается с сообщением об ошибке
# Те же результаты: dummy_variable=${ZZXy23AB?}
# dummy_variable=${ZZXy23AB?"ZXy23AB не присвоено."}
#
# echo ${ZZXy23AB?} >/dev/null
HERE=0
exit $HERE # Выход сценария НЕ здесь.
# На самом деле, этот сценарий должен возвращать статус выхода (echo $?) 1.
: ${1?"Usage: $0 ARGUMENT"}
# Здесь сценарий завершается, если параметр командной строки отсутствует,
#+ с последующим выводом сообщения об ошибке.
# [Link]: 1: Usage: [Link] ARGUMENT
echo "Эти две строки выводятся только если задан параметр командной строки."
echo "Параметр командной строки = \"$1\""
exit 0 # Выход будет здесь только в том случае, если присутствует параметр
#+ командной строки.
173
${#var}
Размер строки (количество символов в $var). Для массива ${#array} это размер
первого элемента в массиве.
Исключения:
#!/bin/bash
# [Link]
E_NO_ARGS=65
var01=abcdEFGH28ij
echo "var01 = ${var01}"
echo "Размер var01 = ${#var01}"
# Теперь, давайте попробуем вставить пробел.
var02="abcd EFGH28ij"
echo "var02 = ${var02}"
echo "Размер var02 = ${#var02}"
exit 0
${var#Pattern}, ${var##Pattern}
174
# Пример функции из примера "[Link]".
# Вырезаем начальные нули из переданных аргументов.
${var%Pattern}, ${var%%Pattern}
175
#!/bin/bash
# [Link]
var1=abcd12345abc6789
pattern1=a*c # * (wild card) соответствие всему между a - c.
echo
echo "var1 = $var1" # abcd12345abc6789
echo "var1 = ${var1}" # abcd12345abc6789
# (альтернативная форма)
echo "Количество символов в ${var1} = ${#var1}"
echo
echo
exit 0
176
E_BADARGS=65
case $# in
0|1) # Вертикальная черта означает, в этом контексте, "ИЛИ".
echo "Usage: `basename $0` старый_суффикс_файла новый_суффикс_файла"
exit $E_BADARGS # Если аргументов 0 или 1, то выручает.
;;
esac
exit 0
#!/bin/bash
var1=abcd-1234-defg
echo "var1 = $var1"
t=${var1#*-*}
177
echo "var1 ( удаляется все, включая первое соответствие) = $t"
# t=${var1#*-} работает так же,
#+ # короткие соответствия строки,
#+ а * соответствие всему предшествующему, включая пустую строку.
# (Спасибо Stephane Chazelas,за разъяснение.)
t=${var1##*-*}
echo "Если var1 содержит \"-\",то возвращается пустая строка... var1 = $t"
t=${var1%*-*}
echo "var1 ( удаляется все от конца) = $t"
echo
# -------------------------------------------
path_name=/home/bozo/ideas/[Link]
# -------------------------------------------
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/", "/"
#+ произойдет сбой.
# Удаление суффикса (имени файла), особенно когда у basename нет суффикса, а
#+ работа производится с 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
178
${var/#Pattern/Replacement} Если префикс var соответствует Pattern, то
Pattern заменяется на Replacement.
#!/bin/bash
# [Link]:
# Демонстрация соответствия шаблонов префиксу/суффиксу строки
echo
# ----------------------------------------------------
# Должны совпадать начало/конец строки, в противном случае не заменяется.
# ----------------------------------------------------
v3=${v0/#123/000} # Соответствует, но не началу.
echo "v3 = $v3" # abc1234zip1234abc
# НЕ ЗАМЕНЯЕТСЯ.
v4=${v0/%123/000} # Соответствует, но не окончанию.
echo "v4 = $v4" # abc1234zip1234abc
# НЕ ЗАМЕНЯЕТСЯ.
exit 0
xyz23=все_равно
xyz24=
179
echo "a = $a" # a = xyz23 xyz24
a=${!xyz@} # То же, что и выше.
echo "a = $a" # a = xyz23 xyz24
echo "---"
abc23=что_то_другое
b=${!abc*}
echo "b = $b" # b = abc23
c=${!b} # Теперь более знакомый тип косвенной ссылки.
echo $c # что_то_другое
Примечания
[1] Если в не интерактивном сценарии $parameter имеет значение null, он прервется со
статусом выхода 127 (код ошибки Bash для «command not found»).
--Shakespeare, Othello
Содержание
11.1. Циклы
180
11.2. Вложенные циклы
11.3. Управление циклом
11.4. Проверки и ветвления
11.1. Циклы
Цикл это блок кода, повторяющий [1] список команд до тех пор, пока управляющее условие
цикла истинно.
Циклы for
Если do находится на одной строке с for, то после list необходима точка с запятой.
#!/bin/bash
# Список планет.
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
181
echo $planet # Каждая планета будет на отдельной строке.
done
echo; echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
# Все планеты будут на одной строке.
# Весь «list», заключенный в кавычки, создает одну переменную.
# Почему? В переменные включены пробелы.
do
echo $planet
done
exit 0
Каждый элемент [list] может содержать несколько параметров. Это полезно при
обработке групп параметров. В таких случаях с помощью команды set (см. Пример 15-16)
принудительно анализируется каждый элемент [list] и каждый компонент объявляется
позиционным параметром.
#!/bin/bash
# Снова планеты.
for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
do
set -- $planet # Анализ переменной "planet"
#+ и объявление позиционных параметров.
# "--" предотвращает неприятные сюрпризы, если $planet равно null или
#+ начинается с тире.
exit 0
182
#!/bin/bash
# [Link]
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind" # Список интересующих файлов.
# Помещаем пустой файл, /usr/bin/fakefile.
echo
ls -l $file | awk '{ print $8 " размер файла: " $5 }' # Вывод 2 полей.
whatis `basename $file` # Файловая информация.
# Обратите внимание, что для этого необходимо установить базу данных Whatis.
# Что бы это сделать, запустите /usr/bin/makewhatis из-под root.
echo
done
exit 0
#!/bin/bash
filename="*txt"
#!/bin/bash
183
# [Link]: Создание [list] в цикле, с помощью "globbing" ...
# Globbing = расширяемое имя файла.
echo
for file in *
# ^ Bash выполняет расширение имени файла выражения,
#+ которое признает шаблон подстановки.
do
ls -l "$file" # Список всех файлов в $PWD (в текущей директории).
# Напомним, что символу шаблона подстановки "*" соответствует любое имя,
#+ однако, в 'шаблоне подстановки' (globbing), ему не соответствуют файлы
#+ начинающиеся с точки.
# Если шаблон не соответствует файлу, он расширяется в себя (т.е. в *).
# Перед этим установите опцию nullglob
#+ (shopt -s nullglob).
# Спасибо S.C.
done
echo; echo
echo
exit 0
#!/bin/bash
for a
do
echo -n "$a "
done
echo
exit 0
184
Для создания [list] в цикле for позволяется использовать подстановку команд. См. также
Пример 16-54, Пример 11-11 и Пример 16-48.
#!/bin/bash
# [Link]: Создание [list] цикла for
#+ командой подстановки.
NUMBERS="9 7 3 8 37.53"
echo
exit 0
Вот более сложный пример использования команды подстановки для создания [list].
#!/bin/bash
# [Link]: Поиск совпадающих строк в двоичном файле.
E_BADARGS=65
E_NOFILE=66
if [ $# -ne 2 ]
then
echo "Usage: `basename $0` искомая_строка файла"
exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
echo "Файл \"$2\" не существует."
exit $E_NOFILE
fi
185
# строкой "$2" | grep "$1" | tr -s "$IFS" '[\n*]'
exit 0
#!/bin/bash
# [Link]
PASSWORD_FILE=/etc/passwd
n=1 # Номер пользователя
# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #33 = bozo
exit $?
# Обсуждение:
# ----------
# Как обычный пользователь может прочитать /etc/passwd или запустить сценарий?
#+ (Подсказка: Проверьте права доступа к /etc/passwd.)
# Это дыра в безопасности? Почему да, или почему нет?
#!/bin/bash
# [Link]:
# Поиск заданной строки в двоичных файлах заданного каталога.
directory=/usr/bin/
fstring="Free Software Foundation" # Смотрим, какие файлы из FSF.
186
do
strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
# В выражении "sed"
#+ нужно разделять нормальным "/" разделителем,
#+ потому что "/" может быть одним из отфильтровываемых символов.
# Невыполнение этого выдает сообщение об ошибке. (Попробуй.)
done
exit $?
# Упражнение (легкое):
# ---------------
# Преобразуйте этот сценарий для принятия параметра из командной строки
#+ $directory и $fstring.
generate_list ()
{
echo "one two three"
}
# one
# two
# three
Вывод цикла for может быть передан посредством конвейера команде или командам.
#!/bin/bash
# [Link]: Список символических ссылок в директории
directory=${1-`pwd`}
# По умолчанию текущая рабочая директория,
#+ если не указывается другая.
# Эквивалентно блоку кода ниже.
# ----------------------------------------------------------
# ARGS=1 # Ожидается один аргумент из командной строки.
#
# if [ $# -ne "$ARGS" ] # Если аргумент не 1...
# then
# directory=`pwd` # текущая рабочая директория
# else
# directory=$1
187
# fi
# ----------------------------------------------------------
for file in "$( find $directory -type l )" # -type l = символическая ссылка
do
echo "$file"
done | sort # В ином случае список файлов
#+ не сортируется.
# Строго говоря цикл не является действительно необходимым,
#+ поскольку выходные данные команды «find» расширяются в одно слово.
# Однако это легко понять и, таким образом, проиллюстрировать.
exit 0
# --------------------------------------------------------
# Jean Helou предлагает следующую альтернативу:
OLDIFS=$IFS
IFS='' # IFS в значении null означает «без переноса слов»
for file in $( find $directory -type l )
do
echo $file
done | sort
#!/bin/bash
# [Link]: Список символических ссылок директории.
188
directory=${1-`pwd`}
# По умолчанию - текущая рабочая директория,
#+ если не указана другая.
for file in "$( find $directory -type l )" # -type l = символические ссылки
do
echo "$file"
done | sort >> "$OUTFILE" # stdout цикла перенаправляется
# ^^^^^^^^^^^^^ в сохраняемый файл.
exit $?
Существует альтернативный синтаксис для цикла for, который будет выглядеть очень
знакомым для программистов на Cи. Для этого потребуются двойные скобки.
#!/bin/bash
# Несколько способов подсчета до 10.
echo
# Обычный синтаксис.
for a in 1 2 3 4 5 6 7 8 9 10
do
echo -n "$a "
done
echo; echo
# +==========================================+
echo; echo
# +==========================================+
189
echo; echo
# +==========================================+
LIMIT=10
echo; echo
# +=========================================================================+
echo; echo
exit 0
---
#!/bin/bash
# Факс (должен быть установлен пакет 'efax').
EXPECTED_ARGS=2
E_BADARGS=85
MODEM_PORT="/dev/ttyS2" # На Вашей машине может отличаться.
# ^^^^^ По умолчанию, порт карты модема PCMCIA.
if [ $# -ne $EXPECTED_ARGS ]
# Проверка правильного количества аргументов командной строки.
then
echo "Usage: `basename $0` телефон# текстовый файл"
exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
echo "Файл $2 не является текстовым."
# Не обыкновенный файл, или не существует.
exit $E_BADARGS
190
fi
# Выводится:
# * 1 ** 2 ** 3 ** 4 ** 5 ** 6 ** 7 ** 8 ** 9 ** 10 *
# А, echo $? возвратило 0, поэтому Bash не увидел ошибки.
echo
for n in 1 2 3
{ echo -n "$n "; }
# ^
while
Эта конструкция проверяет условие в верхней части цикла, и продолжает цикл, до тех
191
пор, пока условие истинно (возвращает статус выхода 0). В отличие от цикла for, цикл
while находит применение в тех случаях, когда количество итераций (повторений
цикла) заранее не известно.
while [ условие ]
do
команда(ы)...
done
Конструкция прямых скобок в цикле while, это не более чем наш старый друг, скобки
проверки test, используемые в проверке if/then. На самом деле, цикл while может
использовать конструкцию более универсальных двойных скобок (while [[условие]]).
Как и в случае с циклом for, при размещении do на той же строке цикла, что и условия,
необходима точка с запятой.
while [ условие ] ; do
Обратите внимание, что скобки проверки не являются обязательными в цикле while. См.,
например, конструкцию getopts.
#!/bin/bash
var0=0
LIMIT=10
echo
exit 0
#!/bin/bash
echo
# Эквивалентно:
while [ "$var1" != "end" ] # while test "$var1" != "end"
192
do
echo "Введите переменную #1 (end - выход) "
read var1 # Не читается 'read $var1' (почему?).
echo "Переменная #1 = $var1" # Потому что нужны кавычки "#" ...
# Если ввели 'end', оно выведется на экран.
# Т.к. не будет проверяться условие завершения верхней частью цикла.
echo
done
exit 0
Цикл while может иметь несколько условий. Завершение цикла определяет только конечное
условие. Для этого потребуется немного отличающийся синтаксис цикла.
#!/bin/bash
var1=unset
previous=$var1
exit 0
Как в цикле for, так и в цикле while может использоваться синтаксис Си, использующий
конструкцию двойных круглых скобок ( см. также Пример 8-5).
#!/bin/bash
# [Link]: Счет до 10 циклом "while".
193
done # Пока никаких сюрпризов.
echo; echo
# +=================================================================+
echo
exit 0
условие ()
{
((t++))
if [ $t -lt 5 ]
then
return 0 # истинно
else
return 1 # ложно
fi
}
while условие
# ^^^^^^^^^
# Функция вызывает — четыре повторения цикла.
do
echo "Продолжается: t = $t"
done
# Продолжается: t = 1
# Продолжается: t = 2
# Продолжается: t = 3
# Продолжается: t = 4
194
Путем принудительного соединения команды read с циклом while, мы получим удобную
конструкцию while read, полезную для чтения и анализа файлов.
until
Эта конструкция проверяет состояние верхней части цикла и продолжает цикл до тех
пор, пока это условие имеет значение ложно (в отличии от цикла while).
until [ истинное_условие ]
do
команда(ы)...
done
Обратите внимание, что цикл проверки until, проверяет условие в верхней части цикла перед
каждым проходом, в отличии от аналогичных конструкций в некоторых языках
программирования.
Как и в случае с циклом for, размещение do на той же строке, что и условия проверки,
требует точки с запятой.
until [ истинное_условие ] ; do
195
Пример 11-19. Цикл until
#!/bin/bash
END_CONDITION=end
# --- #
LIMIT=10
var=0
exit 0
Как выбрать между циклами for, while и until? В Cи, как правило, цикл for используется
когда заранее известно число повторений. В Bash, ситуация неоднозначна. В Bash цикл for
более свободно структурирован и более гибок, чем его эквивалент в других языках. Поэтому,
не стесняйтесь использовать любой тип цикла, позволяющего выполнить работу более
простейшим образом.
Примечания
[1] Итерация: Повторное выполнение команды или группы команд, обычно - но не всегда,
пока заданное условие не выполнилось, или до выполнения заданного условия.
196
Вложенный цикл это цикл в цикле, внутренний цикл в теле внешнего цикла. Как это
работает: первый проход внешнего цикла вызывает внутренний цикл, выполняемый до
завершения. Затем второй проход внешнего цикла снова вызывает внутренний цикл. Это
повторяется до тех пор, пока внешний цикл не завершится. Конечно, прерывание любого -
внутреннего или внешнего цикла, прерывает процесс.
Пример 11-20. Вложенный цикл
#!/bin/bash
# [Link]: Вложенные циклы "for".
# ===============================================
# Запуск внутреннего цикла.
for b in 1 2 3 4 5
do
echo "Передача $inner внутреннего цикла."
let "inner+=1" # Увеличение счетчика внутреннего цикла.
done
# Окончание внутреннего цикла.
# ===============================================
exit 0
См. Пример 27-11 для иллюстрации вложенных циклов while, а Пример 27-13 показывает
цикл while, вложенный в цикл until.
197
Команды влияющие на поведение цикла
break, continue
#!/bin/bash
echo
echo "Вывод чисел от 1 до 20 (но не 3 и 11)."
a=0
# Упражнение:
# Почему цикл выводит числа до 20?
echo; echo
##################################################################
a=0
if [ "$a" -gt 2 ]
then
break # Прерываем всю остальную часть цикла.
fi
198
exit 0
При необходимости команда break может принимать параметр. Обычно break завершает
только внутренний цикл, в котором она находится, а break N прерывает N уровней цикла.
#!/bin/bash
# [Link]: Прерывание циклов.
for outerloop in 1 2 3 4 5
do
echo -n "Group $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
#!/bin/bash
# Команда "continue N" передает работу N-ому уровню цикла.
# --------------------------------------------------------------------
for inner in 1 2 3 4 5 6 7 8 9 10 # внутренний цикл
199
do
done
echo; echo
# Упражнение:
# Придумайте практическое использование для 'continue N' в сценарии.
exit 0
while true
do
for n in .iso.*
do
[ "$n" = ".[Link]" ] && 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
exit 0
200
while true
do
for задание in {шаблон}
do
{задача уже выполнена или выполняется} && continue
{отмечает задачу как выполняемую, выполняет задачу, отмечает задачу как
выполненную}
continue 2
done
break # Или что-то вроде `sleep 600', чтобы избежать прекращения.
done
Примечания
[1] Это встроенные функции оболочки, в то время как другие команды цикла, такие как
while и case - являются ключевыми словами (keywords).
201
case "$variable" in
"$условие1" )
команда...
;;
"$условие2" )
команда...
;;
esac
#!/bin/bash
# Проверка диапазонов символов.
case "$Keypress" in
[[:lower:]] ) echo "Строчная буква";;
[[:upper:]] ) echo "Прописная буква";;
[0-9] ) echo "Цифра";;
* ) echo "Пунктуация, пробелы, или иное";;
esac # Допускаются диапазоны символов в [квадратных скобках], или
#+ диапазоны POSIX в [[ двойных квадратных скобках ]].
# Упражнение:
# --------
# При остановке сценария, он принимает одно нажатие клавиши, а затем
202
#+ прекращается.
# Измените сценарий так, чтобы он принимал повторный ввод,
#+ сообщал о каждом нажатии клавиши и прекращался только при нажатии «X».
# Подсказка: заключите все в цикл "while".
exit 0
#!/bin/bash
read person
case "$person" in
# Обратите внимание — переменная заключена в кавычки.
"E" | "e" )
# Доступен ввод в верхнем или нижнем регистре.
echo
echo "Roland Evans"
echo "4321 Flash Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "revans@[Link]"
echo "Business partner & old friend"
;;
# Обратите внимание, что каждую опцию завершает двойная точка с запятой.
"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@[Link]"
echo "Ex-girlfriend"
echo "Birthday: Feb. 11"
;;
* )
203
# Опция по умолчанию.
# Здесь возможен пустой ввод (нажатие возврата).
echo
echo "Пока в базе нет данных."
;;
esac
echo
# Упражнение:
# --------
# Измените сценарий так, что бы он принимал очередной ввод,
#+ вместо прекращения, после вывода единственного адреса.
exit 0
case "$1" in
"") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;;
# Параметры командной строки отсутствуют,
# или первый параметр пустой.
# Обратите внимание, что ${0##*/} это подстановка параметра ${var##шаблон}.
# Конечным результатом является $0.
204
# Из сценария Stefano Falsetto's "Log2Rot",
#+ часть его пакета "rottlog".
# Используется с разрешения.
#!/bin/bash
# [Link]: Использование подстановки команд для создания переменной case
exit 0
#!/bin/bash
# [Link]: Простой поиск совпадающих строк
# с помощью конструкции 'case'.
match_string ()
{ # Извлечение совпадающих строк.
MATCH=0
E_NOMATCH=90
PARAMS=2 # Функции необходимы 2 аргумента.
E_BAD_PARAMS=91
case "$1" in
"$2") return $MATCH;;
* ) return $E_NOMATCH;;
esac
a=one
b=two
c=three
d=two
205
match_string $a $b # нет совпадения
echo $? # 90
match_string $b $d # совпадение
echo $? # 0
exit 0
#!/bin/bash
# [Link]: Фильтрование строк с помощью структуры "case".
SUCCESS=0
FAILURE=1 # Было FAILURE=-1, но Bash больше не поддерживает
#+ отрицательные возвращаемые значения.
case "$1" in
[a-zA-Z]*) return $SUCCESS;; # Начинается с буквы?
* ) return $FAILURE;;
esac
} # Сравните это с функцией"isalpha ()" в Си.
case $1 in
*[!a-zA-Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
case $1 in
*[!0-9]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
206
echo "\"$*\" начинается с буквенного символа."
if isalpha2 "$@"
then # Нет смысла в проверке, если первый символ не является буквой.
echo "\"$*\" содержит только буквенные символы."
else
echo "\"$*\" содержит, по крайней мере, один не буквенный символ."
fi
else
echo "\"$*\" начинается с не буквенного символа."
# Если нет переданных аргументов, то так же "не буква".
fi
echo
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
# Упражнение:
# --------
# Напишите функцию 'isfloat ()' для проверки цифр с плавающей точкой.
# Подсказка: Функция повторяет 'isdigit ()',
#+ но добавьте обязательную проверку наличия десятичной точки.
207
select
#!/bin/bash
echo
exit
# Упражнение:
# --------
# Исправьте этот сценарий, что бы принимался пользовательский ввод, не
#+ указанный оператором «select».
# Например, если пользователь введет "peas,"
#+ сценарий должен выдать "Извините. Этого в меню нет."
208
#!/bin/bash
echo
choice_of()
{
select vegetable
# [in list] отсутствует, поэтому 'select' использует аргументы переданные
#+ функции.
do
echo
echo "Любимый овощ $vegetable."
echo "Гадость!"
echo
break
done
}
exit 0
Примечания
[1] Шаблон совпадения строк может также начинаться с ( слевой скобки, придавая более
упорядоченный вид.
209
script_name=`basename $0`
echo "Название этого сценария $script_name."
Вывод команды может быть использован как аргумент для другой команды, для
присваивания переменной и даже для создания списка аргументов в цикле for.
rm `cat filename` # "filename" содержит список файлов для удаления.
#
# S. C. указывает, что "слишком большой список аргументов" может привести к
#+ ошибке.
# Лучше так xargs rm -- < filename
# ( -- охватывает те случаи, когда "filename" начинается с "-" )
textfile_listing=`ls *.txt`
# Переменная содержит имена всех файлов *.txt в текущей рабочей директории.
echo $textfile_listing
# Спасибо S.C.
Даже когда слова не разделяются, подстановка команд может удалять конечные символы.
# cd "`pwd`" # Это должно работать всегда.
# Однако...
210
mkdir 'директория с символом перевода строки в конце
'
dir_listing=`ls -l`
echo $dir_listing # без кавычек
211
либо перенаправление или команду cat.
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
#!/bin/bash
212
# [Link]: Не делайте это дома, народ.
# Из "Stupid Script Tricks," Том I.
# echo "$dangerous_variable"
# Не пытайтесь повторить это! Сценарий зависнет.
exit 0
Обратите внимание, что не происходит переполнения буфера. Это один из случаев, когда
интерпретируемый язык, например Bash, обеспечивает более высокий уровень защиты от
ошибок программиста, чем компилируемый язык.
variable1=`for i in 1 2 3 4 5
do
echo -n "$i" # Команда 'echo' решающая
done` #+ для подстановки команд.
i=0
variable2=`while [ "$i" -lt 10 ]
do
echo -n "$i" # Опять необходимо 'echo'.
let "i += 1" # Увеличение.
done`
exit 0
213
Подстановка команд расширяет набор инструментов доступных в Bash. Это всего лишь
вопрос написания программы или сценария, которая выводит в stdout (подобно
инструментам UNIX) и записи этого вывода в переменную.
#include <stdio.h>
int main()
{
printf( "Hello, world.\n" );
return (0);
}
#!/bin/bash
# [Link]
greeting=`./hello`
echo $greeting
bash$ sh [Link]
Hello, world.
214
Форма подстановки команд $(...) допускает вложения. [3]
И что-то посложнее...
#!/bin/bash
# [Link]
# Пример вложений подстановки команд.
E_NOARGS=86
E_BADARG=87
MINLEN=7
if [ -z "$1" ]
then
echo "Usage $0 LETTERSET"
exit $E_NOARGS # Сценарию нужен аргумент командной строки.
elif [ ${#1} -lt $MINLEN ]
then
echo "Аргумент должен иметь хотя бы $MINLEN букв."
exit $E_BADARG
fi
echo
echo "Найдены буквы ${#Anagrams[*]} анаграмм "
echo
echo ${Anagrams[0]} # Первая анаграмма.
echo ${Anagrams[1]} # Вторая анаграмма.
# и т.д.
exit $?
215
1. Пример 11-8
2. Пример 11-27
3. Пример 9-16
4. Пример 16-3
5. Пример 16-22
6. Пример 16-17
7. Пример 16-54
8. Пример 11-14
9. Пример 11-11
10. Пример 16-32
11. Пример 20-8
12. Пример A-16
13. Пример 29-3
14. Пример 16-47
15. Пример 16-48
16. Пример 16-49
Примечания
[1] В подстановке команд, команда может быть внешней системной командой,
внутренней встроенной сценария, или даже функцией сценария.
216
Глава 13. Арифметические расширения
Арифметические расширения представляют собой мощный инструмент для выполнения в
сценариях арифметических операций (с целыми числами). Перевод строки в числовое
выражение осуществляется относительно просто, используя обратные кавычки, двойные
круглые скобки или let.
Варианты
Арифметическое расширение с обратными кавычками (часто используется в сочетании с
expr).
z=$(($z+3))
z=$((z+3)) # То же правильно.
# В двойных скобках,
#+ параметр разыменован, но
#+ это по желанию.
n=0
echo "n = $n" # n = 0
(( n += 1 )) # Увеличение.
# (( $n += 1 )) Это не правильно!
echo "n = $n" # n = 1
217
let z=z+3
let "z += 3" # Кавычки разрешают использовать пробелы при присваивании
#+ переменной.
# Оператор 'let' выполняет арифметическое вычисление,
#+ а не расширение.
1. Пример 16-9
2. Пример 11-15
3. Пример 27-1
4. Пример 27-11
5. Пример A-16
Пользователь Linux, привет! Вы читаете то, что принесет вам удачу и счастье. Просто
перешлите по электронной почте копию этого документа 10 вашим друзьям. Перед тем, как
сделать копии, отправьте 100-строчный сценарий Bash, в нижней части этого письма,
первому в списке. Затем удалите имя и добавьте свое в нижней части списка.
218
Отправьте ваши десять копий сегодня! Не разрывайте цепь!
Сделайте копии в течение 48 часов.
Вежливый 'NIX "куки счастья", с некоторыми изменениями и многими извинениями
Часть 4. Команды
Владение командами Linux является незаменимой прелюдией написания эффективных
сценариев.
Этот раздел охватывает следующие команды:
A
ac, adduser, agetty, agrep, ar, arch, at, autoload, awk
B
badblocks, banner, basename, batch, bc, bg, bind, bison, builtin,
bzgrep, bzip2
C
cal, caller, cat, cd, chmod, chfn, chgrp, chkconfig, chmod, chown,
chroot, cksum, clear, clock, cmp, col, colrm, column, comm,
command, compgen, complete, compress, coproc, cp, cpio, cron,
crypt, csplit, cu, cut
D
date, dc, dd, debugfs, declare, depmod, df, dialog, diff, diff3,
diffstat, dig, dirname, dirs, disown, dmesg, doexec, dos2unix, du,
dump, dumpe2fs
E
e2fsck, echo, egrep, enable, enscript, env, eqn, eval, exec, exit
(Связанная тема: exit status), expand, export, expr
F
219
factor, false, fsck, fdformat, fdisk, fg, fgrep, file, find,
finger, flex, flock, fmt, fold, free, fsck, ftp, fuser
G
getfacl, getopt, getopts, gettext, getty, gnome-mount, grep,
groff, groupmod, groups (Связанная тема: переменная $GROUPS), gs, gzip
H
halt, hash, hdparm, head, help, hexdump, host, hostid, hostname
(Связанная тема: переменная $HOSTNAME), hwclock
I
220
pstree, ptx, pushd, pwd (Связанная тема: переменная $PWD)
Q
quota
R
rcp, rdev, rdist, read, readelf, readlink, readonly, reboot,
recode, renice, reset, resize, restore, rev, rlogin, rm,
rmdirreboot, rmmod, route, rpm, rpm2cpio, rsh, rsync, runlevel,
run-parts, rx, rz
S
sar, scp, script, sdiff, sed, seq, service, set, setfacl,
setquota, setserial, setterm, sha1sum, shar, shopt, shred,
shutdown, size, skill, sleep, slocate, snice, sort, source, sox,
split, sq, ssh, stat, strace, strings, strip, stty, su, sudo, sum,
suspend, swapoff, swapon, sx, sync, sz
T
tac, tail, tar, tbl, tcpdump, tee, telinit, telnet, TeX, texexec,
time, times, tmpwatch, top, touch, tput, tr, traceroute, true,
tset, tsort, tty, tune2fs, type, typeset
U
ulimit, umask, umount, uname, unarc, unarj, uncompress, unexpand,
uniq, units, unlzma, unrar, unset, unsq, unzip, uptime,
usbmodules, useradd, userdel, usermod, users, usleep, uucp, unzip,
uudecode, uuencode, uux
V
vacation, vdir, vmstat, vrfy
W
w, wait, wall, watch, wc, wget, whatis, whereis, which, who,
whoami, whois, write
X
xmessage, xargs, xrandr, xz
Y
yacc, yes
Z
zcat, zdiff, zdump, zegrep, zenity, zfgrep, zgrep, zip
221
Содержание
15. Внутренние и встроенные команды
15.1. Команды управления задачами
16. Внешние фильтры, программы и команды
16.1. Основные команды
16.2. Сложные команды
16.3. Команды Времени/Даты
16.4. Команды обработки текста
16.5. Файловые команды и команды архивирования
16.6. Команды соединения
16.7. Команды управления терминалом
16.8. Математические команды
16.9. Различные команды
17. Системные команды и команды управления
17.1. Анализ системного сценария
#!/bin/bash
# [Link]
222
echo "[Нажмите Ctl-C для выхода.]"; echo
sleep 1 # Ожидание.
sh $0 # Сыграй снова, Sam.
# Примечания:
# ----
# Будьте осторожны, не запускайте этот сценарий слишком надолго.
# Он пожирает слишком много системных ресурсов.
Встроенная (builtin) команда может быть синонимом системной команды с тем же именем,
но Bash переопределяет ее внутри. Например команда echo в Bash не является тем же
самым, что /bin/echo, хотя поведение их почти идентично.
#!/bin/bash
Ключевые слова (keyword) это зарезервированные слова, токены или операторы. Ключевые
слова имеют особое значение в оболочке и в действительности являются строительными
блоками синтаксиса оболочки. Например, for, while, do, и ! это ключевые слова. Подобно
встроенным командам, ключевые слова жестко запрограммированы в Bash, но в отличие от
builtin, ключевое слово само по себе является не командой, а частью конструкции команды.
[2]
Для вывода заэкранированных символов echo необходима опция -e. См. Пример 5-2.
223
Обычно, каждая команда echo выводит в терминале новую строку, но опция -n это
подавляет.
См. также Пример 16-22, Пример 16-3, Пример 16-47 и Пример 16-
48.
Помните, что echo `команда` удаляет все символы перевода строки создаваемые
выводом команда .
bash$ ls -l /usr/share/apps/kjezz/sounds
-rw-r--r-- 1 root root 1407 Nov 7 2000 [Link]
-rw-r--r-- 1 root root 362 Nov 7 2000 [Link]
Итак, как мы можем вставить перевод строки в пределах выводимой строки символов?
# Вставляем перевод строки?
echo "Почему эту строку \n не разбивает на две строки?"
# Не разбивает.
echo
224
# А, действительно, необходим префикс переменной '$'?
echo
echo
echo "---------------"
echo
echo
echo
echo "---------------"
echo
echo
echo $string1
# Еще одна строка текста содержащая перевод строки (может быть).
# ^
# Перевод строки становится пробелом.
printf
225
Справочные страницы printf (системной команды) для более глубоко изучения.
#!/bin/bash
# Демонстрация работы printf
Message1="Greetings,"
Message2="Earthling."
echo
echo
# ==========================================#
# Моделирование функции Cи, sprintf().
# Загрузка переменной с форматированной строкой.
echo
exit 0
E_BADDIR=85
var=nonexistent_directory
226
error()
{
printf "$@" >&2
# Форматирование передаваемых позиционных параметров, и передача их stderr.
echo
exit $E_BADDIR
}
# Спасибо S.C.
read
#!/bin/bash
# "Считывание" переменных.
read var1
# Обратите внимание на отсутствие '$' перед var1.
echo
exit 0
#!/bin/bash
# [Link]
echo
227
# -------------------------- #
echo -n "Введите значение: "
read var
echo "\"var\" = "$var""
# Все как ожидается.
# -------------------------- #
echo
# ------------------------------------------------------------------- #
echo -n "Введите другое значение: "
read # Переменная для 'read' отсутствует, поэтому...
#+ Ввод 'read' присваивается по умолчанию переменной $REPLY.
var="$REPLY"
echo "\"var\" = "$var""
# Это эквивалентно первому блоку кода.
# ------------------------------------------------------------------- #
echo
echo "========================="
echo
# ================================================================= #
{ # Блок кода.
read # Строка 1, игнорируется.
read line2 # Строка 2, сохраняется в переменную.
} <$0
echo "Строка 2 этого сценария:"
echo "$line2" # # [Link]
echo # #!/bin/bash строка проигнорирована.
exit 0
Обычно, ввод \ подавляет перевод строки во время ввода в read. Опция -r вызывает
символ \, чтобы интерпретировать в буквальном смысле.
#!/bin/bash
echo
228
<ENTER>."
echo; echo
echo
exit 0
#!/bin/bash
# [Link]: Обнаружение нажатий служебных клавиш.
# Спасибо Sandro Magi, за то, что показал как это сделать.
# --------------------------------------------
229
# Коды символов создаваемых нажатием клавиш.
arrowup='\[A'
arrowdown='\[B'
arrowrt='\[C'
arrowleft='\[D'
insert='\[2'
delete='\[3'
# --------------------------------------------
SUCCESS=0
OTHER=65
230
exit $OTHER
# ========================================= #
#!/bin/bash
uparrow=$'\x1b[A'
downarrow=$'\x1b[B'
leftarrow=$'\x1b[D'
rightarrow=$'\x1b[C'
case "$x" in
$uparrow)
echo "Вы нажали up-arrow"
;;
$downarrow)
echo " Вы нажали down-arrow"
;;
$leftarrow)
echo " Вы нажали left-arrow"
;;
$rightarrow)
echo " Вы нажали right-arrow"
;;
esac
exit $?
# ========================================= #
#!/bin/bash
while true
do
read -sn1 a
test "$a" == `echo -en "\e"` || continue
read -sn1 a
test "$a" == "[" || continue
read -sn1 a
case "$a" in
A) echo "up";;
B) echo "down";;
C) echo "right";;
D) echo "left";;
esac
done
# ========================================= #
# Упражнение:
# --------
# 1) Добавьте определение клавиш "Home," "End," "PgUp," и "PgDn".
231
Опция -n read не определяет нажатие клавиши ENTER (перевод
строки).
#!/bin/bash
echo "------------------------------------------------"
echo "------------------------------------------------"
232
# Переменная $IFS, присваиваемая внутри цикла, устраняет необходимость
#+ сохранения оригинальной $IFS во временной переменной.
# Спасибо Dim Segebart, за разъяснение.
echo "------------------------------------------------"
echo "Список всех пользователей:"
echo
echo "\$IFS все еще $IFS"
exit 0
#!/bin/sh
# [Link]
# Этот пример предоставлен Bjon Eriksson.
last="(null)"
cat $0 |
while read line
do
echo "{$line}"
last=$line
done
echo
echo "++++++++++++++++++++++"
printf "\nГотово, последняя $last\n" # Вывод этой строки изменится
#+ если раскомментировать строку 5.
# (Bash, версии 4.2 требуется -ge.)
233
# 'echo' предоставляет дополнительные скобки.
#############################################
./[Link]
{#!/bin/sh}
{last="(null)"}
{cat $0 |}
{while read line}
{do}
{echo "{$line}"}
{last=$line}
{done}
{printf "nГотово, последнее: $lastn"}
Файловая система
cd
234
Выводом, конечно же, будет /. Это проблема как командной строки,
так и сценария.
pwd
#!/bin/bash
dir1=/usr/local
dir2=/var/spool
pushd $dir1
# Производит автоматически 'dirs' (стек списка директорий в stdout).
echo "Теперь в директории `pwd`." # Для 'pwd' используются обратные кавычки.
235
echo "Верхняя запись в массиве DIRSTACK -это $DIRSTACK."
popd
echo "Теперь обратно в директории `pwd`."
Переменные
let
#!/bin/bash
echo
236
# Конечно, ++a, и т.п., так же позволительно ...
echo
# Тройной оператор.
let a++
let "t = a<7?7:11" # Ложно
echo $t # 11
exit
var=0
echo $? # 0
# Как и ожидалось.
let var++
echo $? # 1
# Команда выполнена успешно,
#+ тогда почему не $?=0 ???
# Аномалия!
let var++
echo $? # 0
# Как и ожидалось.
let var=0
echo $? # 1
# Команда выполнена успешно,
#+ тогда почему не $?=0 ???
eval
237
Команда eval может использоваться для создания кода в командной
строке или в сценарии.
echo $a # $b
# Первый уровень.
eval echo $a # $c
# Второй уровень.
eval eval echo $a # d
# Третий уровень.
# Спасибо E. Choroba.
#!/bin/bash
# Применение "eval" ...
echo; echo
echo
echo "==========================================================="
echo
echo
echo "==========================================================="
238
echo
#!/bin/bash
# [Link]
arr0=( 10 11 12 13 14 15 )
arr1=( 20 21 22 23 24 25 )
arr2=( 30 31 32 33 34 35 )
# 0 1 2 3 4 5 Число элементов (начинаются с нуля)
choose_array ()
{
eval array_member=\${arr${array_number}[element_number]}
# ^ ^^^^^^^^^^^^
# Используем eval для создания имени переменной,
#+ в данном конкретном случае, имени массива.
239
#!/bin/bash
# [Link]
(( param ++ )) # Далее.
done
exit $?
# =================================================
#!/bin/bash
# Принудительный выход путем прекращения ppp.
# Для соединения dialup, конечно.
SERPORT=ttyS3
# В зависимости от аппаратного обеспечения и даже версии ядра,
#+ порт модема на вашей машине может отличаться --
#+ /dev/ttyS1 или /dev/ttyS2.
240
# Следующие действия должны быть сделаны из-под root.
exit $?
# Упражнения:
# ---------
# 1) Необходимо проверить является ли пользователь, вызывающий сценарий -
#+ root.
# 2) Прежде чем убить процесс, проверьте, запущен ли этот процесс.
# 3) Напишите альтернативную версию этого сценария на основе "fuser":
#+ if [ fuser -s /dev/modem ]; then ...
#!/bin/bash
# Версия "rot13" использующая 'eval'.
# Сравните с "[Link]".
exit 0
Вот еще один пример использования eval для оценки сложного выражения, это одна из
самых ранних версий сценария игры тетрис YongYe.
241
В Примере A-53, с помощью eval, элементы массива преобразуются в список команд.
set
#!/bin/bash
# [Link]
# Сценарий "set-test"
echo
echo "Позиционные параметры до set \`uname -a\` :"
echo "Аргумент командной строки #1 = $1"
echo "Аргумент командной строки #2 = $2"
echo "Аргумент командной строки #3 = $3"
echo
echo +++++
echo $_ # +++++
# Установленные в сценарии флаги.
echo $- # hB
# Аномальное поведение?
echo
242
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
#!/bin/bash
# [Link]: Реверс позиционных параметров.
# Сценарий Dan Jacobson, со стилистическими изменениями автора документа.
set a\ b c d\ e;
# ^ ^ Экранированные пробелы
# ^ ^ Не экранированные пробелы
OIFS=$IFS; IFS=:;
# ^ Сохранение старой IFS и установка новой.
echo
until [ $# -eq 0 ]
do # Шаг через позиционные параметры.
echo "### k0 = "$k"" # До
k=$1:$k; # Добавляет каждый позиционный параметр переменной цикла.
# ^
echo "### k = "$k"" # После
echo
shift;
done
# Вопрос:
# Необходимо ли устанавливать новый IFS, разделитель внутренних полей,
#+ для правильной работы этого сценария?
# Что случится, если этого не сделать? Попробуйте.
# И, почему с помощью нового IFS -- двоеточия — в строке 17,
243
#+ производится добавление в переменную цикла?
# Какова цель этого?
exit 0
$ ./[Link]
### k0 =
### k = a b
### k0 = a b
### k = c a b
### k0 = c a b
### k = d e c a b
-
3
-
d e
c
a b
Вызов 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
#!/bin/bash
set -- $variable
# Присвоение позиционных параметров содержимому "$variable".
first_param=$1
second_param=$2
shift; shift # Сдвиг двух последних позиционных параметров.
244
# shift 2 То же работает
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" # (значение null)
echo "Второй параметр = $second_param" # (значение null)
exit 0
unset
bash$
#!/bin/bash
# [Link]: Сброс переменной.
variable=hello # Объявлена.
echo "variable = $variable"
245
if [ -z "$variable" ] # Попробуйте проверить длину строки.
then
echo "\$variable имеет нулевой размер."
fi
exit 0
export
#!/bin/bash
ARGS=2
E_WRONGARGS=85
filename=$1
column_number=$2
export column_number
# Экспорт номера колонки в среду, поэтому она доступна для поиска.
246
# -----------------------------------------------
awkscript='{ total += $ENVIRON["column_number"] }
END { print total }'
# Да, переменная может содержать сценарий awk.
# -----------------------------------------------
exit 0
declare, typeset
readonly
То же, что declare -r, устанавливает переменную только для чтения, или, по
существу, константой. При попытке изменить переменную выводится сообщение об
ошибке. Это аналог оболочки квалификатора типа const языка Cи.
getopts
247
(необязательный). Двоеточие, после названия опции при объявлении тегов этой опции,
является объединяющим аргументом.
#!/bin/bash
# [Link]: Упражнения с getopts и OPTIND
# Сценарий изменен 10/09/03 Bill Gradwohl.
248
# Попробуйте вызвать этот сценарий с:
# 'scriptname -mn'
# 'scriptname -oq qOption' (qOption может быть произвольная строка.)
# 'scriptname -qXXX -r'
#
# 'scriptname -qr'
#+ -Неожиданный результат, принимает «r» в качестве аргумента для опции "q"
# 'scriptname -q -r'
#+ - Неожиданный результат, как и выше
# 'scriptname -mnop -mnop' -Неожиданный результат
# (OPTIND это ненадежное начало, из которого выходит опция.)
#
# Если параметр ожидает аргумент ("флаг:"), то он будет захватывать все
#+ последующее в командной строке.
NO_ARGS=0
E_OPTERROR=85
exit $?
Поведение сценария
249
Эта команда, при вызове из командной строки, выполняет сценарий. В сценарии
source file-name загружается файл file-name. Файл источник (команда точка)
импортирует, добавляет, код в сценарий (то же эффект, как у директивы #include в
программах Си). В результате, строки кода «источника», как будто бы физически,
присутствуют в теле сценария. Это полезно в ситуациях, когда несколько сценариев
используют общие файлы данных или библиотечные функции.
#!/bin/bash
# Обратите внимание, что этот пример должен вызываться bash, т.е.,
#+ bash [Link], а не sh [Link] !
exit $?
variable1=23
variable2=474
variable3=5
variable4=97
print_message ()
{ # Вывод на экран любого, передаваемого ему, сообщения.
250
if [ -z "$1" ]
then
return 1 # Ошибка, при отсутствии аргумента.
fi
echo
until [ -z "$1" ]
do # Шаг через аргументы, передаваемые функции.
echo -n "$1" # Вывод на экран всех аргументов одновременно, подавляя
#+переводы строк.
echo -n " " # Вставляются пробелы между словами.
shift # Далее.
done
echo
return 0
}
Это возможно даже для сценария, который сам является источником, хотя это, наверное, не
имеет практического применения.
#!/bin/bash
# [Link]: сценарий, «рекурсивно» являющийся источником для сам себя.
# Из "Stupid Script Tricks," Том II.
251
# То, что здесь происходит, на самом деле не является рекурсией,
#+ так как сценарий эффективно «расширяет» сам себя, т.е. создает
#+ новый блок кода с каждым проходом цикла «while» в строке 20, с каждым
#+ «источником».
#
# Конечно, сценарий определяет каждый новый 'источник' строки "#!"
#+ как комментарий, а не как начало нового сценария.
echo
# Упражнение:
# --------
# Напишите сценарий, который использует этот трюк для чего-то полезного.
exit
exec
#!/bin/bash
# ----------------------------------
# Остальные строки не выполняются.
252
echo "Это echo ничего не выводит."
#!/bin/bash
# [Link]
echo
sleep 1
Exec также служит для переназначения файловых дескрипторов. Например, exec <zzz-
file заменяет stdin файлом zzz-file.
shopt
Эта команда позволяет изменять опции оболочки на лету (См. Пример 25-1 и Пример 25-
2 ). Она часто появляется в начальных файлах Bash, но может использоваться и в
сценариях. Необходима версия Bash 2 или более поздняя.
shopt -s cdspell
# Позволяет незначительные ошибки в написании имен директорий с 'cd'
# Опция -s установка, -u сброс.
253
cd /hpme # Упс! Опечатка '/home'.
pwd # /home
# Оболочка исправляет опечатку.
caller
Ввод команды caller внутрь функции выводит в stdout информацию о том, кто
вызвал эту функции.
#!/bin/bash
function1 ()
{
# Внутри function1 ().
caller 0 # Расскажет нам о ней.
}
# 9 main [Link]
# ^ Номер строки из которой вызвана функция.
# ^^^^ Вызвана из "main" (основной) части сценария.
# ^^^^^^^ Имя вызывающего сценария.
Команды
true
bash$ true
bash$ echo $?
0
# Бесконечный цикл
while true # псевдоним для ":"
do
операция-1
операция-2
...
операция-n
# Необходим способ прерывания цикла или сценарий зависает.
done
254
false
bash$ false
bash$ echo $?
1
# Проверка "ложности"
if false
then
echo "ложная оценка \"истинно\""
else
echo "ложная оценка \"ложно\""
fi
# ложная оценка "ложно"
type [команда]
Подобна внешней команде which, type cmd определяет "cmd". В отличии от which,
type это встроенная команда Bash. Полезная опция -a для type определяет
keywords и builtins, а также местонахождение системных команд с одинаковыми
именами.
255
hash [команды]
Записывает названия путей указанных команд - в хэш таблицы оболочки [8], - поэтому
оболочке или сценарию уже не надо искать $PATH при последующих вызовах этих
команд. Когда hash вызывается без аргументов, она просто перечисляет хэшированные
команды. Опция -r сбрасывает хэш-таблицу.
bind
Встроенная команда bind отображает или изменяет привязку клавиш readline [9].
help
Примечания
[1] Как указывает Nathan Coulter, "в то время, как порождение процесса является простой
операцией, выполнение новой программы, в недавно порожденном дочернем процессе,
увеличивает затраты".
[3] Обратите внимание, что let не может быть использована для установки строковых
переменных.
[4] Информация Export делает доступность в более общем контексте. Смотрите также
scope.
[4] Опция является аргументом, который выступает в качестве флага включения или
выключения поведения сценариев. Аргумент, связанный с определенной опцией
(флагом), указывает поведение, в зависимости от того включена или выключена опция
(флаг).
[5] Технически, exit только завершает процесс (или оболочку) в котором он запущен, а не
родительский процесс.
256
[7] Хэширование — это способ поиска созданных ключей для данных, хранящихся в
таблице. Сами элементы данных, «используются» для создания ключей, с помощью
одного из ряда простых математических алгоритмов (способов).
[9] Библиотека readline используется Bash для чтения ввода в интерактивной оболочке.
bash $ jobs
[1]+ Запущена sleep 100 &
Спасибо S.C.
disown
257
fg, bg
wait
#!/bin/bash
if [ -z "$1" ]
then
echo "Usage: `basename $0` искомая строка"
exit $E_NOPARAMS
fi
wait
# Остальная часть сценария не запускается до завершения «updatedb».
# Нужно, чтобы база данных обновилась до нахождения имени файла.
locate $1
exit 0
258
аргумента, например, wait%1 или wait $PPID. [1] См. таблицу id заданий.
ls -l &
echo "Done."
bash$ ./[Link]
Done.
[bozo@localhost test-scripts]$ total 1
-rwxr-xr-x 1 bozo bozo 34 Oct 11 15:09 [Link]
_
ls -l &
echo "Done."
wait
bash$ ./[Link]
Done.
[bozo@localhost test-scripts]$ total 1
-rwxr-xr-x 1 bozo bozo 34 Oct 11 15:09 [Link]
259
Перенаправление вывода команды в файл или даже в /dev/null также
исправляет эту проблему.
suspend
logout
times
Эта возможность имеет сравнительно ограниченную ценность, так как она не является
общей для профиля и базовых сценариев оболочки.
kill
#!/bin/bash
# [Link]
260
kill, завершающий процесс, который упорно отказывается умирать с
простым kill. Иногда работает kill -15. Процесс-зомби, то есть,
дочерний процесс, который прекращен, но, как родительский процесс,
не убит (пока), и не может быть убит пользователем -- не льзя убить то,
что уже мертво -- а init, обычно, рано или поздно вычистит его.
killall
command
bash$ command ls
builtin
enable
261
качестве модуля общей библиотеки (DLL) из правильно скомпилированного объектного
файла. [2].
autoload
Обратите внимание, что autoload не является частью установленного ядра Bash. Она
должна быть загружена с помощью enable -f (см. выше).
Обозначение Значение
%N Номер задания [N]
%S Вызов (командной строкой) задания начинающегося строкой S
%?S Вызов (командной строкой) задания содержащего в себе строку S
%% "текущее" задание (последняя задание остановленное в основном режиме или
запущенное в фоновом режиме)
%+ "текущее" задание (последняя задание остановленное в основном режиме или
запущенное в фоновом режиме)
%- Последнее задание
$! Последний фоновый процесс
Примечания
[1] Это относится только к дочерним процессам.
[2] Источник Си для нескольких загружаемых встроенных команд, как правило, находится
в директории /usr/share/doc/bash-?.??/functions .
[3] Тот же самый эффект, как у autoload может быть достигнут с typeset -fu.
262
16.7. Команды управления терминалом
16.8. Математические команды
16.9. Различные команды
bash$ ls -l
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:49 Chapter_headings.txt
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:49 [Link]
bash$ ls -lv
total 0
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:49 Chapter_headings.txt
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:49 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
-rw-rw-r-- 1 bozo bozo 0 Sep 14 18:44 [Link]
bash$ echo $?
2
263
Пример 16-1. Создание оглавления записи CDR диска с помощью ls
#!/bin/bash
# [Link] ([Link])
# Сценарий автоматической записи CDR.
if [ -z "$1" ]
then
IMAGE_DIRECTORY=$DEFAULTDIR
# Директория по умолчанию, если не задана из командной строки.
else
IMAGE_DIRECTORY=$1
fi
# Прожиг CDR.
echo "Записывается диск."
echo "Пожалуйста подождите, это займет какое-то время."
wodim -v -isosize dev=$DEVICE $IMAGEFILE
# В новых дистрибутивах Linux, утилита "wodim" обладает
#+ функциональностью "cdrecord."
exitcode=$?
echo "Exit code = $exitcode"
exit $exitcode
cat, tac
264
комбинации с перенаправлением (> или >>), она обычно используется для
объединения файлов.
# Использование 'cat'
cat filename # Перечисление файла.
cat file.1 file.2 file.3 > file.123 # Объединяет три файла в один.
rev
переворачивает каждую строку файла и выводит на stdout. Эффект не такой же, как у
tac, она сохраняет порядок следования строк, но переворачивает каждую наоборот
( отображая зеркально).
265
Особенно полезны: флаг архивирования (для копирования дерева
директорий) -а, флаг обновления -u (предотвращающий перезапись
одинаково названных новых файлов), и флаги рекурсии -r/-R.
cp -u source_dir/* dest_dir
# "Синхронизирует" dest_dir с source_dir
#+ путем копирования всех новых и ранее не существующих
файлов.
mv
rm
Удаление (стирание) файла или файлов. Опция -f принудительно удаляет даже файлы
readonly и полезна для обхода пользовательского ввода в сценарии.
bash$ rm -badname
rm: неправильная опция -- b
Попробуйте `rm --help' для получения справки.
266
Другой способ заключается в предварении удаляемого имени файла
точкой со слэш.
bash$ rm ./-badname
При использовании флага рекурсии -r, эта команда удаляет все файлы в
дереве директорий начиная с текущей директории. Неосторожное rm
-rf * может уничтожить большой кусок структуры директорий.
rmdir
mkdir
chmod
chmod +x filename
# Делает "filename" исполняемым для всех пользователей.
267
#+ владелец файла, и, конечно же, root может удалять
#+ любой обычный файл в этой директории.
chattr
root# rm [Link]
Если файл имеет установленный атрибут s (безопасный), то при его удалении его блок
перезаписывается двоичными нулями. [3]
268
ln
Ссылки дают возможность вызывать сценарий (или любой другой тип исполняемого
файла) с несколькими именами, и этот сценарий будет работать так, как он был вызван.
#!/bin/bash
# [Link]: Выводит "hello" или "goodbye"
#+ в зависимости от вызова.
269
# ./goodbye
HELLO_CALL=65
GOODBYE_CALL=66
if [ $0 = "./goodbye" ]
then
echo "Good-bye!"
# Другие команды типа goodbye, в зависимости от обстоятельств.
exit $GOODBYE_CALL
fi
echo "Hello!"
# Другие команды типа hello, в зависимости от обстоятельств.
exit $HELLO_CALL
man, info
Примечания
[1] Опция -v также по порядку сортирует имена файлов по префиксам в верхнем и
нижнем регистрах.
[2] Файл с точкой это файл, имя которого начинается с точки, вот так ~/.Xdefaults.
Такие файлы не отображаются в обычных листингах ls, (хотя ls -а показывает их), и
они не могут быть удалены путем случайного rm -rf *. Файлы с точкой обычно
используются как установочные и конфигурационные файлы в домашнем каталоге
пользователя.
[3] Эта особенность еще не полностью реализована в версиях файловой системы ext2/ext3.
Проверьте документацию find вашего дистрибутива Linux.
270
find
- exec COMMAND \;
Если COMMAND содержит {}, то find подставляет "{}" вместо полного пути имени
выбранного файла.
#+ пользователя.
DIR=/home/bozo/junk_files
find "$DIR" -type f -atime +5 -exec rm {} \;
# ^ ^^
# Фигурные скобки являются заполнителем для выводимого пути имени "find."
#
# Удаляются все файлы в "/home/bozo/junk_files"
#+ к которым не обращались *хотя бы* 5 дней (знак плюс ... +5).
#
# где "-type filetype"
# f = обычный файл
# d = директория
# l = символическая ссылка, и т.д.
#
# (Справочные и информационные страницы 'find' содержат список опций.)
271
# Там указаны лишние. Они быть отфильтрованы?
# Возможно:
#!/bin/bash
# [Link]
# Удаление файлов в текущей директории содержащих плохие символы.
for filename in *
do
badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# badname=`echo "$filename" | sed -n '/[+{;"\=?~()<>&*|$]/p'` то же работает.
# Удаляем файлы, содержащие такие гадости: + { ; " \ = ? ~ ( ) < > & * | $
rm $badname 2>/dev/null
# ^^^^^^^^^^^ Убирает сообщение об ошибке.
done
exit 0
#---------------------------------------------------------------------
# Команды ниже этой строки не будет выполнены, т.к. они после команды _exit_.
# (Спасибо S.C.)
272
#!/bin/bash
# [Link]: Удаление файла по номеру его inode.
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` filename"
exit $E_WRONGARGS
fi
if [ ! -e "$1" ]
then
echo "Файл \""$1"\" не существует."
exit $E_FILE_NOT_EXIST
fi
echo; echo -n "Вы точно уверены, что хотите удалить\"$1\" (да/нет)? "
# Опция '-v' в 'rm' так же задает этот вопрос.
read answer
case "$answer" in
[nN]) echo "Передумали?"
exit $E_CHANGED_MIND
;;
*) echo "Удаляется файл \"$1\".";;
esac
exit 0
#!/bin/bash
# Ищем suid файлы root.
# Файл suid может указывать на дыру в безопасности,
#+ или даже на вторжение в систему.
directory="/usr/sbin"
# Можно так же попробовать /sbin, /bin, /usr/bin, /usr/local/bin, и т.п.
273
permissions="+4000" # suid root (Опасно!)
См. Пример 16-30, Пример 3-4 и Пример 11-10 сценариев использующих find. Ее
справочная страница обеспечивает детальное изучение этой сложной и мощной команды.
xargs
Фильтр для снабжения команды аргументами, а также инструмент для сборки самих
команд. Она разбивает поток данных на небольшие куски для обработки фильтрами и
командами. Считается, что это мощная замена для обратных кавычек. В ситуациях,
когда при подстановке команд происходит сбой, с ошибкой "слишком много
аргументов", подстановка xargs часто работает. [1] Обычно xargs читает из потока
stdin или из конвейера, но она также может задаваться выводом из файла.
По умолчанию командой для xargs является 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...
274
/usr/bin/foomatic-ppd-options: выполняемый текст сценария perl
...
Или:
cat /proc/"$pid"/"$OPTION" | xargs -0 echo
# Форматирование вывода: ^^^^^^^^^^^^^^^
# Из исправленного сценария Han Holl' "[Link]"
#+ в главе "/dev и /proc".
#!/bin/bash
# Опции:
# =======
# -t Вывод команды в stderr.
# -n1 Более 1 аргумента командной строки.
# -P2 Запуск двух процессов одновременно.
#!/bin/bash
275
# из окончания файла /var/log/messages.
LINES=5
exit 0
# Примачание:
# ----
# Как поясняет Frank Wang,
#+ непарные кавычки (одинарные или двойные кавычки) в исходном файле
#+ могут не перевариться xargs.
#
# Он предлагает следующее дополнение в строке 15:
# tail -n $LINES /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile
# Упражнение:
# --------
# Измените этот сценарий для отслеживания изменений в /var/log/messages
#+ с интервалом в 20 минут.
# Подсказка: С помощью команды "watch".
Как и в find, пара фигурных скобок выступает в качестве заполнителя заменяющей текст.
#!/bin/bash
# [Link]
E_NOARGS=85
ls . | xargs -i -t cp ./{} $1
# ^^ ^^ ^^
# -t опция "подробно" (выводит командную строку в stderr).
# -i опция "замена строк".
# {} является заполнителем вместо выводимого текста.
# Это похоже на использование пары фигурных скобок в "find."
#
276
# Список файлов в текущей директории (ls .),
#+ вывод "ls" передавается как аргумент в "xargs" (опции -i -t),
#+ затем копируются (cp) эти аргументы ({}) в новую директорию ($1).
#
# Конечный результат является точным эквивалентом
#+ cp * $1
#+ за исключением файлов со встроенными символами «пробел».
exit 0
#!/bin/bash
# [Link]: Уничтожение процесса по имени.
# Сравните этот сценарий с [Link].
# Например,
#+ попробуйте "./[Link] xterm" --
#+ и посмотрите все ли xterm исчезли на рабочем столе.
# Предупреждение:
# -------
# Это очень опасный сценарий.
# Небрежный запуск (особенно из-под root) может привести к потере
#+ данных и другим нежелательным эффектам.
E_BADARGS=66
PROCESS_NAME="$1"
ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
# ^^ ^^
# ---------------------------------------------------------------
# Примечания:
# -i опция "замена строк" xargs.
# {} заполнитель вместо заменяемого.
# 2&>/dev/null подавляет нежелательные сообщения об ошибках.
#
# Можно ли grep "$PROCESS_NAME" заменить на pidof "$PROCESS_NAME"?
# ---------------------------------------------------------------
exit $?
# -n1 Более 1 аргумента командной строки.
# Команда "killall" имеет тот же эффект, что и этот сценарий,
#+ но ее использование не так поучительно.
277
#!/bin/bash
# [Link]: Частотный анализ не обработанных слов в текстовом файле.
if [ $# -ne "$ARGS" ]
# Сценарию передается правильное число аргументов?
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
#####################################################
cat "$1" | xargs -n1 | \
# Список файлов, одно слово на строке.
tr A-Z a-z | \
# Смещаем символы в нижний регистр.
sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\
/g' | \
# Фильтруем промежутки и запятые, и заменяем
#+ пространства между словами переводом строки,
sort | uniq -c | sort -nr
# Наконец удаляем дубликаты, считаем префиксы и сортируем численно.
#####################################################
exit $?
expr
expr 3 + 5
возвращает 8
expr 5 % 3
278
возвращает 2
expr 1 / 0
expr 5 \* 3
возвращает 15
y=`expr $y + 1`
#!/bin/bash
# Использование 'expr'
# =======================================
echo
# Арифметические операции
# ---------- ---------
a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(приращение переменной)"
a=`expr 5 % 3`
# модуль
echo
echo "5 mod 3 = $a"
echo
echo
279
# Логические операции
# ------- ---------
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 \<= 3`
echo "Если a <= 3, b = 1 (истинно)"
echo "b = $b" # 1 ( 3 -le 3 )
# То же, что и оператор "\>=" (больше или равно).
echo
echo
# Строковые операции
# ------ ---------
a=1234zipper43231
echo "Строка являющаяся \"$a\"."
280
и величиной 6 символов это \"$b\"."
echo
exit 0
#!/bin/bash
echo
echo "Строковые операции использующие конструкцию \"expr \$string : \""
echo "==================================================="
echo
a=1234zipper5FLIPPER43231
# ***************************
#+ Экранированные скобки
#+ соответствия substring
# ***************************
echo
281
# == ==
echo "Первые 7 символов \"$a\" это `expr "$a" : '\(.......\)'`."
# ===== == ==
# Снова, экранированные круглые скобки принудительно
#+ проверяют совпадения substring.
#
echo "Последние 7 символов \"$a\" это `expr "$a" : '.*\(.......\)'`."
# ========= конец строки оператора ^^
# (На самом деле, означает пропустить один или несколько любых
#+ символов до указанной существующей substring.)
echo
exit 0
Perl и sed, awk намного превосходят в анализе объектов строки. Короткие «подпрограммы»
sed или awk внутри сценария (см. раздел 36.2) являются более привлекательной
альтернативой expr.
См. Раздел 10.1, что бы подробнее узнать об использовании expr в строковых операциях.
Примечания
[1] И даже когда xargs не является строго необходимым, он может ускорить выполнение
команды, связанной с пакетной обработкой нескольких файлов.
Просто вызываемая date выводит дату и время в stdout. Эта команда становится
интересна в форматировании и анализе опций.
282
Пример 16-10. Использование date
#!/bin/bash
# Работа команды 'date'
prefix=temp
suffix=$(date +%s) # Опция "+%s" в'date' является GNU-специфичной.
filename=$prefix.$suffix
echo "Временное файловое имя = $filename"
# Хорошо для создания 'уникальных и случайных' имен файлов из времени,
#+ даже лучше, чем использование $$.
exit 0
bash$ date
Fri Mar 29 21:07:39 MST 2002
bash$ date -u
Sat Mar 30 04:07:42 UTC 2002
#!/bin/bash
# [Link]
# Автор: Nathan Coulter
# Используется в ABS Guide с разрешения (спасибо!).
diff () {
printf '%s' $(( $(date -u -d"$TARGET" +%s) -
$(date -u -d"$CURRENT" +%s)))
# %d = день месяца.
}
283
TARGET=$(date -u -d'2007-12-25 12:30:00' '+%F %T.%N %Z')
# %F = полная дата, %T = %H:%M:%S, %N = наносекунды, %Z = часовой пояс.
# Упражнение:
# --------
# Перепишите функцию diff() для принятия передаваемых ей параметров,
#+ вместо использования глобальных переменных.
Команда date имеет ряд параметров вывода. Например %N наносекундные порции текущего
времени. Одним из интересных использований этого является генерация случайных целых
чисел.
date +%N | sed -e 's/000$//' -e 's/^0//'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Удаляются начальные и конечные нули, если они есть.
# Длина создаваемого целого числа зависит от количества удаленных нулей.
# 115281032
# 63408725
# 394504284
date +%j
# Выводит на экран порядковый день года (дней, прошедших с 1 января).
date +%k%M
# Выводит на экран часы и минуты в 24-часовом формате, в одной цифровой строке.
284
OneYearAgo=$(date --date='1 год назад')
zdump
time
real 0m0.067s
user 0m0.004s
sys 0m0.005s
touch
285
spreadsheet*.data для защиты этих файлов от перезаписи файлами
с теми же именами во время cp -u
/home/bozo/financial_info/spreadsheet*data
/home/bozo/tax_audit.
at
batch
Команда управления заданием batch похожа на at, но она запускает список команд,
понижая нагрузку на систему. Как at, она может читать команды из файла при
помощи опции -f.
cal
sleep
286
sleep 3 # Пауза 3 секунды.
usleep
hwclock, clock
287
tsort
uniq
288
#!/bin/bash
# [Link]: Частотный анализ не обработанного слова в текстовом файле.
# Это более эффективная версия сценария «[Link]».
########################################################
# main ()
sed -e 's/\.//g' -e 's/\,//g' -e 's/ /\
/g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
# =========================
# Частота встречаемости
exit 0
# Упражнения:
# ---------
# 1) Добавьте команды 'sed' для фильтрации иных знаков пунктуации,
#+ таких как точка с запятой.
# 2) Измените сценарий для фильтрации множественных
#+ пробелов и других пробелов.
289
Эта строка встречается только один раз.
Эта строка встречается дважды
Эта строка встречается дважды
Эта строка встречается трижды
Эта строка встречается трижды
Эта строка встречается трижды
expand, unexpand
cut
FILENAME=/etc/passwd
290
for user in $(cut -d: -f1 $FILENAME)
do
echo $user
done
cut -d ' ' -f2,3 filename эквивалентно awk -F'[ ]' '{ print $2, $3
}' filename
paste
join
291
Команда join работает ровно с двумя файлами, но вставляет вместе только строки с
тегами общих полей (обычно числовая метка) и записывает результат в stdout. Файлы
для объединения должны быть отсортированы в соответствии совпадающим тегам
полей.
File: [Link]
100 Shoes
200 Laces
300 Socks
File: [Link]
100 $40.00
200 $1.00
300 $2.00
head
#!/bin/bash
# [Link]: Обнаружение сценариев в директории.
292
done
exit 0
# Упражнения:
# ---------
# 1) Измените этот сценарий, чтобы, для проверки наличия
#+ в ней сценариев (а не только в текущей рабочей директории),принимать
#+ в качестве необязательного аргумента директорию.
#
# 2) Как его остановить, если сценарий будет «ложно срабатывать»
#+ в сценариях на Perl, awk и других языках сценариев.
# Исправьте этот момент.
#!/bin/bash
# [Link]: Вывод 10-значных случайных чисел
# =================================================================== #
# Анализ
# --------
# head:
# опция -c4 принимает первые 4 байта.
# od:
# Опция -N4 ограничивает вывод 4 байтами.
# Опция -tu4 выбирает десятичный формат вывода без знака.
# sed:
# Опция -n, в сочетании с флагом "p" команды "s",
# выводит только совпадающие строки.
293
# (здесь "p" это флаг команды "s", отличающийся действием
#+ от команды "p").
# Теперь sed готов продолжить чтение входных данных. (Обратите внимание, что
#+ до продолжения, если опция -n не была передана, sed выведет строку
#+ еще раз).
# Далее, sed считывает оставшиеся символы, и ищет
#+ конец файла.
# И только теперь он готов к обработке 2-й строки (которая пронумерована '$'
#+ как завершающая, последняя).
# Он видит не соответствие ее <диапазону>, потому и завершает задачу.
# Несколько слов о значениях команд sed:
# «Только в первой строке, удаление любого символа до пробела справа, потом
#+ его вывод.»
# диапазон действие
# ничего (совпадающие строки) s/.* //
# ничего (совпадающие строки) q (выход)
# =================================================================== #
exit
tail
#!/bin/bash
filename=[Link]
294
echo "$filename содержит последние строки системного журнала."
exit 0
grep
Поиск в указанном файле(ах) данных соответствующих шаблону, где шаблоном может быть
обычный текст или регулярное выражение.
295
Опция -w ищет соответствия только целым словам.
# grep -cz .
# ^ точка
# означает подсчет(-c) совпадающих элементов ".", разделенных нулями (-z)
# то есть, не пустые (содержащие по крайней мере 1 символ).
#
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz . # 3
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) является отдельным
#+ элементом соответствия.
# Спасибо, S.C.
Опция --color (или --colour) выделяет соответствующую строку цветом (в консоли или
в окне xterm). Так как grep выводит каждую строку, содержащую соответствие шаблону, то
это позволяет увидеть в чем именно соответствие. См. Так же опцию -o, которая показывает
только совпадающий участок строки.
#!/bin/bash
# [Link]
296
# Эмуляция полезной утилиты 'from' в Solaris, BSD, и т.п..
# "From" выводит строки заголовков всех сообщений e-mail вашей директории.
exit $?
При вызове с более чем одним заданным целевым файлом, grep определяет, какой файл
содержит совпадения.
Если совпадение успешно, grep возвращает статус выхода 0, что делает возможным его
нахождение в условии проверки в сценарии, особенно в сочетании с параметром -q,
подавляющем вывод.
SUCCESS=0 # Если поиск grep успешен
word=Linux
filename=[Link]
297
# if grep -q "$word" "$filename" может заменить строки 5 - 7.
then
echo "$word найдено в $filename"
else
echo "$word не найдено в $filename"
fi
Пример 32-6 демонстрирует использование grep для поиска слова по шаблону в системном
файле журнала.
#!/bin/bash
# [Link]: Простая повторная реализация grep.
E_BADARGS=85
echo
echo
done
echo
exit 0
# Упражнения:
# ---------
# 1) Добавьте в вывод перевод строк, если в любом заданном файле будет найдено
#+ более чем одно совпадение.
# 2) Добавьте функциональности.
Может ли grep осуществлять поиск по двум (или более) отдельным шаблонам? Что делать,
если нужно с помощью grep вывести все строки, содержащие «pattern1» и «pattern2»,
в файле или файлах, ?
298
Одним из способов является передача конвейером результата вывода grep pattern1 в
grep pattern2.
# Filename: tstfile
Теперь давайте искать в этом файле строки одновременно содержащие «file» и «text»...
#!/bin/bash
# [Link]
# На самом деле является оберткой для однострочника (строки 46).
E_NOPATT=71
DICT=/usr/share/dict/[Link]
# ^^^^^^^^ Это источник для поиска списка слов.
# Словарь ASCII, одно слово на строке.
# Если будет нужен соответствующий словарь,
#+ загрузите пакет словаря автора "yawl".
# [Link]
# или
# [Link]
299
then #+ как аргумент командной строки ...
echo #+ . . . то . . .
echo "Usage:" #+ Сообщение об использовании.
echo
echo ""$0" \"шаблон,\""
echo "где \"шаблон\" в форме"
echo "xxx..x.x..."
echo
echo "x'ы это известные буквы,"
echo "а пропуски — неизвестные буквы (пробелы)."
echo "Буквы и пробелы могут быть в любой позиции."
echo "Например, попробуйте: sh [Link] w...i....n"
echo
exit $E_NOPATT
fi
echo
# ===============================================
# Здесь делается вся работа.
grep ^"$1"$ "$DICT" # Да, только одна строка!
# | |
# ^ это обозначение начала слова регулярного выражения.
# $ это обозначение окончания слова регулярного выражения.
# ===============================================
echo
$ sh [Link] w...i....n
wellington
workingman
workingmen
egrep — extended (расширенная) grep — то же самое, что и grep -E. Она использует
несколько иной, расширенный, набор регулярных выражений, которые позволяют
производить поиск более гибко. Она допускает использование логического оператора |
(ИЛИ).
bash $ egrep 'matches|Matches' [Link]
Строка 1 matches.
Строка 3 Matches.
Строка 4 содержит matches, а так же Matches
fgrep — fast (быстрая) grep — то же самое, что и grep -F. Это строка посимвольного
поиска (без регулярных выражений), которая обычно ускоряет поиск.
300
опциями -E и -F, соответственно.
#!/bin/bash
# [Link]
E_BADARGS=85
MAXCONTEXTLINES=50 # Максимально выводимое количество строк.
DEFAULT_DICTFILE="/usr/share/dict/[Link]"
# Путь к файлу словаря по умолчанию.
# При необходимости измените.
# Примечания:
# ----
# Данный выпуск 1913 Вебстер начинает каждую запись с прописной
#+ буквы (остальные символы в нижнем регистре).
# Только *начальная строка* записи начинается таким образом,
#+ и, именно поэтому работает алгоритм поиска ниже.
# ---------------------------------------------------------
Definition=$(fgrep -A $MAXCONTEXTLINES "$1 \\" "$dictfile")
301
# Определения в форме "Слово \..."
#
# И, да, "fgrep" достаточно быстр для поискать
#+ даже в очень большом текстовом файле.
echo "$Definition" |
sed -n '1,/^[A-Z]/p' |
# Вывод, из первой строки вывода, в первую строку следующей записи
sed '$d' | sed '$d'
# Удаляем две последние строки вывода
#+ (пустая строка и первая строка следующей записи).
# ---------------------------------------------------------
exit $?
# Упражнения:
# ---------
# 1) Измените сценарий так, чтобы принимать ввод букв любого
# + типа (прописные, строчные, смешанный регистр) и преобразовывать
# + их в приемлемый для обработки формат.
#
# 2) Конвертируйте сценарий в приложение GUI,
# + используя что-то типа 'gdialog' или 'zenity' ...
# + Что бы сценарий больше не принимал аргументы из командной строки.
#
# 3) Измените сценарий для анализа одного из других доступных словарей
# + Public Domain, например U.S. Census Bureau Gazetteer.
look
Команда look работает как grep, но осуществляет поиск с сортировкой списка слов
302
по «словарю». По умолчанию, look ищет совпадения в /usr/dict/words, но могут
быть указаны и другие словари.
#!/bin/bash
# поиск: Поиск по словарю каждого слова в данных файла.
echo
echo "Проверка файла $file"
echo
if [ "$lookup" -eq 0 ]
then
echo "\"$word\" правильное."
else
echo "\"$word\" не правильное."
fi
echo
exit 0
# ----------------------------------------------------------------
# Код строк ниже не будет выполняться из-за команды 'exit' выше.
exit 0
sed, awk
303
sed
awk
wc
# Другой способ:
# find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
# (shopt -s nullglob; set -- *.txt; echo $#)
# Спасибо S.C.
Передача в total, с помощью wc, размера всех файлов, чьи имена начинаются с букв в
диапазоне d - h
bash$ wc [d-h]* | grep total | awk '{print $3}'
71832
Использование wc для подсчета количества слов «Linux» в основном исходном файле этой
книги.
304
bash$ grep Linux [Link] | wc -l
138
# Спасибо S.C.
tr
tr -d 0-9 <filename
# Удаляет все цифры из файла "filename".
Опция --squeeze-repeats (или -s) удалит все, кроме первого знака строки
последовательных символов. Эта опция полезна для удаления лишних пробелов.
bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
X
305
bash$ echo "acfdeb123" | tr -c b-d +
+c+d+b++++
#!/bin/bash
# Изменяет имя файла в верхний регистр.
E_BADARGS=85
exit 0
# Упражнение:
# Перепишите этот сценарий, чтобы можно было изменять
#+ файл *либо* в верхний, либо в нижний регистр.
# Подсказка: Используйте команды "case" или "select".
Пример 16-22. lowercase: Изменение имен всех файлов в рабочей директории в нижний
регистр.
#!/bin/bash
#
# Меняет имя каждого файла в рабочей директории в нижний регистр.
#
# Вдохновлено сценарием John Dubois,
#+ который Chet Ramey перенес на Bash,
#+ и значительно упрощен автором ABS Guide.
306
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 $?
exit $?
#!/bin/bash
# [Link]: конвертер текстовых файлов DOS в UNIX.
E_WRONGARGS=85
if [ -z "$1" ]
then
echo "Usage: `basename $0` имя_файла_для_ конвертации"
exit $E_WRONGARGS
fi
NEWFILENAME=$[Link]
307
tr -d $CR < $1 > $NEWFILENAME
# Удаляются CR-ы и пишутся в новый файл.
exit 0
# Упражнение:
# --------
# Измените этот сценарий для конвертации из UNIX в DOS.
#!/bin/bash
# [Link]: Классический алгоритм rot13,
# стойкое шифрование 3-летней давности
# вскрываемое сейчас за 10 минут.
cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M' # "a" переходит в "n", "b" в "o" ...
# Конструкция cat "$@"
#+ допускает ввод из stdin или из файлов.
exit 0
#!/bin/bash
# [Link]: Шифрование цитат
key=ETAOINSHRDLUBCFGJMQPVWZYXK
# "key" это не более, чем перемешанный алфавит.
# Изменение "key" изменяет шифрование.
308
# --Марка Твена
#
# Выводится:
# "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."
# Расшифровка:
# cat "$@" | tr "$key" "A-Z"
exit 0
# Упражнение:
# --------
# Измените сценарий так, что бы он либо шифровал либо расшифровывал,
#+ в зависимости от аргументов командной строки.
#!/bin/bash
# [Link]
x="wftedskaebjgdBstbdbsmnjgz"
echo $x | tr "a-z" 'oh, turtleneck Phrase Jar!'
Варианты tr
Утилита tr имеет два исторических варианта. В версии BSD не используются скобки (tr
a-z A-Z), а в SysV они применяются (tr '[a-z]' '[A-Z]'). Версия tr GNU
напоминает версию BSD.
fold
fmt
#!/bin/bash
309
WIDTH=40 # 40 колонок в ширину.
# А можно и так
# echo $b | fold - -s -w $WIDTH
exit 0
col
column
#!/bin/bash
# [Link]
# Незначительно измененный пример файла справочной страницы «column».
(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \
; ls -l | sed 1d) | column -t
# ^^^^^^ ^^
exit 0
colrm
310
Фильтр для удаления колонок. Удаляет колонки (символов) из файла и записывает файл,
с отсутствующим рядом указанных столбцов, обратно в stdout. colrm 2 4
<filename удаляет со второго по четвертый символы из каждой строки текстового
файла filename.
nl
Вывод nl очень похож на cat -b, поскольку, по умолчанию, nl не выводит пустые строки.
#!/bin/bash
# [Link]
Echo " номер строки = $LINENO" # 'nl' понимает это как строку 4
# (nl не нумерует пустые строки).
# 'cat -n' понимает правильно, как строку #6.
nl `basename $0`
echo; echo # Теперь давайте попробуем с 'cat -n'
exit 0
# -----------------------------------------------------------------
pr
311
pr -o 5 --width=65 fileZZZ | more выводит прекрасный постраничный
список fileZZZ на экран с границами 5 и 65.
Особенно полезна опция -d, принудительных двойных пробелов (эффект подобен sed
-G).
gettext
GNU пакет gettext является набором утилит для локализации и перевода текста,
выводимого программами, на иностранные языки. Будучи изначально предназначенным
для программ Си, теперь он поддерживает целый ряд языков программирования и
сценариев.
msgfmt
iconv
recode
TeX, gs
TeX и Postscript — это языки разметки текста, используемые при подготовке копий для
печати или форматированного вывода на дисплей.
TeX - система печати разработанная Donald Knuth. Часто бывает удобно написать
312
сценарий оболочки, содержащий все параметры и аргументы, передаваемые одному из
этих языков разметки.
texexec
Утилита для обработки TeX файлов pdf. Во многих дистрибутивах Linux находится в
/usr/bin, является, на самом деле, оберткой оболочки, которая вызывает Perl для
вызова Tex.
enscript
Утилита обработки формул eqn является также частью groff, и ее функция заключается в
преобразовании разметки уравнения в команды groff.
#!/bin/bash
# [Link]: Форматирование исходных справочных страниц для просмотра.
E_WRONGARGS=85
313
if [ -z "$1" ]
then
echo "Usage: `basename $0` файл"
exit $E_WRONGARGS
fi
# ---------------------------
groff -Tascii -man $1 | less
# Из man page для groff.
# ---------------------------
lex, yacc
Примечания
[1] Это верно только для GNU версии tr, а не универсальной версии, часто встречающейся
на коммерческих системах UNIX.
314
была исправлена, с целью использования различных фильтров сжатия, например: tar
czvf archive_name.[Link] *, рекурсивно архивирует и сжимает (gzip) все
файлы дерева директории, за исключением файлов с точкой, текущей рабочей
директории ($PWD). [2]
7. -u обновление архива
shar
Утилита Shell archiving. В архив оболочки текст или двоичные файлы объединяются
без сжатия и полученный архив является по существу сценарием оболочки, с
заголовком #!/ bin/sh, содержащим все необходимые команды разархивирования, а
также сами файлы. Непечатные двоичные символы в указанных файле(ах)
преобразуются, в выходном файле shar, в печатные символы ASCII. Команда unshar
распаковывает архивы shar.
315
Команда mailshar является сценарием Bash использующим shar для объединения
нескольких файлов в один для e-mail. Этот сценарий поддерживает сжатие и
uuencoding.
ar
rpm
Пакетный менеджер Red Hat, или утилита rpm предоставляет оболочку для исходных
или бинарных архивов. Среди прочего она включает в себя команды для установки и
проверки целостности пакетов.
316
cpio
#!/bin/bash
ARGS=2
E_BADARGS=65
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` указанный источник"
exit $E_BADARGS
fi
source="$1"
destination="$2"
###################################################################
find "$source" -depth | cpio -admvp "$destination"
# ^^^^^ ^^^^^
# Читайте справочные страницы 'find' и 'cpio' для
#+ расшифровки этих опций.
# Работает только по отношению к $PWD (текущей директории)...
#+ или укажите полный путь.
###################################################################
# Упражнение:
# --------
exit $?
317
rpm2cpio
#!/bin/bash
# [Link]: Распаковка 'rpm' архива
exit 0
# Упражнение:
# Добавьте проверку на 1) существование "целевого_файла" и
#+ 2) архива rpm.
# Подсказка: Проверка вывода командой 'file'.
pax
pax -f daily_backup.pax
# Список файлов архива
Сжатие
gzip
318
Стандартная утилита сжатия GNU/UNIX, заменившая низкоуровневую и
проприетарную compress. Соответствующая команда распаковывания - gunzip
эквивалентна gzip -d.
bzip2
Другая утилита сжатия, обычно более эффективная (но более медленная), чем gzip,
особенно с большими файлами. Соответствующая команда распаковки это bunzip2.
compress, uncompress
sq
Еще одна утилита сжатия (squeeze), фильтр, который осуществляет сортировку списков
слов только ASCII. Использует обычный синтаксис для фильтра, sq < input-file
> output-file. Быстрая, но не такая эффективная как gzip. Фильтр для распаковки,
соответственно unsq, вызываемый как [Link]
319
Вывод sq может быть передан конвейером в gzip для дальнейшего
сжатия.
zip, unzip
Файловая информация
file
Опция -f указывает file запуск чтения из указанного файла для анализа списка имен
файлов в пакетном режиме. Опция -z, при использовании сжатого целевого файла,
пытается принудительно анализировать тип несжатого файла.
DIRECTORY=/usr/local/bin
320
KEYWORD=Bourne
# Bourne и Bourne-Again сценарии оболочки
# Выводится:
#!/bin/bash
# [Link]: Удаление комментариев (/* COMMENT */) в программе Си.
E_NOARGS=0
E_ARGERROR=66
E_WRONG_FILE_TYPE=67
if [ $# -eq "$E_NOARGS" ]
then
echo "Usage: `basename $0` файл-программы-Си" >&2 # Сообщения об ошибке в
#+ stderr.
exit $E_ARGERROR
fi
if [ "$type" != "$correct_type" ]
then
echo
echo "Этот сценарий работает только с файлами программ Си."
echo
exit $E_WRONG_FILE_TYPE
fi
321
# Нужно добавить еще одну строку в сценарий sed для рассмотрения случая,
#+ когда строка кода имеет комментарий после него на той же строке.
# Это оставим как нетривиальное упражнение.
exit 0
# ----------------------------------------------------------------
# Код ниже этой строки не будет выполнен из-за 'exit 0' выше.
usage() {
echo "Usage: `basename $0` файл_программы_Си" >&2
exit 1
}
exit 0
which
Команда which выводит полный путь к "команде." Это полезно для выяснения того,
установлены ли в системе определенные команды или утилиты.
$bash which rm
/usr/bin/r
whereis
322
Подобно which, выше, команда whereis выдает полный путь к "команде," а так же
путь к ее справочной странице.
$bash whereis rm
rm: /bin/rm /usr/share/man/man1/rm.1.bz2
whatis
команда whatis ищет "команду" в базе данных whatis. Она полезна для выявления
системных команд и важных конфигурационных файлов. Рассматривайте, как
упрощенную команду man.
#!/bin/bash
DIRECTORY="/usr/X11R6/bin"
# Попробуйте так же "/bin", "/usr/bin", "/usr/local/bin", и т.д.
exit 0
vdir
bash$ vdir
total 10
323
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 [Link]
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 [Link]
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 [Link]
bash ls -l
total 10
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 [Link]
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 [Link]
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 [Link]
locate, slocate
Команда locate выполняет поиск файлов, с помощью сохраняемой, только для этой
цели, базы данных. Команда slocate это безопасная версия locate (которая может
быть алиасом slocate).
getfacl, setfacl
Команды извлечения или установки управляющие списком доступа к файлу ( file access
control list)--владельца, группы и прав доступа к файлу.
bash$ getfacl *
# file: [Link]
# owner: bozo
# group: bozgrp
user::rw-
group::rw-
other::r--
# file: [Link]
# owner: bozo
# group: bozgrp
user::rw-
group::rw-
other::r--
324
group::rw-
mask::rw-
other::r--
readlink
strings
#!/bin/bash
# [Link]: "word-strings" (расширение команды "strings")
#
# Этот сценарий фильтрует вывод «strings», сверяя его с файлом словаря.
# Эффективно устраняет бред и шум, и выводит только слова.
# ===========================================================
# Обычная проверка для аргументов сценария
ARGS=1
E_BADARGS=85
E_NOFILE=86
if [ $# -ne $ARGS ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
325
WORDFILE=/usr/share/dict/[Link] # Файл словаря.
# Можно указать другой словарь в формате: одно слово на одной строке.
# Например, пакет словаря "yawl",
# [Link]
# ***********************************************************************
# Обратите внимание на технику передачи/конвейера вывода «tr» самой себе,
#+ но с разными аргументами и/или опциями в каждой последовательной передаче.
# ***********************************************************************
exit $?
Сравнение
diff, patch
326
отдельных столбцах, с отметкой не совпадающих строк. Опции -c и -u также делают
вывод команды легким для понимания.
Обычное использование diff - это вывод разницы файлов для использования с patch.
Опция -е выводит файлы подходящие для сценариев ed или ex.
patch: гибкая утилита версий. Указывая какой-то файл, созданный diff, patch
может обновить предыдущую версию пакета до новой версии. Гораздо удобней
распространять файл относительно небольшого «diff», чем пересматривать вновь все
тело пакета. «Патчи» ядра стали основным методом распространения частых релизов
ядра в Linux.
Патчинг ядра:
cd /usr/src
gzip -cd [Link] | patch -p0
# Обновление исходных текстов ядра, с помощью 'patch'.
# Из README, документации ядра Linux,
# анонимного автора (Alan Cox?).
327
С помощью diffstat создается гистограмма (график распределения
точек) вывода из diff.
diff3, merge
diff3 - это расширенная версия diff, которая сравнивает три файла за раз. Эта
команда возвращает статус выхода 0, после успешного выполнения, но к сожалению
это не дает никакой информации о результатах сравнения.
sdiff
cmp
Команда cmp это упрощенная версия diff, выше. В то время как diff сообщает
различия между двумя файлами, cmp просто показывает где они отличаются.
#!/bin/bash
# [Link]
328
E_UNREADABLE=86
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` file1 file2"
exit $E_BADARGS
fi
if [[ ! -r "$1" || ! -r "$2" ]]
then
echo "Оба сравниваемых файла должны существовать и быть доступными для
чтения."
exit $E_UNREADABLE
fi
exit 0
comm
• -1 подавляет колонку 1
329
• -2 подавляет колонку 2
• -3 подавляет колонку 3
Эта команда полезна для сравнения «словарей» или списков слов - сортированных
текстовых файлов с одним словом на каждой строке.
Утилиты
basename
Удаляет сведения из имени файла о пути, выводя только имя файла. Конструкция
basename $0 позволяет сценарию узнать свое имя, то есть имя, которым он был
вызван. Она может быть использована в сообщениях "usage", если, например, сценарий
вызывается без аргументов :
echo "Usage: `basename $0` arg1 arg2 ... argn"
dirname
#!/bin/bash
address=/home/bozo/[Link]
split, csplit
Это утилиты для разделения файла на более мелкие части. Они обычно используются
330
для разделения больших файлов при резервном копировании на дискеты или
подготовке электронной почты, или их загрузке.
#!/bin/bash
# [Link]
exit $?
Кодирование и шифрование
sum, cksum, md5sum, sha1sum
331
0f43eccea8f09e0a0b2b5cf1dcf333ba /boot/vmlinuz
Команда cksum показывает размер цели, в байтах, будь это файл или
stdout.
#!/bin/bash
# [Link]: Проверка, были ли подделаны файлы в заданной директории.
E_DIR_NOMATCH=80
E_BAD_DBFILE=81
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
# ------------------------------------------- #
directory_checked="${record[0]}"
if [ "$directory_checked" != "$directory" ]
then
echo "Директории не совпадают!"
332
# Попробуйте использовать файл другой директории.
exit $E_DIR_NOMATCH
fi
if [ "${record[n]}" = "${checksum[n]}" ]
then
echo "${filename[n]} без изменений."
fi
let "n+=1"
done <"$dbfile" # Чтение контрольной суммы файла базы данных.
# =================================================== #
# main ()
if [ -z "$1" ]
then
directory="$PWD" # Если не указана,
else #+ используется текущая рабочая директория.
directory="$1"
fi
# ------------------------------------------------------------------ #
if [ ! -r "$dbfile" ] # Нужно создать файл базы данных?
then
echo "Создание файла базы данных, \""$directory"/"$dbfile"\"."; echo
set_up_database
fi
# ------------------------------------------------------------------ #
333
echo
exit 0
Также посмотрите Пример A-19, Пример 36-16 и Пример 10-2 творческого использования
команды md5sum.
uuencode
Эта утилита кодирует двоичные файлы (изображения, звуковые файлы, сжатые файлы и
т.д.) в символы ASCII, что делает их пригодными для передачи в теле сообщения
электронной почты или в публикации новостей. Она особенно полезна, когда не
доступно кодирование MIME (мультимедиа).
uudecode
#!/bin/bash
# Кодирование uuencoded всех файлов в текущей рабочей директории.
334
# Uuencode файлы имеют «начало» в начале,
#+ и "окончание" около конца.
if [ "$search1" -gt 0 ]
then
if [ "$search2" -gt 0 ]
then
echo "uudecoding - $File -"
uudecode $File
fi
fi
done
# Упражнение:
# --------
# Измените этот сценарий для проверки заголовка каждого файла группы
#+ новостей и перехода к следующему, если заголовок не найден.
exit 0
mimencode, mmencode
crypt
В одно время это была стандартная утилита шифрования файлов UNIX. [5]
Политически мотивированные правительственные постановления, запрещающие
экспорт криптографического программного обеспечения, привели к исчезновению
crypt из огромного мира UNIX, и она до сих пор отсутствует в большинстве
дистрибутивов Linux. К счастью, программисты создали ряд достойных альтернатив,
среди них собственный cruft автора (см. Пример A-4).
openssl
335
-pass pass:мой_пароль
# ^^^^^^^^^^ Пароль пользователя.
# aes-128-ecb это выбор метода шифрования.
# Расшифровка файла:
openssl aes-128-ecb -d -salt -in [Link] -out [Link] \
-pass pass:мой_пароль
# ^^^^^^^^^^^ Пароль пользователя.
sourcedir="/home/bozo/testfiles"
encrfile="[Link]"
password=my_secret_password
shred
Надежное стирание файла, путем перезаписи его, перед удалением, несколько раз
случайными битами. Эта команда имеет тот же эффект, как Пример 16-61, но делает это
более тщательно и элегантно.
Разные
mktemp
Создает временный файл [6] с «уникальным» именем. При вызове из командной строки
без дополнительных аргументов создает файл нулевой длины в директории /tmp.
bash$ mktemp
/tmp/tmp.zzsvql3154
336
PREFIX=filename
tempfile=`mktemp $[Link]`
# ^^^^^^ Нужны, по крайней мере, 6 заполнителей
#+ в имени временного файла.
# Если нет шаблона файла, то
#+ "[Link]" по умолчанию.
make
install
dos2unix
ptx
more, less
Постраничный вывод текстового файла или потока в stdout, страница за раз. Могут
337
использоваться в фильтре вывода stdout . . . или сценария.
Примечания
[1] Архив, в обсуждаемом здесь смысле, это просто набор связанных файлов, хранящихся в
одном месте.
[3] Контрольная сумма может быть выражена, как шестнадцатеричное число или по
другим основаниям.
[5] Это симметричный блочный шифр, используемый для шифрования файлов на одной
системе или локальной сети, в отличие от класса шифрования общественного ключа, из
которого хорошо известным примером является pgp.
338
bash$ host [Link]
[Link]. Имеет адрес [Link]
ipcalc
nslookup
Non-authoritative answer:
Name: [Link]
dig
Domain Information Groper. Подобно nslookup, dig производит поиск имени хоста
сервера. Может запускаться из командной строки или из сценария.
Наиболее интересные опции dig это +time=N создает таймаут между запросами в N
секунд, +nofail для продолжения запроса серверам, пока не будет получен ответ, а -x
производит обратный поиск адреса.
339
;; QUESTION SECTION:
;[Link].[Link]. IN PTR
;; AUTHORITY SECTION:
[Link]. 3600 IN SOA [Link]. [Link].
2002031705 900 600 86400 3600
#!/bin/bash
# [Link]: Обнаружение спаммера злоупотребляющего соединением.
# Спасибо, Michael Zick.
# Упражнение:
# --------
# Расширьте функциональность этого сценария, таким образом,
#+ чтобы он автоматически отправлял по электронной почте
#+ уведомление контактным адресам ISP.
# Подсказка: с помощью команды "mail".
exit $?
# [Link] [Link]
# Известный спам домен.
# "crnet_mgr@[Link]"
# "crnet_tec@[Link]"
# "postmaster@[Link]"
340
# Более сложную версию этого сценария
#+ смотри на странице SpamViz, [Link]
#! /bin/bash
# [Link]: Выявление спам доменов
# is-spammer <[Link]>
# Функциональность.
# Использует IFS для анализа строк назначаемых в массивы.
# И даже делает что-то полезное: Проверяет черный список электронной почты.
341
echo $(dig +short $2 -t txt)
fi
}
server=${1}${2}
reply=$( dig +short ${server} )
# Смотрите: [Link]
echo -n ' [Link] выдает: '
echo $(chk_adr ${rev_dns} '[Link]')
342
# Смотрите: [Link] (Различная рассылка почты)
echo
echo 'Distributed Server Listings'
echo -n ' [Link] выдает: '
echo $(chk_adr ${rev_dns} '[Link]')
else
echo
echo 'Не используйте этот адрес.'
fi
exit 0
# Упражнение:
# --------
traceroute
ping
343
Передача пакета ICMP ECHO_REQUEST другой машине, в локальной или удаленной
сети. Это диагностический инструмент для проверки сетевого соединения и должен
использоваться с осторожностью.
whois
Выполняет поиск DNS (Domain Name System). Опция -h позволяет задать для запроса
конкретный whois-сервер. См. Пример 4-6 и Пример 16-40.
finger
bash$ finger
Login Name Tty Idle Login Time Office Office Phone
bozo Bozo Bozeman tty1 8 Jun 25 16:59 (:0)
bozo Bozo Bozeman ttyp0 Jun 25 16:59 (:0.0)
bozo Bozo Bozeman ttyp1 Jun 25 17:07 (:0.0)
344
Directory: /home/bozo Shell: /bin/bash
Office: 2355 Clown St., 543-1234
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
Mail last read Tue Jul 3 10:08 2007 (MST)
No Plan.
chfn
vrfy
Набор команд sx и rx служит для передачи файлов на/с удаленный хост с помощью
протокола xmodem. Они являются частью коммуникационных пакетов, таких как
minicom.
sz, rz
ftp
uucp, uux, cu
uucp: копирование с UNIX на UNIX. Это коммуникационный пакет для обмена файлами
между серверами UNIX. Сценарий оболочки является эффективным способом
обработки последовательности команд uucp.
345
С появлением Интернета и электронной почты, uucp, кажется, канула в безвестность,
но она по-прежнему существует и остается вполне работоспособной в ситуациях, когда
подключение к интернету не доступно или отсутствует. Преимуществом uucp является
отказоустойчивость, то есть, при восстановлении соединения после сбоя, операция
копирования будет возобновлена с места, где была прервана.
---
---
cu: вызов удаленной системы и подключение, как простого терминала. Это своего рода
упрощенная версия telnet. Эта команда так же является частью пакета uucp.
telnet
wget
Утилита wget не интерактивно извлекает или загружает файлы с Интернет или FTP-
сайта. Она прекрасно работает в сценариях.
wget -p [Link]
# Опция -p или --page-requisite заставляет wget извлекать все файлы,
#+ необходимые для вывода указанной страницы
wget -r [Link] -O $SAVEFILE
# Опция -r рекурсивно двигается и извлекает все ссылки на указанном сайте.
wget -c [Link]
# Опция -c позволяет wget возобновить прерванные закачки.
# Она работает с серверами ftp и множеством сайтов HTTP.
#!/bin/bash
# [Link]: Загрузка котировок акций.
E_NOPARAMS=86
346
fi
stock_symbol=$1
file_suffix=.html
# Извлечение HTML файла, а так же соответствующего ему имени.
URL='[Link]
# Финансовый таблоид Yahoo, с суффиксами запросов акций.
# -----------------------------------------------------------
wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
# -----------------------------------------------------------
# Поиск на [Link]
# -----------------------------------------------------------
# URL="[Link]
# wget -O "$savefilename" "${URL}"
# -----------------------------------------------------------
# Сохранение списка URL-адресов.
exit $?
# Упражнения:
# ---------
#
# 1) Добавьте проверку для запустившего сценарий пользователя,
№+ что он работает он-лайн. (Подсказка: Анализируйте вывод
#+ из 'ps-ax' для «ppp» или «connect».
# 2) Измените этот сценарий для получения прогноза погоды,
#+ беря, в качестве аргумента, почтовый индекс пользователя.
lynx
С опцией -traversal, lynx стартует с указанным HTTP URL в качестве аргумента, затем
«проходит» через все ссылки, расположенные на этом конкретном сервере. С помощью
опции -crawl, выводит текстовую страницу в лог-файл.
rlogin
Удаленный вход, инициация сеанса на удаленном хосте. Эта команда имеет дыры в
безопасности, так что вместо нее используют ssh.
rsh
347
Remote shell, выполняет команды на удаленном хосте. Она имеет дыры в
безопасности, поэтому вместо нее используют ssh.
rcp
rsync
#!/bin/bash
# [Link]
URL=rsync://[Link]/fedora-linux-core/updates/
# URL=rsync://[Link]/fedora/core/updates/
# URL=rsync://[Link]/fedora-linux-core/updates/
DEST=${1:-/var/www/html/fedora/updates/}
LOG=/tmp/repo-update-$(/bin/date +%Y-%m-%d).txt
PID_FILE=/var/run/${0##*/}.pid
348
# шаблон исключений rsync
# Временно закомментируйте нежелательные пакеты, используя "#" ...
EXCLUDE=(
/1
/2
/3
/testing
/4/SRPMS
/4/ppc
/4/x86_64
/4/i386/debug
"/4/i386/kde-i18n-*"
"/4/i386/[Link]-langpack-*"
"/4/i386/*[Link]"
"/4/i386/GFS-*"
"/4/i386/cman-*"
"/4/i386/dlm-*"
"/4/i386/gnbd-*"
"/4/i386/kernel-smp*"
# "/4/i386/kernel-xen*"
# "/4/i386/xen-*"
)
init () {
# Пусть команда pipe возвращает возможную ошибку rsync, например,
#+ падение скорости сети.
set -o pipefail # Нововведение в Bash, версии 3.
check_pid () {
# Проверка существования процесса.
if [ -s "$PID_FILE" ]; then
echo "Существование PID файла. Проверка ..."
PID=$(/bin/egrep -o "^[[:digit:]]+" $PID_FILE)
if /bin/ps --pid $PID &>/dev/null; then
echo "Процесс $PID существует. ${0##*/} возможно работает!"
/usr/bin/logger -t ${0##*/} \
"Процесс $PID существует. ${0##*/} возможно работает!"
exit $E_RETURN
fi
echo "Процесс $PID не существует. Запуск нового процесса ..."
fi
}
for p in "${EXCLUDE[@]}"; do
exclude="$exclude --exclude \"$p\""
349
done
}
350
# Находились в той же subshell (1) во время всего цикла.
echo "Загрузка..."
/bin/nice /usr/bin/rsync \
$OPTS \
--filter "merge,+/ $TMP" \
--exclude '*' \
$URL $DEST \
| /usr/bin/tee $LOG
RET=$?
echo "Сделано"
rm -f $PID_FILE 2>/dev/null
return $RET
}
# -------
# Main
init
check_pid
set_range
get_list
get_file
RET=$?
# -------
exit $RET
351
вместо них возможность использования ssh, scp или сценария expect.
ssh
#!/bin/bash
# [Link]: Использование ssh.
# Предположения:
# ------------
# fd-2 не захвачено ( '2>/dev/null' ).
# ssh/sshd предполагает, что stderr ('2') будет выводиться пользователю.
#
# sshd запускается на вашей машине.
# Для любого «обычного» дистрибутива это должно быть так,
#+ и делается без каких-либо самодельных генераторов ключей ssh.
ls -l
352
# Теперь запустите написанное выше в командной строке на удаленном хосте,
#+ с зашифрованной передачей.
exit 0
scp
Локальная сеть
write
Это утилита для связи типа терминал-терминал. Она позволяет отправлять строки с
вашего терминала (консоли или xterm) на терминал другого пользователя. Конечно, для
отключения доступа записи в терминале может быть использована команда mesg.
netconfig
Утилита командной строки для настройки сетевой адаптера (с помощью DHCP). Эта
команда является родной для дистрибутивов Red Hat Linux.
353
Почта
mail
Этот урезанный почтовый клиент для командной строки прекрасно работает в качестве
встраиваемой в сценарий команды.
#!/bin/sh
# [Link]: Сценарий самостоятельной рассылки
# ============================================================================
cat $0 | mail -s "Сценарий \"`basename $0`\" сам пошлет вам." "$adr"
# ============================================================================
# --------------------------------------------
# Поздравление от сценария самостоятельной рассылки.
# Озорник запустит этот сценарий и он сам пошлет вам сообщение.
# --------------------------------------------
mailto
Подобно команде mail, mailto отсылает e-mail сообщения из командной строки или
сценария. Однако mailto также может отправлять сообщения MIME (мультимедиа).
mailstats
Показывает почтовую статистику. Эта команда может быть вызвана только root.
354
root# mailstats
Statistics from Tue Jan 1 20:32:08 2008
M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis msgsqur Mailer
4 1682 24118K 0 0K 0 0 0 esmtp
9 212 640K 1894 25131K 0 0 0 local
=====================================================================
T 1894 24758K 1894 25131K 0 0 0
C 414 0
vacation
Примечания
[1] Демон (daemon) фоновый процесс не относящийся к терминальной сессии. Демоны
либо запускают назначенные службы в определенное время или явно вызываются
определенными событиями.
Ввод tput cup X Y выведет координаты курсора (X,Y) текущего терминала. clear
очистит экран терминала от уже введенной или выведенной информации.
355
• smso, обратное отображение текста
1. Пример 36-15
2. Пример 36-13
3. Пример A-44
4. Пример A-42
5. Пример 27-2
Обратите внимание, что stty предлагает более мощный набор команд для управления
терминалом.
infocmp
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, курсор и строка
приглашения появляются в верхней левой части терминала.
clear
Команда clear просто очищает экран консоли или xterm от текста. В углу верхней
левой части экрана или окна xterm появляются строка приглашения и курсор. Эта
команда может использоваться в командной строке или в сценарии. См. Пример 11-26.
resize
356
Выводит команды, необходимые для задания $TERM и $TERMCAP для дублирования
размера текущего терминала (размеры).
bash$ resize
set noglob;
setenv COLUMNS '80';
setenv LINES '24';
unset noglob;
script
#!/bin/bash
# [Link]
CEILING=10000 # От 1 до 10000
PRIME=0
E_NOTPRIME=
is_prime ()
{
local factors
factors=( $(factor $1) ) # Загрузка вывода `factor` в массив.
if [ -z "${factors[2]}" ]
# Третий элемент массива "factors":
#+ ${factors[2]} это аргумент 2-го фактора.
# Если он пуст, то есть нет 2-го фактора,
#+ то аргумент является простым.
then
return $PRIME # 0
else
return $E_NOTPRIME # null
fi
357
}
echo
for n in $(seq $CEILING)
do
if is_prime $n
then
printf %5d $n
fi # ^ Пяти позиций на число достаточно.
done # Для увеличения $CEILING, увеличьте потолок.
echo
exit
bc
Поскольку это довольно хорошо зарекомендовавшая себя утилита UNIX, то она может быть
использована в конвейере, получая данные из сценария.
#!/bin/bash
# [Link]: Подсчет ежемесячного платежа по ипотечному кредиту.
echo
echo "Учитывается основная, процентная ставка и срок ипотеки,"
echo "подсчитывается ежемесячный платеж."
bottom=1.0
echo
echo -n "Введите основную ставку (без запятых) "
read principal
echo -n "Введите процентную ставку (проценты) " # Если 12%, введите "12", а не
358
".12".
read interest_r
echo -n "Введите срок (месяцы) "
read term
# --------------------------------------------------------------------
# Rick Boivie указал на более эффективную реализацию вышеуказанного цикла,
#+ что уменьшает время вычислений на 2/3.
# 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 ' в подстановку команд.
# --------------------------------------------------------------------------
# С другой стороны Frank Wang предлагает:
# bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc)
359
# let "payment = $top/$bottom"
payment=$(echo "scale=2; $top/$bottom" | bc)
# Используется два десятичных знака для долларов и центов.
echo
echo "monthly payment = \$$payment" # Выводится перед суммой знак доллара.
echo
exit 0
# Упражнения:
# 1) Отфильтруйте разрешение ввода запятых в основной сумме.
# 2) Отфильтруйте разрешение ввода процентов как в процентной
# или как в десятичной форме.
# 3) Если вы действительно амбициозны, разверните этот сценарий
# для вывода таблицы полной амортизации.
#!/bin/bash
###########################################################################
# Shellscript: [Link] — вывод чисел по различным основаниям (Bourne Shell)
# Автор : Heiner Steven ([Link]@[Link])
# Date : 07-03-95
# Category : Desktop
# $Id: [Link],v 1.2 2000/02/06 19:55:35 heiner Exp $
# ==> Above line is RCS ID info.
###########################################################################
# Описание
#
# Изменения
# 21-03-95 stv fixed error occuring with 0xb as input (0.2)
###########################################################################
NOARGS=85
PN=`basename "$0"` # Имя программы
VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2
Usage () {
echo "$PN — вывод числа по различным основаниям, $VER (stv '95)
usage: $PN [число ...]*****
360
Msg () {
for i # ==> отсутствует in [list]. Почему?
do echo "$PN: $i" >&2
done
}bcbc
PrintBases () {
# Определение основания числа
for i # ==> отсутствует in [list]...
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
done
}
while [ $# -gt 0 ]
# ==> Этот "цикл while" здесь действительно необходим,
# ==>+ так как все case выводят из цикла
# ==>+ или завершают сценарий.
# ==> (Комментарий выше Paulo Marcel Coelho Aragao.)
do
case "$1" in
--) shift; break;;
-h) Usage;; # ==> Сообщение Справки.
-*) Usage;;
*) break;; # Первое число
361
esac # ==> Возможна соответствующая проверка ошибок неправильного ввода.
shift
done
if [ $# -gt 0 ]
then
PrintBases "$@"
else # Чтение из stdin.
while read line
do
PrintBases $line
done
fi
exit
#!/bin/bash
# Вызов 'bc' с помощью подстановки команд
# в комбинации с 'here document'.
362
v1=23.53
v2=17.881
v3=83.501
v4=171.63
exit 0
#!/bin/bash
# [Link]: Приближение PI при стрельбе ядрами.
363
#+ со стороной в 10000 единиц.
# Эта площадь имеет в центре совершенно круглое озеро,
#+ диаметром в 10000 единиц.
# На самом деле, участок занят в основном водой,
#+ за исключением земли по углам.
# (Представьте себе квадрат с вписанной в него окружностью).
declare -r M_PI=3.141592654
# 9-значное значение PI, для сравнения.
get_random ()
{
SEED=$(head -n 1 /dev/urandom | od -N 1 | awk '{ print $2 }')
RANDOM=$SEED # Из примера сценария
#+ "[Link]".
let "rnum = $RANDOM % $DIMENSION" # Диапазон меньше 10000.
echo $rnum
}
364
# ==========================================================
# main() {
# Блок кода "main", имитирует функцию main() языка Си.
# Инициализация переменных.
shots=0
splashes=0
thuds=0
Pi=0
error=0
done
echo
echo "После $shots выстрелов, PI похоже на $Pi"
# Как правило, немного больше, возможно из-за ошибок округления
#+ и несовершенной случайности $RANDOM.
# Но все-таки обычно в пределах плюс-минус 5%...
#+ довольно точно, для грубого приближения.
error=$(echo "scale=9; $Pi - $M_PI" | bc)
pct_error=$(echo "scale=2; 100.0 * $error / $M_PI" | bc)
echo -n "Отклонение от математического значения PI = $error"
echo " ($pct_error% error)"
echo
365
exit 0
dc
#!/bin/bash
# [Link]: Преобразование десятичного числа в шестнадцатеричное.
if [ -z "$1" ]
then # Необходим аргумент командной строки.
echo "Usage: $0 number"
exit $E_NOARGS
fi # Упражнение: добавьте проверку правильности аргумента.
hexcvt ()
{
if [ -z "$1" ]
then
echo 0
return # "Возвращает" 0, если аргумент не передан функции.
fi
366
# p выводит вершину стека.
# Другие опции: смотри 'man dc' ...
return
}
hexcvt "$1"
exit
#!/bin/bash
# [Link]: Множитель числа
if [ -z $1 ]
then
echo "Usage: $0 число"
exit $E_NOARGS
fi
367
# -------------------------------------------------------
# Код выше написан Michel Charpentier <charpov@[Link]>
# (однострочник, разбитый здесь на две строки).
# Используется в ABS Guide с разрешения (спасибо!).
exit
# $ sh [Link] 270138
# 2
# 3
# 11
# 4093
awk
#!/bin/bash
# [Link]: Возвращает "гипотенузу" правильного треугольника.
# (квадратный корень суммы квадратов двух катетов)
exit
368
jot, seq
По умолчанию разделителем между каждым целым числом является символ перевода строки,
но он может быть изменен параметром -s.
bash$ seq 5
1
2
3
4
5
bash$ seq -s : 5
1:2:3:4:5
#!/bin/bash
# Использование "seq"
echo
echo; echo
echo; echo
BEGIN=75
END=80
369
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
echo; echo
exit 0
Простой пример:
#!/bin/bash
# [Link]: Подсчет букв в текстовом файле.
# Написан Stefano Palmeri.
# Используется в ABS Guide с разрешения.
# Слегка изменен автором.
show_help(){
370
echo
echo Usage: `basename $0` буквы файла
echo Примечание: `basename $0` аргументы чувствительны к регистру.
echo Пример: `basename $0` [Link] G n U L i N U x.
echo
}
exit $?
371
getopt
#!/bin/bash
# Использование getopt
E_OPTERR=65
if [ "$#" -eq 0 ]
then # Сценарию необходим хотя бы один аргумент командной строки.
echo "Usage $0 -[опции a,b,c]"
exit $E_OPTERR
fi
while [ ! -z "$1" ]
do
case "$1" in
-a) echo "Опция \"a\"";;
-b) echo "Опция \"b\"";;
-c) echo "Опция \"c\"";;
-d) echo "Опция \"d\" $2";;
*) сброс;;
esac
shift
done
exit 0
372
Peggy Russell поясняет:
run-parts
yes
373
Эта «особенность» может использоваться для создания на лету очень
большого файла ASCII:
bash$ yes $PATH > huge_file.txt
Ctl-C
yes ()
{ # Тривиальная эмуляция "yes" ...
local DEFAULT_TEXT="y"
while [ true ] # Бесконечный цикл.
do
if [ -z "$1" ]
then
echo "$DEFAULT_TEXT"
else # Если аргумент ...
echo "$1" # ... расширяется и выводит его.
fi
done # Единственными отсутствующими вещами являются
} #+ опции --help и --version.
banner
Обратите внимание, что banner был исключен из многих дистрибутивов Linux, по-
видимому, потому что он больше не нужен.
printenv
lp
Команды lp и lpr отправляют файл (ы) для очередь для печати на принтере. [2]
Происхождение этих команд ясно из их названий line printers. [3]
374
bash$ lp [Link] или bash lp <[Link]
Связанные команды - lpq, для просмотра очереди печати, и lprm, для удаления
заданий из очереди на печать.
tee
(перенаправление)
|----> в файл
|
==========================|====================
команда ---> команда ---> |tee ---> команда ---> ---> вывод конвейера
===============================================
mkfifo
#!/bin/bash
# Короткий сценарий написанный Omair Eshkenazi.
# Используется в ABS Guide с разрешения (спасибо!).
375
mkfifo pipe1 # Да, каналу может быть присвоено имя.
mkfifo pipe2 # Таким образом обозначается «именованный канал.»
rm -f pipe1
rm -f pipe2
exit $?
[Link] BOZO
pipe1 BOZO
pipe2 BOZO
[Link] BOZO
[Link] BOZO
pathchk
Эта команда проверяет правильность имен файлов. Если имя файла превышает
максимально допустимую длину (255 символов), а так же путь поиска одной или
нескольких директорий, то выводится сообщение об ошибке.
dd
Эта команда, data duplicator, несколько туманна и очень страшна, возникла как утилита
для обмена данными на магнитных лентах между микрокомпьютерами UNIX и
мэйнфреймами IBM, она до сих пор используется. Команда dd просто копирует файл
(или stdin/stdout), но с некоторыми преобразованиями. Возможные преобразования
включают ASCII/EBCDIC, [5] верхний/нижний регистр, замену пары байтов между
входом и выходом, и пропуск и/или усечения заголовка или окончания входного файла.
376
• if=INFILE
• of=OUTFILE
• bs=BLOCKSIZE
• skip=BLOCKS
• seek=BLOCKS
• count=BLOCKS
• conv=CONVERSION
#!/bin/bash
# [Link]
file_subscript=copy
exit $?
377
Пример 16-58. Работа dd
#!/bin/bash
# [Link]
# ----------------------------------------------------------------
exit $?
#!/bin/bash
# [Link]: Захват нажатий клавиш без необходимости нажатия ENTER.
378
# Спасибо S.C.
dd if=kernel-image of=/dev/fd0H1440
dd if=/dev/fd0 of=/home/bozo/projects/[Link]
dd if=[Link] of=/dev/sdb
#!/bin/bash
# [Link]
# Подготовка карты SD с загрузочным образом для Raspberry Pi.
if [ -n "$1" ]
then
imagefile="$1"
else
imagefile="$DEFAULTif"
fi
if [ -n "$2" ]
then
sdcard="$2"
else
sdcard="$DEFAULTsdcard"
379
fi
if [ ! -e $imagefile ]
then
echo "Файл образа \"$imagefile\" не найден!"
exit $E_NOIMAGE
fi
exit $?
# Упражнения:
# ---------
# 1) Обеспечьте дополнительную проверку ошибок.
# 2) Создайте сценарий автоопределения устройства для SD-карты (сложно!).
# 3) Создайте сценарий автоопределения файла образа (*img) в $PWD.
Другие приложения с помощью dd создают временный своп (Пример 31-2) и RAM диск,
(Пример 31-3). Она даже может сделать копию на низком уровне целых разделов жесткого
диска, хотя это не рекомендуется.
#!/bin/bash
# [Link]: Удаление "всех" следов файла.
file=$1
380
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
echo
exit 0
381
# Углубленный анализ по теме удаления файлов и безопасности,
#+ см. статью Peter Gutmann,
#+ "Secure Deletion of Data From Magnetic and Solid-State Memory".
# [Link]
od
Фильтр od или octal dump, преобразует ввод (или файлы) в восьмеричное (по
основанию 8) или другое основание. Он полезен для просмотра или обработки файлов
двоичных данных или иных, нечитаемых, системных файлов устройств, таких как
/dev/urandom, а так же как фильтр для двоичных данных.
hexdump
objdump
080490bc <.init>:
80490bc: 55 push %ebp
80490bd: 89 e5 mov %esp,%ebp
...
mcookie
382
Эта команда создает «magic cookie,» 128-битное (32-х символьное) псевдослучайное
шестнадцатеричное число, обычно используемое X-сервером в качестве «подписи».
Она также доступна для использования в сценарии для «быстрого и чернового»
создания случайных чисел.
random000=$(mcookie)
Команда mcookie является еще одним способом создания «уникального» имени файла.
#!/bin/bash
# [Link]: Генератор временных имен файлов
suffix=${BASE_STR:POS:LEN}
# Извлекается 5-символьная строка,
#+ начинающаяся с позиции 11.
temp_filename=$prefix.$suffix
# Конструкция имени файла.
# sh [Link]
# Временное имя файла = temp.e19ea
exit 0
units
383
интерактивном режиме, units может найти применение в сценарии.
#!/bin/bash
# [Link]
# Нужно иметь установленную утилиту 'units'.
Unit1=мили
Unit2=метры
cfactor=`convert_units $Unit1 $Unit2`
quantity=3.73
exit 0
m4
#!/bin/bash
# [Link]: Использование процессора m4
# Строки
string=abcdA01
echo "len($string)" | m4 # 7
echo "substr($string,4)" | m4 # A01
echo "regexp($string,[0-1][0-1],\&Z)" | m4 # 01Z
# Вычисления
var=99
384
echo "incr($var)" | m4 # 100
echo "eval($var / 3)" | m4 # 33
exit
xmessage
zenity
doexec
dialog
385
Семейство инструментов dialog предоставляет способ вызова интерактивных
«диалоговых» окон из сценариев. Более сложные вариации dialog -- gdialog,
Xdialog, и kdialog -- на самом деле вызывают виджеты X-Windows .
sox
Примечания
[1] Этот сценарий адаптирован из дистрибутива Debian Linux.
[4] Прекрасный обзор этой темы в статье Andy Vaught, Introduction to Named Pipes,
сентябрь 1997 года, Linux Journal.
[5] EBCDIC это акроним Extended Binary Coded Decimal Interchange Code, формата данных
IBM. Не совсем обычное применение опции dd conv=ebcdic это быстрая и легкая, но
не очень безопасная кодировка текстового файла.
cat $file | dd conv=swab,ebcdic > $file_encrypted
# Кодирование (выглядит как бред).
# Можно переставить байты (swab), для дополнительного запутывания.
386
Часть 5. Дополнительные темы
На данный момент, мы уже готовы вникать в некоторые трудные и необычные аспекты
сценариев. Для этого мы попробуем различными способами 'выйти за рамки' и изучить
отдельные условия (что случится, когда мы зайдем на неизведанную территорию?).
Содержание
18. Регулярные выражения
18.1. Краткое введение в регулярные выражения
18.2. Подстановка
19. Here Documents
19.1. Here Strings
20. Перенаправление Ввода/Вывода
20.1. Использование exec
20.2. Перенаправление блоков кода
20.3. Приложения
21. Подоболочка
22. Ограниченные оболочки
23. Подстановка процесса
24. Функции
24.1. Сложные функции и функциональные сложности
24.2. Локальные переменные
24.3. Рекурсия без локальных переменных
25. Псевдонимы
26. Конструкции list
27. Массивы
28. Косвенные ссылки
29. /dev и /proc
29.1. /dev
29.2. /proc
30. Сетевое программирование
31. Ноль и Null
32. Отладка
33. Опции
34. Gotchas
35. Стильное написание сценария
387
35.1. Неофициальные стили сценариев Shell
36. Разное
36.1. Интерактивные и не интерактивные оболочки и сценарии
36.2. Обертки оболочки
36.3. Тесты и сравнения: альтернативы
36.4. Рекурсия: сценарий вызывающий сам себя
36.5. «Раскрашивание» сценариев
36.6. Оптимизация
36.7. Разные советы
36.8. Вопросы безопасности
36.9. Вопросы переносимости
36.10. Сценарии Shell под Windows
37. Bash, версии 2, 3 и 4
37.1. Bash, версия 2
37.2. Bash, версия 3
37.3. Bash, версия 4
--Stowe Boyd
Содержание
18.1. Краткое введение в регулярные выражения
18.2. Подстановка
388
метасимволов, которые соответствуют (или указываются) шаблону.
Регулярное выражение должно содержать хотя бы одно из следующего:
• Набор символов. Это символы сохраняющие свои буквальные значения. Простейший
тип регулярного выражения состоит только из набора символов, без метасимволов.
• Якорь. Обозначение (привязка) положения в текстовой строке, которое соответствует
регулярному выражению. Например, ^ и $ являются якорями (привязками).
"[a-z0-9]" соответствие любой одной буквы нижнего регистра или любой одной
цифры.
389
"[^b-d]" соответствие любому символу, за исключением находящихся в диапазоне
b-d. В данном случае ^ - это отрицание или инвертирование значения следующего
регулярного выражения (играет ту же роль, что и - !, но в другом контексте).
• Обратный слэш --\-- пропуск - специальный символ, который означает, что символ
интерпретируется буквально (и поэтому больше не является специальным).
• "\$" возвращает обратно буквальное значение «$», а не значение окончания строки
регулярного выражения. Подобно "\\", имеющему буквальное значение "\".
Угловые скобки должны быть заэкранированы, так как они имеют другой
буквальный смысл.
"\<the\>" соответствует слову "the," но не словам "them," "there," "other," и т.д.
bash$ cat textfile
This is line 1, of which there is only one instance.
This is the only instance of line 2.
This is line 3, another line.
This is line 4.
390
Единственный способ быть уверенным, что регулярные выражения работают -
это проверять их.
# Спасибо, S.C.
391
Фигурные скобки не доступны, как регулярные выражения, в
'классической' (POSIX не совместимой) версии awk. Однако,
расширенная версия awk, gawk GNU, имеет опцию --re-
interval, которая позволяет им быть регулярными выражениями
(без экранирования).
A-Za-z0-9.
392
a-z.
# ...
if [[ $arow =~ [[:digit:]] ]] # Введено число?
then # символьный класс POSIX
if [[ $acol =~ [[:alpha:]] ]] # Число, а затем буква? Не правильно!
# ...
# Пример из сценария [Link].
Примечания
[1] Метасмысл - это смысл термина или выражения на более высоком уровне абстракции.
Например, буквальное значение регулярного выражения - это обычное выражение,
393
которое соответствует принятому значению. Метасмысл радикально отличается, как
подробно описано в этой главе.
[2] Поскольку sed, awk и grep все делают одной строкой, то новая строка не будет
применяться в соответствии. Точка будет соответствовать новой строке в тех случаях,
когда необходим символ новой строки в многострочном выражении.
#!/bin/bash
echo
# Спасибо, S.C.
exit 0
18.2. Подстановка
Сам Bash не признает регулярные выражения. Внутри сценариев регулярные выражения
интерпретируют такие команды и утилиты, как sed и awk.
Bash осуществляет расширения имени файла [1] процессом, известным как globbing
(подстановка шаблона) - но он не использует стандартный набор регулярных выражений.
Вместо этого globbing распознает и расширяет символы шаблонов подстановки. Globbing
интерпретирует стандартные символы шаблонов подстановки [2] -- * и?, символы в
квадратных скобках и некоторые другие специальные символы (например, ^ для отрицания
смысла соответствия). Есть серьезные ограничения символов шаблонов подстановки
globbing. Строкам, содержащим *, не будут соответствовать имена файлов, которые
начинаются с точки, как, например, .bashrc. [3] Кроме того,?, в globbing, имеет отличный
394
смысл от смысла в регулярных выражений.
bash$ ls -l
total 2
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1
-rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 [Link]
-rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 [Link]
bash$ ls -l t?.sh
-rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 [Link]
bash$ ls -l [ab]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1
bash$ ls -l [a-c]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 a.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1
bash$ ls -l [^ab]*
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1
-rw-rw-r-- 1 bozo bozo 466 Aug 6 17:48 [Link]
-rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 [Link]
bash$ ls -l {b*,c*,*est*}
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 b.1
-rw-rw-r-- 1 bozo bozo 0 Aug 6 18:42 c.1
-rw-rw-r-- 1 bozo bozo 758 Jul 30 09:02 [Link]
Bash использует расширения имен файлов без кавычек, как аргументы командной строки.
Команда echo демонстрирует это.
bash$ echo *
a.1 b.1 c.1 [Link] [Link]
bash$ echo t*
[Link] [Link]
395
IFS="$(printf '\n\t')" # Удаляется пустое место (пробел).
Примечания
[1] Расширение имени файла означает расширение шаблонов имен файлов или шаблонов,
содержащих специальные символы. К примеру, example.??? может быть расширено
как example.001 и/или [Link].
[3] Расширению имени файла может соответствовать файл с точкой, но только если шаблон
явно содержит точку, как буквальный символ.
# Спасибо, S.C.
396
--Aldous Huxley, Island
Выбирайте limit string достаточно необычным, что он не возник где-нибудь в списке команд и
не запутал.
Обратите внимание, что here documents иногда может быть полезен для усиления эффекта не
интерактивных утилит и команд, таких как, например, wall.
wall <<zzz23EndOfMessagezzz23
Ваш e-mail полуденного заказа на пиццу системному администратору.
(Добавьте еще доллар на анчоусы или дополнение из грибов.)
# Это идет текст дополнительного сообщения.
# Примечание: 'wall' выводит строку комментария.
zzz23EndOfMessagezzz23
397
# wall <message-file
# Однако, внедрение шаблона сообщения в сценарий является
#+ неоднозначным единовременным решением.
exit
Даже такие маловероятные кандидаты, как текстовый редактор vi, прибегают к here
documents.
Пример 19-2. dummyfile: Создание пустого 2-строчного файла
#!/bin/bash
E_BADARGS=85
if [ -z "$1" ]
then
echo "Используем: `basename $0` имя_файла"
exit $E_BADARGS
fi
TARGETFILE=$1
exit
Приведенный выше сценарий может так же эффективно быть выполнен с ex, а не с vi. Here
documents может включать в себя список общих команд ex, формируя свою собственную
категорию, известную как сценарии ex.
#!/bin/bash
# Заменяет все вхождения "Smith" на "Jones"
#+ в файлах с именами содержащими суффикс ".txt".
ORIGINAL=Smith
REPLACEMENT=Jones
398
for word in $(fgrep -l $ORIGINAL *.txt)
do
# -------------------------------------
ex $word <<EOF
:%s/$ORIGINAL/$REPLACEMENT/g
:wq
EOF
# :%s это подстановка команд "ex".
# :wq это «записать-и-выйти».
# -------------------------------------
done
cat <<End-of-message
-------------------------------------
Это строка 1 сообщения.
Это строка 2 сообщения.
Это строка 3 сообщения.
Это строка 4 сообщения.
Это последняя строка сообщения.
-------------------------------------
End-of-message
exit 0
#--------------------------------------------
# Код ниже отключен, из-за "exit 0" выше.
399
ведущие табуляции (но не пропуски места) на выходе. Это может быть полезно для создания
более читаемого сценария.
Пример 19-4. Многострочные сообщения с подавлением табуляций
#!/bin/bash
# Почти, как предыдущий пример, но...
cat <<-ENDOFMESSAGE
Это строка 1 сообщения.
Это строка 2 сообщения.
Это строка 3 сообщения.
Это строка 4 сообщения.
Это последняя строка сообщения.
ENDOFMESSAGE
# Сценария будет выводить левому краю.
# Ведущие табуляции в каждой строке не будут видны.
exit 0
if [ $# -ge $CMDLINEPARAM ]
then
NAME=$1 # Если больше, чем один параметр командной строки,
#+ то принимается только первый.
else
NAME="John Doe" # Умолчание, если нет параметра командной строки.
fi
cat <<Endofmessage
400
Привет, $NAME.
Рад встрече, $NAME, от $RESPONDENT.
Endofmessage
exit
E_ARGERROR=85
if [ -z "$1" ]
then
echo "Использовать: `basename $0` Имя_файла_для_загрузки"
exit $E_ARGERROR
fi
Server="[Link]"
Directory="/incoming/Linux"
# Они не должны быть жестко закодированы в сценарии,
#+ что бы могли изменяться аргументом командной строки.
401
exit 0
Заключение в кавычки или экранирование «limit string», заголовка here document, отключает
подстановку параметров в его тело. Причина в том, что заключение limit string в
кавычки/экранирование эффективно экранирует специальные символы $, ', и \ , и заставляет
их толковать буквально. (Спасибо, Allen Halsey, за это разъяснение.)
Пример 19-7. Выключение подстановки параметров
#!/bin/bash
# 'cat' с here-document, но с отключенной подстановкой параметров.
NAME="John Doe"
RESPONDENT="автор этого прекрасного сценария"
cat <<'Endofmessage'
Привет, $NAME.
Рад встрече, $NAME, от $RESPONDENT.
Endofmessage
# И, кроме того:
cat <<"SpecialCharTest"
SpecialCharTest
exit
402
#!/bin/bash
# [Link]
# Основан на идее Albert Reiner.
# -----------------------------------------------------------
# 'Here document содержит тело создаваемого сценария.
(
cat <<'EOF'
#!/bin/bash
a=7
b=3
exit 0
EOF
) > $OUTFILE
# -----------------------------------------------------------
if [ -f "$OUTFILE" ]
then
chmod 755 $OUTFILE # Созданный файл исполняемый.
else
echo "Проблема при создании файла: \"$OUTFILE\""
fi
exit 0
Возможно присваивать переменную из выходных данных here document. На самом деле это
хитрая форма подстановки команд.
variable=$(cat <<SETVAR
Эта переменная
работает на нескольких строках
SETVAR
)
403
echo "$variable"
GetPersonalData ()
{
read firstname
read lastname
read address
read city
read state
read zipcode
} # Это, по-видимому, интерактивная функция, но...
echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo
exit 0
: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?} # Выводит сообщение об ошибке, если одна
#+ из переменных не установлена.
TESTVARIABLES
exit $?
404
Разновидностью данного способа является возможность «комментировать»
блоки кода.
: <<COMMENTBLOCK
echo "Эта строка не будет выведена на экран."
В этой строке комментария отсутствует префикс "#".
Это другая строка комментария с отсутствующим префиксом "#".
&*@!!++=
Строка выше не вызовет сообщения об ошибке,
потому что интерпретатор Bash будет игнорировать ее.
COMMENTBLOCK
exit 0
######################################################################
# Отметим, однако, что если в комментированном блоке кода содержится
#+ переменная со скобками, то это может вызвать проблемы.
# например:
#/!/bin/bash
405
: <<COMMENTBLOCK
echo "Эта строка не будет выведена на экран."
&*@!!++=
${foo_bar_bazz?}
$(rm -rf /tmp/foobar/)
$(touch my_build_directory/cups/Makefile)
COMMENTBLOCK
$ sh [Link]
[Link]: строка 3: foo_bar_bazz: параметр null или не присвоена
: <<'COMMENTBLOCK'
DOC_REQUEST=70
: <<DOCUMENTATIONXX
Список статистических данных заданной директории в табличном формате.
---------------------------------------------------------------
Параметром командной строки задается директория для перечисления.
Если директория не задана или заданная директория не может быть прочитана,
то перечисляется текущая рабочая директория.
DOCUMENTATIONXX
if [ -z "$1" -o ! -r "$1" ]
then
directory=.
else
directory="$1"
fi
406
exit 0
DOCUMENTATIONXX
exit $DOC_REQUEST
fi
Также смотрите Пример A-28, Пример A-40, Пример А-4, Пример A-42 и другие примеры
самодокументируемых сценариев.
#!/bin/bash
echo
"----------------------------------------------------------------
------"
cat <<LimitString
echo "Это строка 1 сообщения внутри here document."
echo "Это строка 2 сообщения внутри here document."
echo "Это конечная строка сообщения внутри here document."
LimitString
407
#^^^^ Отступ limit string. Ошибка! Этот сценарий не будет вести
#+ себя так, как ожидается.
echo
"----------------------------------------------------------------
------"
exit 0
# Это работает.
cat <<!
Привет!
! Еще три восклицательных знака!!!
!
# Но ...
cat <<!
Привет!
Следует один восклицательный знак!
!
!
# Сбой с сообщением об ошибке.
Примечания
[1] Исключением является, как пояснил Dennis Benzinger, использование <<- для
408
подавления табуляций.
# Попробуем:
if grep -q "txt" <<< "$VAR"
then # ^^^
echo "$VAR содержит в строках последовательность \"txt\""
fi
# Спасибо Sebastian Kaminski за предложение.
409
echo "$element" 1>&2
done <<< $(echo ${ArrayVar[*]})
E_NOSUCHFILE=85
Из 'man bash':
Here Strings
Вариант here documents, в формате:
<<<word
E_MISSING_ARG=87
if [ -z "$1" ]
then
410
echo "Используйте: $0 файл_почтового_ящика"
exit $E_MISSING_ARG
fi
do
if [[ $mail =~ ^From ]] # Соответствие полю "From" в сообщении.
then
(( body = 0 )) # "Обнуление" переменных.
(( match = 0 ))
unset date
elif (( body ))
then
(( match ))
# echo "$mail"
# Раскомментируйте строку выше, если хотите вывести тело
#+ сообщения на экран.
case "$header" in
[Ff][Rr][Oo][Mm] ) [[ $value =~ "$2" ]] && (( match++ )) ;;
# Соответствие строке "From".
[Dd][Aa][Tt][Ee] ) read -r -a date <<< "$value" ;;
# ^^^
# Соответствие строке "Date".
[Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read -r -a sender <<< "$value" ;;
# ^^^
# Соответствие IP адресу (может быть фальшивым).
esac
else
(( body++ ))
(( match )) &&
echo "MESSAGE ${date:+of: ${date[*]} }"
# Весь массив $date ^
echo "IP адрес отправителя: ${sender[1]}"
# Второе поле строка "Received" ^
fi
411
exit $?
# Упражнения:
# ---------
# 1) Разбейте одну функцию, выше, на несколько,
#+ для лучшей читабельности.
# 2) Добавьте в сценарий анализ для проверки различных ключевых слов.
$ mailbox_grep.sh scam_mail
MESSAGE of Thu, 5 Jan 2006 08:00:56 -0500 (EST)
IP address of sender: [Link]
Упражнение: Найдите другое применение here strings, такое как, например, передача ввода в
dc.
По умолчанию, всегда существует три открытых файла [1], stdin (клавиатура), stdout
(экран) и stderr (вывод сообщения об ошибках на экран). Эти и любые другие открытые
файлы могут быть перенаправлены. Перенаправление означает простой захват вывода файла,
команды, программы, сценария или даже блока кода сценария (см. Пример 3-1 и Пример 3-2)
и его направления, в качестве входных данных, другому файлу, команде, программе или
сценарию.
Каждый открытый файл получает определенный дескриптор файла.[2] Файловые
дескрипторы stdin, stdout и stderr имеют значения 0, 1 и 2, соответственно. Для
дополнительных открытых файлов, остаются дескрипторы 3-9. Иногда полезно назначать
один из этих дополнительных файловых дескрипторов stdin, stdout или stderr, как
временную дублирующую ссылку. [3] Это упрощает восстановление нормальной работы
после сложных перенаправлений и перестановок (см. Пример 20-1).
ВЫВОД_КОМАНДЫ >
# Перенаправляет stdout в файл.
# Если файла нет — он создается, если существует, то он перезаписывается.
412
ls -lR > [Link]
# Создание файла содержащего листинг дерева директории.
: > filename
# > уменьшает файл "filename" до нулевого размера.
# Если файла нет, то создается файл нулевого размера, (подобно 'touch').
# : служит фиктивным заполнителем, не производя никакого вывода.
> filename
# > уменьшает файл "filename" до нулевого размера.
# Если файла нет, то создается файл нулевого размера, (подобно 'touch').
# (Результат подобен ": >", выше, но не работает в некоторых оболочках.)
ВЫВОД_КОМАНДЫ >>
# Перенаправляет stdout в файл.
# Создает файл, если его нет, в противном случае — добавляет в него.
1>filename
# Перенаправляет stdout в файл "filename."
1>>filename
# Перенаправляет и добавляет stdout в файл "filename."
2>filename
# Перенаправляет stderr в файл "filename."
2>>filename
# Перенаправляет и добавляет stderr в файл "filename."
&>filename
# Перенаправляет оба stdout и stderr в файл "filename."
# Этот оператор работает, начиная с Bash 4, final release.
M>N
# "M" - файловый дескриптор, по умолчанию 1, если не указан иной.
# "N" — имя файла.
# Файловый дескриптор "M" перенаправляет в файл "N."
M>&N
# "M" — файловый дескриптор, по умолчанию 1, если не указан иной.
# "N" — другой файловый дескриптор.
#============================================================================
413
# Перенаправление stderr, одна строка за раз.
ERRORFILE=[Link]
2>&1
# Перенаправление stderr в stdout.
# Сообщения об ошибках передаются туда же, куда и стандартный вывод.
>>filename 2>&1
bad_command >>filename 2>&1
# Присоединяет оба stdout и stderr в файл "filename" ...
2>&1 | [команда(ы)]
bad_command 2>&1 | awk '{print $5}'
# Направляет stderr через туннель.
# |& было добавлено в Bash 4 как аббревиатура для 2>&1 |.
i>&j
# Перенаправление файлового дескриптора i в j.
# Все выходные данные файла указанные i отправляются в файл j.
>&j
# Перенаправление, по умолчанию, файловый дескриптор 1 (stdout) в j.
# Весь вывод направляется в j.
0< FILENAME
< FILENAME
# Доступ ввода из файла.
# Команда компаньон ">", часто используется в сочетании.
# grep искомое слово <имя_файла
[j]<>filename
# Открывает файл "filename" для чтения и записи,
#+ и присваивает ему дескриптор файла "j".
# Если "filename" не существует, создает его.
# Если дескриптор файла "j" не указан, то по умолчанию 0, stdin.
# Приложение для записи в указанное место в файле.
414
# Подобен ">", но с более общим эффектом.
# Полезно для соединения (связки) вместе команд, сценариев, файлов и
#+ программ.
cat *.txt | sort | uniq > result-file
# Сортирует все выводимые файлы .txt и удаляет повторяющиеся строки,
# в заключении сохраняет результат в "result-file".
Несколько перенаправлений ввода и вывода и/или конвейер могут быть объединены в одну
командную строку.
команда < входной_файл > выходной_файл
# Или эквивалент:
< входной_файл команда > выходной_файл # Хотя это необычно.
415
# Спасибо, S.C.
Примечания
[1] По соглашению в UNIX и Linux потоки данных периферийных устройств (файлов
устройств), рассматриваются как файлы, являющиеся аналогами обычных файлов.
[2] Дескриптор файла (ФД) это просто число, которое операционная система присваивает
открытому файлу для его отслеживания. Рассматривайте его как упрощенный тип
указателя на файл. Аналогично дескриптору файла в Cи.
[3] Использование file descriptor 5 может вызвать проблемы. Когда Bash создает
дочерний процесс, такой как exec, потомок наследует файловый дескриптор 5 (см.
Chet Ramey's archived e-mail, SUBJECT: RE: File descriptor 5 is held open). Лучше всего
этот файловый дескриптор не использовать.
echo
echo "Из файла читаются следующие строки."
echo "-------------------------------"
echo $a1
echo $a2
416
exec 0<&6 6<&-
# Теперь восстановим stdin из файлового дескриптора 6, где он был сохранен,
#+ и закроем дескриптор 6 ( 6<&- ), чтобы освободить его для использования
#+ другими процессами.
#
# <&6 6<&- работает так же.
echo
exit 0
exec N > filename влияет на весь сценарий или текущую оболочку. PID
перенаправления сценария или оболочки с этого момента меняется. Хотя . . .
LOGFILE=[Link]
# ----------------------------------------------------------- #
# Весь вывод команд в этом блоке передается в файл $LOGFILE.
417
echo
df
# ----------------------------------------------------------- #
echo
echo "== stdout восстановлен по умолчанию == "
echo
ls -al
echo
exit 0
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if [ -z "$2" ]
then
echo "Нужно указать выходной файл."
echo "Используйте: $0 входной_файл выходной_файл"
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 были перенаправлены.
# Обратите внимание, что «cat» может быть пропущено.
# -----------------------------------------------
418
# После восстановления, следующая строка выводится в stdout как обычно.
echo "Файл \"$1\" записан в \"$2\" преобразованным в верхний регистр."
exit 0
Lines=0
echo
echo "------------------------"
echo
exit 0
$ cat [Link]
Строка 1.
Строка 2.
Строка 3.
Строка 4.
419
Строка 5.
Строка 6.
Строка 7.
Строка 8.
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если не указано имя файла.
else
Filename=$1
fi
# Filename=${1:-[Link]}
#+ может заменить проверку выше (подстановка параметра).
count=0
echo
exit 0
# Однако ...
# Bash *может* иногда запускать subshell в КАНАЛЬНОМ цикле «while-read»,
#+ в отличие от ПЕРЕНАПРАВЛЕННОГО цикла 'while'.
abc=hi
echo -e "1\n2\n3" | while read l
420
do abc="$l"
echo $abc
done
echo $abc
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если имя файла не указано.
else
Filename=$1
fi
count=0
echo
exit 0
421
Пример 20-7. Перенаправление цикла until
#!/bin/bash
# Подобно примеру выше, но с циклом "until".
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если имя файла не задано.
else
Filename=$1
fi
exit 0
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если имя файла не задано.
else
Filename=$1
fi
422
exit 0
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если файл не указан.
else
Filename=$1
fi
line_count=`wc $Filename | awk '{ print $1 }'` # Число строк целевого файла.
exit 0
if [ -z "$1" ]
then
Filename=[Link] # Умолчание, если файл не задан.
else
Filename=$1
fi
TRUE=1
423
# Сравнение "if/then" не имеет возможности прохода, если не встроено в цикл.
exit 0
Перенаправление stdout блока кода имеет эффект сохранения выходных данных в файл.
См. Пример 3-2.
Here documents представляет собой особый случай перенаправления блоков кода. В этом
случае, можно направлять вывод here document на stdin цикла while.
# Пример Albert Siersema
# Используется с разрешения (благодарность!).
function doesOutput()
# Могут быть и внешние команды, конечно.
# Здесь мы демонстрируем, что можно использовать также и функцию.
{
ls -al *.jpg | awk '{print $5,$9}'
}
424
let nr++
totalSize=$((totalSize+fileSize)) # Или: "let totalSize+=fileSize"
done<<EOF
$(doesOutput)
EOF
20.3. Приложения
Умелое использование перенаправления ввода-вывода позволяет разделять и соединять
фрагменты вывода команды (см. Пример 15-7). Что, в свою очередь, позволяет создавать
отчеты и лог-файлы.
Пример 20-12. Регистрация событий
#!/bin/bash
# [Link]
# Автор: Stephane Chazelas.
# Используется в ABS Guide с разрешения.
FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5
case $LOG_LEVEL in
1) exec 3>&2 4> /dev/null 5> /dev/null;;
2) exec 3>&2 4>&2 5> /dev/null;;
425
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/[Link]
else exec 6> /dev/null # Получаем вывод.
fi
FD_LOGEVENTS=7
if [[ $LOG_EVENTS ]]
then
# exec 7>(exec gawk '{print strftime(), $0}' >> /var/log/[Link])
# Строка выше не работает в версиях Bash более поздних, чем 2,04. Почему?
exec 7>> /var/log/[Link] # Добавляется в "[Link]".
log # Запись времени и даты.
else exec 7> /dev/null # Получаем вывод.
fi
exit 0
426
Глава 21. Subshells (Подоболочки)
Subshell это новый запускаемый процесс уже запущенным сценарием оболочки (дочерний
процесс или процесс потомок).
(
# Внутри скобок, а поэтому subshell...
while [ 1 ] # Бесконечный цикл.
do
echo "Запущенна subshell..."
done
)
Анализ:
PID 2698, сценария, запустившего PID 2699, подоболочку.
427
Примечание: Строка "UID ..." отфильтрована командой «grep»,
а здесь показана для примера.
В общем, внешняя команда, в форках сценария, отключает подпроцесс, [1], тогда как
встроенная в Bash - нет. По этой причине, встроенные команды выполняются быстрее и
используют меньше системных ресурсов, чем эквивалентные им внешние команды.
Список команд в круглых скобках
( команда1; команда2; команда3; ... )
echo
outer_variable=Outer
global_variable=
# Объявление глобальной переменной для хранения значения переменной
subshell.
(
echo "Мы внутри subshell."
echo "Значение subshell ВНУТРИ subshell = $BASH_SUBSHELL"
inner_variable=Inner
echo; echo
echo "Мы вне subshell."
echo "Значение subshell ВНЕ subshell = $BASH_SUBSHELL"
echo
if [ -z "$inner_variable" ]
then
echo "inner_variable не определена основной частью оболочки"
else
428
echo "inner_variable определена основной частью оболочки"
fi
echo
# =======================================================================
# Дополнительно ...
exit 0
# Вопросы:
# --------
# Однажды выйдя из подоболочки, есть ли способ, чтобы повторно, в той же
#+ подоболочке, изменять или иметь доступ к переменным подоболочки ?
См. также $ BASHPID и Пример 34-2.
Определение: Область действия переменной - это область, в которой она имеет смысл, в
которой она имеет значение на которое может ссылаться. Например, видимость
локальной переменной находится лишь в функции, блоке кода или подоболочке, в которой
она определена, в то время как глобальная переменная видима всему сценарию, в котором
она появляется.
echo
429
Изменения в директориях, выполненные в подоболочке, не переносятся в родительскую
оболочку.
Пример 21-2. Список профилей пользователей
#!/bin/bash
# [Link]: Вывод всех профилей пользователей.
exit 0
Subshell может использоваться для настройки «специальной среды» для группы команд.
КОМАНДА1
КОМАНДА2
КОМАНДА3
(
IFS=:
PATH=/bin
unset TERMINFO
set -C
shift 5
КОМАНДА4
КОМАНДА5
exit 3 # Только выход из subshell!
)
# Родительская оболочка не пострадала и сохранилась окружающая среда.
КОМАНДА6
КОМАНДА7
430
# или [[ ${variable-x} != x$variable ]]
# или [[ ${variable+x} = x ]]
# или [[ ${variable-x} != x ]]
Для перенаправления I/O в subshell используется оператор "|" конвейер, например так
ls -al | (команда).
var1=23
echo "$var1" # 23
{ var1=76; }
echo "$var1" # 76
431
Примечания
[1] Внешняя команда вызываемая exec, (как правило) не является форком
подпроцесса/подоболочки.
• Вывод перенаправления.
echo
432
echo "Теперь в `pwd`"
echo
set -r
# установлен --restricted имеющий другой эффект.
echo "==> Теперь в ограниченном режиме. <=="
echo
echo
echo
echo
echo
echo
echo
exit 0
433
Глава 23. Подстановка процесса
Конвейер stdout команды в stdin другой команды – мощное средство. Но, что делать,
если вам нужно передать stdout нескольких команд? Это возможно сделать
подстановкой процесса.
Операция подстановки процессов передает вывод процесса (или процессов) в stdin другого
процесса.
Шаблон
Список команд заключается в скобки
434
>(список_команд)
<(список_команд)
Процесс подстановки может сравнивать выводы двух разных команд или даже выводы
различных вариантов одной и той же команды.
bash$ comm <(ls -l) <(ls -al)
total 12
-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0
-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2
-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 [Link]
total 20
drwxrwxrwx 2 bozo bozo 4096 Mar 10 18:10 .
drwx------ 72 bozo bozo 4096 Mar 10 17:58 ..
-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0
-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2
435
-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 [Link]
# Проверка распаковки:
gzip -d <([Link] | md5sum -c mydata-orig.md5)
# md5sum оригинала проверяет stdin и обнаруживает проблемы сжатия.
436
tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > [Link].bz2 3>&-
exec 3>&-
Есть способ обойти проблему передачи echo при помощи цикла while-read, запущенного в
подоболочке.
Пример 23-1. Перенаправление блока кода без порождения потомка (форка)
#!/bin/bash
# [Link]: цикл while-read с подстановкой процесса.
echo
while read i
do
echo $i
global=3D": Доступна за пределами цикла."
# ... потому что она НЕ запущена в subshell.
done < <(echo "случайный ввод")
# ^ ^
# А также ...
declare -a inloop
index=0
cat $0 | while read line
do
inloop[$index]="$line"
((index++))
# Запущено в subshell, далее ...
done
437
echo "OUTPUT = "
echo ${inloop[*]} # ... ничего не выводится на экран.
declare -a outloop
index=0
while read line
do
outloop[$index]="$line"
((index++))
# НЕ запущено в subshell, далее ...
done < <( cat $0 )
echo "OUTPUT = "
echo ${outloop[*]} # ... выводится на экран весь сценарий.
exit $?
declare -a array0
while read
do
array0[${#array0[@]}]="$REPLY"
done < <(sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}')
# Устанавливает в значение по умолчанию переменную 'read', $REPLY,
#+ подстановка процессов, а затем копирование в массив.
echo "${array0[@]}"
exit $?
# ====================================== #
bash [Link]
# --------------------------------------------------------------#
while read des what mask iface; do
# Некоторые команды ...
done < <(route -n)
# ^ ^ Первое < это перенаправление, второе- подстановка процесса.
438
# Чтобы проверить это, давайте проделаем что-то.
while read des what mask iface; do
echo $des $what $mask $iface
done < <(route -n)
# Вывод:
# Kernel IP routing table
# Destination Gateway Genmask Flags Metric Ref Use Iface
# [Link] [Link] [Link] U 0 0 0 lo
# --------------------------------------------------------------#
# --------------------------------------------------------------#
(
route -n | while read x; do ((y++)); done
echo $y # $y все еще не присвоено
Вообще говоря
(
: | x=x
# кажется, что запускается subshell
: | ( x=x )
# в то время как
x=x < <(:)
# на самом деле - нет
)
Примечания
[1] Имеет тот же эффект, что и именованный канал (временный файл), а именованные
каналы, на самом деле, в одно время использовались в подстановке процесса.
439
24.2. Локальные переменные
24.3. Рекурсия без локальных переменных
имя_функции {
команда...
}
или
имя_функции () {
команда...
}
440
эквивалентен команде.
Пример 24-1. Простые функции
#!/bin/bash
# [Link]: Выполнение функций (простых).
JUST_A_SECOND=1
funky ()
{ # Это, примерно, так же просто, как присваивание функции.
echo "Это функция funky."
echo "Теперь выйдем из функции funky."
} # Объявление функции должно осуществляться до ее вызова.
fun ()
{ # Несколько более сложная функция.
i=0
REPEATS=30
echo
echo "И теперь, действительно, начинается самое интересное."
echo
funky
fun
exit $?
# Однако...
f1 ()
{
echo "Вызывается функция \"f2\" из функции \"f1\"."
f2
441
}
f2 ()
{
echo "Функция \"f2\"."
}
# Спасибо, S.C.
#!/bin/bash
# [Link]
empty ()
{
}
# $ sh [Link]
# [Link]: строка 6: Синтаксическая ошибка или
# непредвиденная лексема `}'
# [Link]: строка 6: `}'
# $ echo $?
# 2
func ()
{
# Комментарий 1.
# Комментарий 2.
# Это, по-прежнему, пустая функция.
# Спасибо, Mark Bova, за разъяснение.
}
# В результате те же сообщения об ошибке, как и выше.
# Однако ...
not_quite_empty ()
{
неправильная_команда
} # Сценарий, содержащий эту функцию, *не* взорвется тех пор, пока
#+ функция не вызвана.
not_empty ()
{
:
442
} # Содержит : (команду null), и это - нормально.
Возможно даже вкладывать функцию внутрь другой функции, хотя это не очень хорошо.
f1 ()
{
f2 () # Вложена
{
echo "Функция \"f2\", внутри \"f1\"."
}
echo
# Спасибо, S.C.
Объявление функции может производиться в самых неожиданных местах, даже там, где не
работают команды.
ls -l | foo() { echo "foo"; } # Допустимо, но бесполезно.
if [ "$USER" = bozo ]
then
bozo_greet () # Объявление функции встроенное в конструкцию if/then.
{
echo "Привет, Bozo."
}
fi
443
exit # Вызываемая функция «exit()», это не встроенный 'exit'.
# Или, подобное:
filename=file1
[ -f "$filename" ] &&
foo () { rm -f "$filename"; echo "Файл "$filename" удален."; } ||
foo () { echo "Файл "$filename" не существует."; сенсорная панель; }
foo
# И зачем это?
# Это хитрый способ запутывания кода в сценарии.
func ()
{
echo "Первая версия функции ()."
}
func ()
{
echo "Вторая версия функции ()."
}
exit $?
444
# Возможно даже использовать функции для переопределения или
#+ упреждения системных команд.
# Конечно, это *не* желательно.
if [ "$2" ]
then
echo "-Параметр 2 это \"$2\".-"
fi
return 0
}
echo
445
echo "Присвоен параметр нулевого размера."
func2 "" # Вызов с параметром нулевого размера.
echo
exit 0
func ()
{
echo "$1" # Вывод на экран первого аргумента переданного функции.
} # Аргумент определен командной строкой?
echo "============================================================"
echo
echo "Затем вызов функции: явно передан аргумент командной строки."
func $1
# Теперь виден!
446
exit 0
echo_var ()
{
echo "$1"
}
message=Hello
Hello=Goodbye
echo "-------------"
# Что случится, если мы изменим содержимое переменной "hello"?
Hello="Hello, again!"
echo_var "$message" # Hello
echo_var "${!message}" # Hello, again!
exit 0
dereference ()
{
y=\$"$1" # Имя переменной (не значение!).
echo $y # $Junk
447
}
Junk="Какой-то текст"
echo $Junk "до" # Какой-то текст до
dereference Junk
echo $Junk "после" # Какой-то другой текст после
exit 0
my_read () {
# Вызывается именем переменной my_read,
#+ выводит предыдущее значение в квадратных скобках, как значение по
#+ умолчанию, затем запрашивает новое значение.
local local_var
echo
exit 0
448
Выход и возвращение
Статус выхода
return
#!/bin/bash
# [Link]: Большее из двух целых чисел.
max2 33 34
449
return_val=$?
exit 0
# Упражнение (легкое):
# ---------------
# Конвертируйте в интерактивный сценарий, то есть, сценарий
#+ запрашивающий ввод (двух чисел).
подсчет_строк_в_etc_passwd()
{
[[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))
# Если /etc/passwd читается, то REPLY устанавливается в
#+ строку count.
# Возвращаются оба значения параметра и информация о
#+ состоянии.
# 'echo' кажется ненужным, но ...
#+ оно удаляет избыточные пробелы из вывода.
}
if count_lines_in_etc_passwd
then
echo "Есть строка $REPLY в /etc/passwd."
else
echo "Строки в /etc/passwd не подсчитаны."
fi
# Спасибо, S.C.
#!/bin/bash
450
# Используем: латинская цифра-для-преобразования
LIMIT=200
E_ARG_ERR=65
E_OUT_OF_RANGE=66
if [ -z "$1" ]
then
echo "Используем: `basename $0` число-для-преобразования"
exit $E_ARG_ERR
fi
num=$1
if [ "$num" -gt $LIMIT ]
then
echo "Вне диапазона!"
exit $E_OUT_OF_RANGE
fi
return $number
# Упражнения:
# ---------
# 1) Объясните, как работает эта функция.
# Подсказка: деление с последующим вычитанием.
# 2) Расширьте диапазон функции.
# Подсказка: Используйте "echo" и захват подстановки команд.
}
451
# Это так необходимо??? Можно упростить?
echo
exit
#!/bin/bash
# [Link]
return_test 27 # o.k.
echo $? # Возвращено 27.
# =========================================================
return_test -151896 # Работает ли большое отрицательное число?
echo $? # Возвратится -151896?
# Нет! Возвратится 168.
# Версии Bash до 2.05b поддерживают большие отрицательные целые числа
#+ возвращаемых значений.
# Это было полезное свойство.
# Новые версии Bash, к сожалению, отключили эту лазейку.
# Они могут сбрасывать старые версии сценариев.
# Внимание!
# =========================================================
exit 0
alt_return_test ()
{
452
fvar=$1
Return_Val=$fvar
return # Возвращает 0 (успешно).
}
alt_return_test 1
echo $? # 0
echo "возвращаемое значение = $Return_Val" # 1
alt_return_test 256
echo "возвращаемое значение = $Return_Val" # 256
alt_return_test 257
echo "возвращаемое значение = $Return_Val" # 257
alt_return_test 25701
echo "возвращаемое значение = $Return_Val" #25701
#!/bin/bash
# [Link]: Большая из двух БОЛЬШИХ целых чисел.
453
fi
exit 0
# Упражнения:
# ---------
# 1) Найдите более элегантный способ проверки
#+ параметров, передаваемых функции.
# 2) Упростите структуру "OUTPUT" if/then.
# 3) Перепишите сценарий, чтобы принимать ввод параметров из командной #+
строки.
Вот еще один пример захвата "возвращаемого значения" функции. Понимание его
требует некоторых знаний awk.
454
#+ и для февраля в високосный год.
}
# ----------------------------------------------
# Пример использования:
month=4 # Апрель, для примера (4-й месяц).
days_in=$(month_length $month)
echo $days_in # 30
# ----------------------------------------------
Перенаправление
Перенаправление stdin функции
Функция, по существу, является блоком кода, а это означает, что ее stdin может быть
перенаправлен (как в Примере 3-1).
#!/bin/bash
# [Link]
#
# Получение "настоящего имени" из имени пользователя в /etc/passwd.
file=/etc/passwd
pattern=$1
if [ $# -ne "$ARGCOUNT" ]
then
echo "Используйте: `basename $0` USERNAME"
exit $E_WRONGARGS
fi
file_excerpt $pattern
455
# или
# awk -F: '/PATTERN/ {print $5}'
# или
# awk -F: '($1 == "username") { print $5 }' # настоящее имя из
#+ username
# Однако, они не так поучительны.
exit 0
# Попробуйте:
Функция ()
{
{
...
} < файл
}
# Аналогично,
# Спасибо, S.C.
Примечания
[1] Команда return является встроенной командой Bash.
456
24.2. Локальные переменные
Как переменная становится локальной ?
Локальные переменные
Переменная, объявленная как локальная, это переменная, которая является видимой только в
пределах блока кода, в котором она появилась. Имеет локальную (местную) сферу
деятельности. В функции, локальная переменная, имеет смысл только в пределах блока
функции. [1]
#!/bin/bash
# [Link]: Глобальная и локальная переменные внутри функции.
func ()
{
local loc_var=23 # Объявлена, как локальная переменная.
echo # Используя встроенное 'local'.
echo "\"loc_var\" внутри функции = $loc_var"
global_var=999 # Если не объявлена, как локальная,
# то, по умолчанию - глобальная.
echo "\"global_var\" внутри функции = $global_var"
}
func
echo
echo "\"loc_var\" вне функции = $loc_var"
# $loc_var вне функции =
# Нет, $loc_var не видна.
echo "\"global_var\" вне функции = $global_var"
# $global_var вне функции = 999
# $global_var видна везде.
echo
exit 0
# В отличие от Cи, переменная Bash, объявленная внутри функции, является
#+ локальной, ТОЛЬКО если объявлена как таковая.
func ()
{
global_var=37 # До вызова функции видима только
#+ в блоке функции.
457
} # ОКОНЧАНИЕ ФУНКЦИИ
func
echo "global_var = $global_var" # global_var = 37
# Была создана вызовом функции.
#!/bin/bash
function0 ()
{
echo
echo "Объявление локальной и назначение одной команде."
local t1=$(exit 1)
echo $? # 0
# Неожиданно!
# По-видимому, присваивание переменной
#+ произошло до объявления ее локальной.
#+ Выводится возвращаемое значение последнего действия.
echo
echo " Объявление локальной, а затем присвоение (отдельные
команды)."
local t2
t2=$(exit 1)
echo $? # 1
# Как и ожидалось.
function0
458
24.2.1. Локальные переменные и рекурсии.
Рекурсия - это интересная, а иногда и полезная форма самовоспроизведения. Herbert Mayer
определяет ее как «... выражение алгоритма, использующего более простой вариант этого
же алгоритма...»
Рассматриваемое определение определяется с точки зрения самого себя, [2] выражение его
собственным выражением, [3] змея глотает свой собственный хвост, [4] или... функция,
вызывающая сама себя. [5]
#!/bin/bash
# [Link]
# Демонстрация рекурсии.
recurse ()
{
var="$1"
exit $?
#!/bin/bash
# [Link]
# Сценарий, который определяет «рекурсию» графическим способом.
RECURSIONS=10
r_count=0
sp=" "
define_recursion ()
{
((r_count++))
459
sp="$sp"" "
echo -n "$sp"
echo "\"The act of recurring ... \"" # Из словаря 1913 Webster's.
echo
echo "Рекурсия: "
define_recursion
echo
exit $?
# факториал
# ---------
MAX_ARG=5
E_WRONG_ARGS=85
E_RANGE_ERR=86
if [ -z "$1" ]
then
echo "Usage: `basename $0` number"
exit $E_WRONG_ARGS
fi
fact ()
{
460
local number=$1
# Переменная "number" должна быть объявлена как локальная,
#+ иначе работать не будет.
if [ "$number" -eq 0 ]
then
factorial=1 # Факториал 0 = 1.
else
let "decrnum = number - 1"
fact $decrnum # Вызов рекурсивной функции (вызывающей саму себя).
let "factorial = $number * $?"
fi
return $factorial
}
fact $1
echo "Факториал $1 это $?."
exit 0
Также см. Пример A-15, пример рекурсии в сценарии. Имейте в виду, что рекурсия является
ресурсоёмкой операцией и выполняется медленно, и поэтому, как правило, не подходит для
сценариев.
Примечания
[1] Однако, как поясняет Thomas Braunberger, локальная переменная, объявленная в
родительской функции, также видна для функций, вызванных родительской
функцией.
#!/bin/bash
function1 ()
{
local func1var=20
function2
}
function2 ()
{
echo "В function2, \$func1var = $func1var."
}
function1
exit 0
# Вывод сценария:
461
Это документировано в мануале Bash:
recursive_function ()
{
echo "$1" # Заставляет функцию что-то делать, и ускоряет
#+ сегментацию.
(( $1 < $2 )) && recursive_function $(( $1 + 1 )) $2;
# Пока 1-й параметр меньше 2-го,
#+ приращиваем 1-й и запускаем рекурсию.
}
462
#!/bin/bash
# [Link] : Последовательность Фибоначчи (рекурсивно)
# Author: M. Cooper
# License: GPL3
# ----------алгоритм--------------
# Fibo(0) = 0
# Fibo(1) = 1
# else
# Fibo(j) = Fibo(j-1) + Fibo(j-2)
# ---------------------------------
Fibonacci ()
{
idx=$1 # Нет необходимости в локальной переменной. Почему?
if [ "$idx" -lt "$MINIDX" ]
then
echo "$idx" # Первые два условия 0 1 ... см.выше.
else
(( --idx )) # j-1
term1=$( Fibonacci $idx ) # Fibo(j-1)
(( --idx )) # j-2
term2=$( Fibonacci $idx ) # Fibo(j-2)
echo
exit 0
463
#
# Используется в "Advanced Bash Scripting Guide"
#+ с разрешения автора.
# Небольшие изменения и комментарии автора ABS.
#=================================================================#
# Ханойская пагода это математическая головоломка приписываемая Edouard
#+ Lucas, французскому математику 19-го века.
#
# Есть три вертикальные стойки, установленные на основании.
# Первая стойка имеет, уложенный на нее, набор дисков.
# В центре дисков - отверстия, поэтому они могут двигаться стойкам и лежат
#+ друг на друге.
# Диски имеют различные диаметры и сложены в порядке возрастания
#+ ( от большего диаметра внизу, до меньшего - вверху).
#
# Задача состоит в перекладке стопки дисков на одну из стоек.
# Можно перемещать только один диск на другую стойку.
# Разрешается перемещать диски обратно на исходную стойку.
# Можно размещать меньший диск на большем, но не наоборот.
# Или, запрещено размещать больший диск на меньшем.
#
# Для небольшого числа дисков требуется только несколько ходов.
#+ Для каждого дополнительного диска,
#+ требуемое количество ходов приблизительно удваивается,
#+ и «стратегия» становится все более сложной.
#
# Подробнее на: [Link]
#+ или стр. 186-92 _The Armchair Universe_ A.K. Dewdney.
#
#
# ... ... ...
# | | | | | |
# _|_|_ | | | |
# |_____| | | | |
# |_______| | | | |
# |_________| | | | |
# |___________| | | | |
# | | | | | |
# .--------------------------------------------------------------.
# |**************************************************************|
# #1 #2 #3
#
#=================================================================#
464
esac
}
case $# in
1) case $(($1>0)) in # Нужно иметь, хотя бы, один диск.
1) # Вложенные инструкции case.
dohanoi $1 1 3 2
echo "Всего перемещено = $Moves" # 2^n - 1, где n = № дисков.
exit 0;
;;
*)
echo "$0: Неправильное значение количества дисков";
exit $E_BADPARAM;
;;
esac
;;
*)
echo "usage: $0 N"
echo " Где \"N\" это количество дисков."
exit $E_NOPARAM;
;;
esac
# Упражнения:
# ---------
# 1) Будут ли когда-либо выполнены команды за этой точкой ?
# Почему? (Легко)
# 2) Объясните работу делающуюся функцией «dohanoi».
# (Трудно — см. ссылку на Dewdney, выше.)
465
Глава 25. Псевдонимы (alias)
Псевдоним (alias) Bash - это, по сути, не более чем сочетание клавиш, аббревиатура,
средство для сокращенного ввода длинной последовательности команд. Если, к примеру, мы
вставим alias lm="ls -l | more" в файл ~/.bashrc, то каждый lm [1], вводимый в
командной строке, будет автоматически заменяться на ls -l | more. Это помогает
уменьшить ввод в командную строку и избегать необходимости запоминания сложных
комбинаций команд и параметров. Установка alias rm="rm -i" (интерактивный режим
удаления) помогает избежать многих неприятностей, предотвращая случайное удаление
важных файлов.
В сценарии алиасы не всегда полезны. Было бы неплохо, если бы алиасы могли выполнять
некоторые функции препроцессора Cи, например расширение макроса, но, к сожалению,
Bash не расширяет аргументы в теле алиаса. [2] Кроме того, сценарию не удается в "сложных
конструкциях" расширить сам алиас, например в if/then, циклах и функциях. Ограничение
добавления заключается в том, что алиас не расширяется рекурсивно. Почти все, что мы
можем делать алиасом, гораздо более эффективно делает функция.
Пример 25-1. Псевдонимы в сценарии
#!/bin/bash
# [Link]
shopt -s expand_aliases
# Необходимо задать этот параметр, иначе сценарий не будет расширен алиасами.
# Во первых.
alias Jesse_James='echo "\"Alias Jesse James\" это была комедия в 1959 с
участием Bob Hope."'
Jesse_James
echo
directory=/usr/X11R6/bin/
prefix=mk* # См. спецсимволы вызывающие проблемы.
echo "Variables \"directory\" + \"prefix\" = $directory$prefix"
echo
TRUE=1
466
echo
if [ TRUE ]
then
alias rr="ls -l"
echo "Пробуем \"rr\" объявленное в if/then:"
rr /usr/X11R6/bin/mk* #* В результате сообщение об ошибке!
# Алиасы не расширяются в составных объявлениях.
echo "Однако, ранее расширенный алиас по-прежнему признается:"
ll /usr/X11R6/bin/mk*
fi
echo
count=0
while [ $count -lt 3 ]
do
alias rrr="ls -l"
echo "Пробуем \"rrr\" в цикле \"while\":"
rrr /usr/X11R6/bin/mk* #* Алиас так же не расширяется.
# [Link]: строка 57: rrr: не найдена
let count+=1
done
echo; echo
exit 0
echo
exit 0
467
bash$ ./[Link]
total 6
drwxrwxr-x 2 bozo bozo 3072 Feb 6 14:04 .
drwxr-xr-x 40 bozo bozo 2048 Feb 6 14:04 ..
-rwxr-xr-x 1 bozo bozo 199 Feb 6 14:04 [Link]
Примечания
[1] ..., как первое слово из командной строки. Очевидно что алиас имеет смысл только в
начале команды.
468
Глава 26. Конструкции list
Конструкции and list и or list обеспечивают метод последовательной обработки ряда команд.
Они могут эффективно заменить составные вложения if/then или даже объявления case.
Объединение команд в цепочку
and list
Каждая команда выполняется в свою очередь, при том условии, что предыдущая команда
возвращает значение true (ноль). При первом возвращении false (не ноль) цепочка
команд завершается (первая команда возвратившая false является последней
выполняемой).
Интересно использование двоякого состояния and list в более ранней версии сценария
YongYe игры тетрис :
equation()
Пример 26-1. Использование and list для проверки аргументов командной строки
#!/bin/bash
# and list
469
if [ ! -z "$1" ]
then
echo "Аргумент #1 = $1"
fi
if [ ! -z "$2" ]
then
echo "Аргумент №2 = $2"
echo "Последние 2 аргумента переданы в сценарий."
else
echo "В сценарий передано меньше 2 аргументов."
fi
# Это длиннее и более сложнее, чем с помощью "and list".
exit $?
Пример 26-2. Еще одна проверка аргументов командной строки с помощью and list
#!/bin/bash
exit 0
or list
470
команда-1 || команда-2 || команда-3 || ... команда-n
Каждая команда выполняется по очереди до тех пор, пока предыдущая команда возвращает
значение false. Первый возвращенный результат true завершает цепочку команд (первая
команда возвратившая true является последней выполненной). Это очевидная
противоположность "and list".
#!/bin/bash
E_BADARGS=85
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS # Отсутствует аргумент? Выручает.
else
file=$1 # Объявляем файл.
fi
exit $?
Если первая команда в or list возвращает значение true, она будет выполняться.
471
# ==> ...
# ==> …
Статусом выхода and list или or list - является статус выхода последней
выполненной команды.
# Тот же результат
( false && true ) || echo false # false
# Но НЕ
false && ( true || echo false ) # (ничего не выводится на экран)
# Спасибо, S.C.
См. Пример A-7 и Пример 7-4 для иллюстрации использования конструкций and/or list при
проверке переменных.
472
Глава 27. Массивы
Более новые версии Bash поддерживают одномерные массивы. Элементы массива могут быть
инициализированы формой записи переменной[xx]. Кроме того, сценарий может ввести
весь массив, явно объявляя его declare -a переменная. Для разыменования
(извлечения содержимого) элемента массива, используются фигурные скобки, то есть $
{элемент[xx]}.
area[11]=23
area[13]=37
area[51]=UFOs
echo
473
area[5]=`expr ${area[11]} + ${area[13]}`
echo "area[5] = area[11] + area[13]"
echo -n "area[5] = "
echo ${area[5]}
# -----------------------------------------------------------------
# Другой массив, "area2".
# Другой способ присвоения массиву переменных...
# array_name=( XXX YYY ZZZ ... )
# -----------------------------------------------
# Опять другой массив, "area3".
# И другой способ присвоения массива переменных...
# array_name=([xx]=XXX [yy]=YYY ...)
area3=([17]=семнадцать [24]=двадцать_четыре)
exit 0
Как видим, самый удобный способ инициализации всего массива - это запись
array=( элемент1 элемент2 ... элементN ).
base64_charset=( {A..Z} {a..z} {0..9} + / = )
# Для инициализации элементов массива
#+ используются круглые скобки расширения.
# Выдержка из сценария vladz "[Link]"
#+ в приложении"Внесенные сценарии".
Bash допускает операции над массивами переменных, даже если переменные не объявлены
474
явно, как массивы.
string=abcABC123ABCabc
echo ${string[@]} # abcABC123ABCabc
echo ${string[*]} # abcABC123ABCabc
echo ${string[0]} # abcABC123ABCabc
echo ${string[1]} # Не выводится!
# Почему?
echo ${#string[@]} # 1
# В массиве один элемент.
# Сама строка.
# Аттрибуты.
Attrib[1]=" Wallace Stevens"
Attrib[2]="\"Thirteen Ways of Looking at a Blackbird\""
# Эта поэма в публичном доступе (копирайт истек).
echo
echo
exit 0
# Упражнение:
# --------
475
# Измените этот сценарий для строк стихотворения из текстового файла данных.
echo "--------------"
echo ${#array[0]} # 4
# Величина (число букв) первого элемента массива.
echo ${#array} # 4
# Величина первого элемента массива.
# (Альтернативный способ записи)
echo ${#array[1]} # 4
# Величина второго элемента массива.
# Индексация массивов в Bash начинается с нуля.
echo ${#array[*]} # 6
# Число элементов в массиве.
echo ${#array[@]} # 6
# Число элементов в массиве.
echo "--------------"
exit
476
Многие из стандартных операций string работают с массивами.
Пример 27-4. Операции string массива
#!/bin/bash
# [Link]: string операции массива.
echo
echo "---------"
echo "----------------------"
477
# Замена первого вхождения содержимого строки с заменой.
echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
# ^ # Применяется ко всем элементам массива.
echo "-----------------------------"
replacement() {
echo -n "!!!"
}
echo ${arrayZ[@]/%e/$(replacement)}
# ^ ^^^^^^^^^^^^^^
# on!!! two thre!!! four fiv!!! fiv!!!
# Вывод replacement() это строка замены.
# Q.E.D: Действие замены является, по сути, «назначением».
echo "------------------------------------"
# Доступ к "for-each":
echo ${arrayZ[@]//*/$(replacement необязательные_аргументы)}
# ^^ ^^^^^^^^^^^^^
# !!! !!! !!! !!! !!! !!!
echo
exit 0
# Прежде чем попасть под большой молоток - Perl, Python, или других ЯП -
# сравни:
# $( ... ) это подстановка команды.
# Функция запускается, как подпроцесс (потомок).
# Функция записывает свой вывод (если выводится на экран) в stdout.
# Объявление, в сочетании с «echo» и подстановкой команды,
#+ может читать stdout функции.
# Запись имени[@] указывает (эквивалент) на операцию "for-each".
478
# Bash более мощный, чем ты думал!
echo
exit 0
# Упражнение:
# --------
# Измените этот сценарий, что бы его листинг был в первозданном виде,
#+ вместе с пробелами, разрывами строк и т.д.
declare -a colors
# Все последующие команды в этом сценарии будут рассматриваться как массив #+
переменной «colors».
echo
element_count=${#colors[@]}
479
# Специальный синтаксис для извлечения количества элементов массива.
# element_count=${#colors[*]} то же работает.
#
# Переменная "@" позволяет разделять слово в кавычках
#+ (извлекает переменные, разделенные пробелами).
#
# Что соответствует поведению "$@" и "$*"
#+ в позиционных параметрах.
index=0
echo
echo
exit 0
480
#!/bin/bash
# [Link]
echo
ListArray()
{
echo
echo "Элементы в array0: ${array0[@]}"
echo "Элементы в array1: ${array1[@]}"
echo "Элементы в array2: ${array2[@]}"
echo "Элементы в array3: ${array3[@]}"
echo
echo "Размер первого элемента в array0 = ${#array0}"
echo "Размер первого элемента в array1 = ${#array1}"
echo "Размер первого элемента в array2 = ${#array2}"
echo "Размер первого элемента в array3 = ${#array3}"
echo
echo "Количество элементов в array0 = ${#array0[*]}" # 3
echo "Количество элементов в array1 = ${#array1[*]}" # 1 (Сюрприз!)
echo "Количество элементов в array2 = ${#array2[*]}" # 0
echo "Количество элементов в array3 = ${#array3[*]}" # 0
}
# ===================================================================
ListArray
ListArray
# или
array0[${#array0[*]}]="новый2"
array1[${#array1[*]}]="новый2"
array2[${#array2[*]}]="новый2"
array3[${#array3[*]}]="новый2"
ListArray
481
height=${#array2[@]}
echo
echo "Вершина стека для array2 = $height"
# 'pop' это:
unset array2[${#array2[@]}-1] # Массивы начинаются с нуля,
height=${#array2[@]} #+ Это значит, что первый элемент имеет
#+ индекс 0.
echo
echo "POP"
echo "Новая вершина стека для array2 = $height"
ListArray
# Изменим:
array4=( ${array0[@]/second/2nd} )
echo
echo "Элементы в array4: ${array4[@]}"
array7=( ${array0[@]#new1} )
echo # После array6 это не удивляет.
echo "Элементы в array7: ${array7[@]}"
# Michael Zick: Вопрос, а если эти строки находятся в жестких или мягких
#+ кавычках?
# Nathan Coulter: Здесь нет такого понятия, как "мягкие кавычки."
#! Что происходит на самом деле: это происходит настройка
#!+ шаблона после всех других расширений [word], в данном случае
482
#!+ как ${ parameter#word}.
zap='new*'
array9=( ${array0[@]/$zap/} )
echo
echo "Количество элементов в array9: ${#array9[@]}"
array9=( "${array0[@]/$zap/}" )
echo "Элементы в array9: ${array9[@]}"
# На этот раз нулевые элементы остались.
echo "Количество элементов в array9: ${#array9[@]}"
exit
# Копирование массива.
array2=( "${array1[@]}" )
# или
array2="${array1[@]}"
#
# Тем не менее, как поясняет Jochen DeSmet, это не получится с
#+ «разреженными» массивами, массивами
#+ с дырами (недостающими элементами).
# ------------------------------------------
array1[0]=0
# array1[1] не назначен
array1[2]=2
array2=( "${array1[@]}" ) # Скопирует его?
echo ${array2[0]} # 0
echo ${array2[2]} # (null), должно быть 2
# ------------------------------------------
483
# Добавление элемента в массив.
array=( "${array[@]}" "new element" )
# или
array[${#array[*]}]="new element"
# Спасибо, S.C.
#!/bin/bash
filename=sample_file
# cat sample_file
#
# 1 a b c
# 2 d e fg
declare -a array1
element_count=${#array1[*]}
echo $element_count # 8
484
# Version: $ID$
#
# Разъяснения и дополнительные комментарии William Park.
# задаем -vx
echo
echo '- - testing: =( ${array[@]} ) - -'
times
declare -a bigTwo=( ${bigOne[@]} )
# Внимание: ^ ^ круглые скобки
times
echo
echo '- - testing: =${array[@]} - -'
times
declare -a bigThree=${bigOne[@]}
# Теперь без круглых скобок.
times
485
# Я буду продолжать использовать первую форму в моем примере, потому
#+ что мне кажется, что это лучше иллюстрирует то, что происходит.
# Обратите внимание:
# ----
# Объявления "declare -a" в строках 32 и 44
#+ не являются строго необходимыми, так как это
#+ не явная форма объявления в Array=( ... ).
# Однако, устранение этих объявлений замедляет выполнение
#+ следующих разделов сценария.
# Попробуй и увидишь.
exit 0
CpArray_Mac() {
Hype()
{
486
# (Объединяется вместе с массивом, содержащим "Really Rocks".)
# Возвращается в массив с именем $2.
local -a TMP
local -a hype=( Really Rocks )
$($CopyArray $1 TMP)
TMP=( ${TMP[@]} ${hype[@]} )
$($CopyArray TMP $2)
}
exit 0
487
declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 )
# Разреженная составляющая -- [1] не определена.
echo
echo '-Убедитесь, что массив имеет действительно разреженные составляющие.-'
echo "Количество элементов: 4" # Жесткий код для иллюстрации.
for (( i = 0 ; i < 4 ; i++ ))
do
echo "Элемент [$i]: ${array2[$i]}"
done
# См. более общий пример кода в [Link].
declare -a dest
# Список результатов.
echo
echo '- - Проверка измененного массива - -'
cnt=${#dest[@]}
declare -a subArray=${dest[1]}
cnt=${#subArray[@]}
488
done
exit 0
--
Массивы позволяют развертывать старые знакомые алгоритмы в виде сценариев оболочки.
Является ли это хорошей идеей - решать читателям.
Пример 27-11. Сортировка пузырей
#!/bin/bash
# [Link]: Сортировка пузырей sorts.
exchange()
{
# Замена двух элементов массива.
local temp=${Countries[$1]} # Временное хранилище
#+ для элемента, после замены.
Countries[$1]=${Countries[$2]}
Countries[$2]=$temp
return
}
489
declare -a Countries # Объявление массива,здесь не обязательно,
#+ поскольку он инициализируется ниже.
number_of_elements=${#Countries[@]}
let "comparisons = $number_of_elements - 1"
# ----------------------------------------------------------------------
# Paulo Marcel Coelho Aragao предложил циклы for, как более простую
альтернативу.
#
# for (( last = $number_of_elements - 1 ; last > 0 ; last-- ))
## Исправил C.Y. Hunt ^ (Спасибо!)
# do
# for (( i = 0 ; i < last ; i++ ))
# do
# [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \
# && exchange $i $((i+1))
# done
# done
# ----------------------------------------------------------------------
490
let "comparisons -= 1" # Как только "тяжелые" элементы пузырьков опускаются,
#+ в каждом проходе нужно делать на одно сравнение
#+ меньше.
echo
echo "$count: ${Countries[@]}" # Вывод полученного массива в конце каждого
#+ прохода.
echo
let "count += 1" # Увеличиваем отсчет передач.
exit 0
--
Возможны ли в массивах вложенные массивы?
#!/bin/bash
# "Вложенный" массив.
exit 0
--
Встроенные массивы в сочетании с косвенными ссылками создают увлекательные
возможности
Пример 27-12. Встроенные массивы и косвенные ссылки
#!/bin/bash
# [Link]
# Встроенные массивы и косвенные ссылки.
491
# Сценарий Dennis Leeuw.
# Используется с разрешения.
# Изменен автором документа.
ARRAY1=(
VAR1_1=value11
VAR1_2=value12
VAR1_3=value13
)
ARRAY2=(
VARIABLE="test"
STRING="VAR1=value1 VAR2=value2 VAR3=value3"
ARRAY21=${ARRAY1[*]}
) # Встроили ARRAY1 во второй массив.
function print () {
OLD_IFS="$IFS"
IFS=$'\n' # Вывод каждого элемента массива
#+ на отдельной строке.
TEST1="ARRAY2[*]"
local ${!TEST1} # Смотрим, что случится, если мы удалим эту строку.
# Косвенная ссылка.
# Она делает компоненты $TEST1
#+ доступными данной функции.
# Вывод переменной
echo "Variable VARIABLE: $VARIABLE"
print
echo
exit 0
492
# (Трудное) упражнение для читателей: реализуйте это.
--
Массивы позволяют реализовывать различные задачи сценарием оболочки Сито
Эратосфена. Конечно, ресурсоемкие приложения такого рода, в действительности, должны
быть написаны на компилируемом языке, таком как Cи. Как сценарий, он работает
мучительно медленно.
Пример 27-13. Сито Эратосфена
#!/bin/bash
# [Link] ([Link])
# Сито Эратосфена
# Древний алгоритм нахождения простых чисел.
LOWER_LIMIT=1 # Начинается с 1.
UPPER_LIMIT=1000 # И заканчивается 1000.
# (Можете установить другой потолок ... если есть много свободного времени)
PRIME=1
NON_PRIME=0
let SPLIT=UPPER_LIMIT/2
# Оптимизация:
# Нужно проверить только половину чисел до верхнего предела. Почему?
declare -a Primes
# Простые числа Primes[] являются массивом.
initialize ()
{
# Инициализируем массив.
i=$LOWER_LIMIT
until [ "$i" -gt "$UPPER_LIMIT" ]
do
Primes[i]=$PRIME
let "i += 1"
done
# Полагаем, что все члены массива виновны (простые),
#+ пока не доказана невиновность.
}
print_primes ()
{
# Вывод членов массива Primes [], помеченных как простые.
i=$LOWER_LIMIT
493
if [ "${Primes[i]}" -eq "$PRIME" ]
then
printf "%8d" $i
# 8 пробелов на число делают столбцы симпатичными.
fi
done
let i=$LOWER_LIMIT+1
# Давайте начнем с 2.
t=$i
fi
# ==============================================
# main ()
# Вызов функций последовательно.
initialize
sift
print_primes
# Это то, что называют структурным программированием.
# ==============================================
echo
exit 0
# -------------------------------------------------------- #
# Код строк ниже не будет выполняться, из-за «exit.»
494
# Это улучшенная версия просеивания Stephane Chazelas,
#+ исполняемая несколько быстрее.
i=1
until (( ( i += 1 ) > SPLIT )) # Нужно проверить только половину.
do
if [[ -n ${Primes[i]} ]]
then
t=$i
until (( ( t += i ) > UPPER_LIMIT ))
do
Primes[t]=
done
fi
done
echo ${Primes[*]}
exit $?
# [Link] (reference)
# Проверка результатов [Link]
typeset -i i t
Primes[i=1]='' # 1 это не простое число.
until (( ( i += 1 ) > (${UPPER_LIMIT}/i) )) # Нужно проверить только i.
do # Почему?
if ((${Primes[t=i*(i-1), i]}))
# Незаметно, но поучительно, использование арифметического расширения
#+ для составляющих.
then
until (( ( t += i ) > ${UPPER_LIMIT} ))
do Primes[t]=; done
fi
done
495
# echo ${Primes[*]}
echo # Измените исходный сценарий для вывода (80-столбцового вывода).
printf "%8d" ${Primes[*]}
echo; echo
exit $?
declare -a stack
push() # Из стека.
{
if [ -z "$1" ] # Ничего не извлекается?
then
return
fi
return-=+=
}
496
pop() # Извлечение элемента стека.
{
Data= # Опустошаем данные.
Data=${stack[$SP]}
let "SP += 1" # Вылет указателя стека.
return
}
# =======================================================
# Теперь немного приятного.
echo
echo
push garbage
pop
status_report # Мусор в, мусор из.
pop # ПОСЛЕДНИЙ
status_report
pop # убежало
status_report
pop # 23
status_report # Последний в, первый из!
echo
exit 0
# =======================================================
497
# Упражнения:
# ---------
# Q(1) = Q(2) = 1
# Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), for n>2
echo
echo "Q-series [$LIMIT terms]:"
echo -n "${Q[1]} " # Вывод первых двух членов.
echo -n "${Q[2]} "
498
t0=`expr $n - ${Q[n1]}` # n - Q[n-1]
t1=`expr $n - ${Q[n2]}` # n - Q[n-2]
done
echo
exit 0
Rows=5
Columns=5
# Массив 5 X 5.
load_alpha ()
{
local rc=0
local index
for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y
do # Используем различные символы, какие нравятся.
local row=`expr $rc / $Columns`
local column=`expr $rc % $Rows`
let "index = $row * $Rows + $column"
alpha[$index]=$i
# alpha[$row][$column]
499
let "rc += 1"
done
# Проще было бы
#+ declare -a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y )
#+ но как-то не хватает «аромата» двумерного массива.
}
print_alpha ()
{
local row=0
local index
echo
done
echo
}
if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
then
let "index = $1 * $Rows + $2"
# Теперь выводим это наоборот.
echo -n " ${alpha[index]}"
# alpha[$row][$column]
fi
500
{ #+ «балансируем» на его нижнем левом углу.
local row
local column
if [ "$row" -ge 0 ]
then
let "t1 = $column - $row"
let "t2 = $column"
else
let "t1 = $column"
let "t2 = $column + $row"
fi
echo; echo
done
exit 0
# Упражнения:
# ---------
# 1) Перепишите загрузку массива и функции вывода в более интуитивном и
# менее запутанном стиле.
#
# 2) Выясните, как работают функции поворота массива.
# Подсказка: подумайте о последствиях обратного индексирования массива.
#
# 3) Перепишите этот сценарий для обработки не квадратного массива,
# например 6 X 4.
# Попробуйте свести к минимуму «искажения», когда массив повернут.
501
режимами адресации для ссылок и управления отдельными элементами положения строки и
столбца.
Еще более сложный пример имитации двумерного массива - Пример A-10.
Наиболее интересные сценарии с использованием массивов:
• Пример 12-3
• Пример 16-46
• Пример A-22
• Пример A-44
• Пример A-41
• Пример A-42
var=23
# ============================================== #
502
a=letter_of_alphabet # Переменная "a" содержит имя другой переменной.
letter_of_alphabet=z
echo
# Прямая ссылка.
echo "a = $a" # a = letter_of_alphabet
# Косвенная ссылка.
eval a=\$$a
# ^^^ Принудительная eval(оценка), и ...
# ^ экранируемый первый $ ...
# ------------------------------------------------------------------------
# «eval» принуждает обновится $a, подстанавливая ее на обновленное
# значение \$$a.
# Таким образом, мы видим, причину частого появления «eval» в нотации
# косвенной ссылки.
# ------------------------------------------------------------------------
echo "Теперь a = $a" # Теперь a = z
echo
t=table_cell_3
table_cell_3=24
echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24
echo -n "dereferenced \"t\" = "; eval echo \$$t # разыменованное "t" = 24
# Это простой случай, следующий работает также (почему?).
# 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
Exit 0
503
выражение и присвоить его: eval newvar=\$$varname.
Какая практическая польза от косвенной ссылки на переменные? Она придает Bash немного
функциональности указателей Cи, например в таблице подстановки. А они имеют некоторые
очень интересные приложения...
Nils Radtke демонстрирует, как создавать «динамические» имена переменной и оценивать их
содержимое. Это может пригодиться в исходных файлах конфигурации.
#!/bin/bash
# ---------------------------------------------
# Это может быть «исходный код» из отдельного файла.
isdnMyProviderRemoteNet=[Link]
isdnYourProviderRemoteNet=[Link]
isdnOnlineService="MyProvider"
# ---------------------------------------------
# ================================================================
chkMirrorArchs () {
arch="$1";
if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
then
return 0;
else
return 1;
fi;
}
getSparc="true"
unset getIa64
chkMirrorArchs sparc
echo $? # 0
# Истинно
chkMirrorArchs Ia64
echo $? # 1
504
# Ложно
# Примечания:
# -----
# Явно создается даже замещаемая часть имени переменной.
# Все параметры для вызова chkMirrorArchs в нижнем регистре.
# Имя переменной состоит из двух частей: "get" и "Sparc" ...
ARGS=2
E_WRONGARGS=85
" "$filename"
# Обратите внимание, что для awk не нужен eval перед \$$.
# -------------------------------------------------
# Окончание сценария awk.
505
exit $?
Файловые системы Linux или UNIX, как правило, имеют специальные директории /dev и
/proc.
29.1. /dev
Директория /dev содержит записи физических устройств, которые могут или не могут
присутствовать в оборудовании. [1] Обычно они называются файлами устройств. В качестве
примера - разделы привода жесткого диска, содержащие смонтированную файловую
систему(ы) имеют записи в /dev, и выводятся командой df.
506
bash$ df
Filesystem 1k-blocks Used Available Use%
Mounted on
/dev/hda6 495876 222748 247527 48% /
/dev/hda1 50755 3887 44248 9% /boot
/dev/hda8 367013 13262 334803 4% /home
/dev/hda5 1714416 1123624 503704 70% /usr
Среди прочего, директория /dev содержит петлевые устройства, такие как /dev/loop0.
Устройство loopback — трюк, который позволяет обращаться к обычному файлу, как к
блочному устройству. [2] Это позволяет монтировать всю файловую систему одним большим
файлом. См. Пример 17-8 и Пример 17-7.
Некоторые устройства в /dev имеют другие, специализированные, назначения, и
называются псевдо-устройствами, такие как /dev/null, / dev/zero, /dev/urandom,
/dev/sda1 (раздел жесткого диска), /dev/udp (порт пакетов пользовательских
дейтаграмм ) и /dev/tcp.
Например:
Чтобы вручную примонтировать флэш-накопитель USB, добавьте следующую строку в
/etc/fstab. [3]
/dev/sda1 /mnt/flashdrive auto noauto,user,noatime 0 0
507
Сокет — это коммуникационный узел связанный с определенным портом I/O. (Это аналог
аппаратного сокета, или гнезда для соединительного кабеля). Позволяет передавать
данные между устройствами на самой машине, между машинами в той же сети, между
машинами в разных сетях и, конечно же, между машинами в разных местах в Интернете.
URL="[Link]/13"
Time=$(cat </dev/tcp/"$URL")
UTC=$(echo "$Time" | awk '{print$3}') # Третье поле - временя UTC (GMT).
# Упражнение: Измените сценарий для различных часовых поясов.
Загрузка URL:
bash$ exec 5<>/dev/tcp/[Link]/80
bash$ echo -e "GET / HTTP/1.0\n" >&5
bash$ cat <&5
: <<ОБЪЯСНЕНИЕ
508
Если Баш был скомпилирован с --enable-net-redirections, он может использовать
специальный символ устройства для перенаправления TCP и UDP. Эти
перенаправления одинаково используются STDIN/STDOUT/STDERR. Устройствами записи
для /dev/tcp являются 30,36:
mknod /dev/tcp c 30 36
exit $MYEXIT
e=`mknote 49`
g=`mknote 41`
a=`mknote 36`
b=`mknote 32`
c=`mknote 30`
cis=`mknote 29`
d=`mknote 27`
e2=`mknote 24`
509
n=`mknote 32767`
# Европейское обозначение.
echo -n "$g$e2$d$c$d$c$a$g$n$g$e$n$g$e2$d$c$c$b$c$cis$n$cis$d \
$n$g$e2$d$c$d$c$a$g$n$g$e$n$g$a$d$c$b$a$b$c" > /dev/dsp
# dsp = Digital Signal Processor (Цифровой сигнальный процессор)
Примечания
[1] Записи в /dev предоставляют точки монтирования для физических и виртуальных
устройств. Эти записи занимают очень мало места на диске.
[3] Конечно, точка монтирования /mnt/flashdrive должна уже существовать. Если нет,
то из-под root создайте ее, mkdir /mnt/flashdrive.
29.2. /proc
Директория /proc это псевдо-файловая система. Файлы /proc отражают запущенные
процессы системы и ядра, а так же содержат информацию и статистические данные о них.
bash$ cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
5 cua
7 vcs
10 misc
14 sound
29 fb
36 netlink
128 ptm
136 pts
510
162 raw
254 pcmcia
Block devices:
1 ramdisk
2 fd
3 ide0
9 md
3 0 3007872 hda 4472 22260 114520 94240 3551 18703 50384 549710 0
111550 644030
3 1 52416 hda1 27 395 844 960 4 2 14 180 0 800 1140
3 2 1 hda2 0 0 0 0 0 0 0 0 0 0 0
3 4 165280 hda4 10 0 20 210 0 0 0 0 0 210 210
...
511
bash$ fgrep Mem /proc/meminfo
MemTotal: 515216 kB
MemFree: 266248 kB
if [ "$CPU" = "Pentium(R)" ]
then
запуск_каких-то_команд
...
else
запуск_других_команд
...
fi
#!/bin/bash
# [Link]
# Получение параметров процесса в командной строке.
OPTION=cmdline
# Определяем PID.
pid=$( echo $(pidof "$1") | awk '{ print $1 }' )
# Получаем только первое ^^^^^^^^^^^^^^^^^^ из нескольких параметров.
echo
echo "ID процесса (первое из нескольких) "$1" = $pid"
echo -n "Аргументы командной строки: "
echo; echo
# Например:
512
# sh [Link] xterm
+
devfile="/proc/bus/usb/devices"
text="Spd"
USB1="Spd=12"
USB2="Spd=480"
if [ "$bus_speed" = "$USB1" ]
then
echo "Порт USB 1.1."
# Соответствует USB 1.1.
fi
513
if [ $# -ne $ARGNO ]
then
echo "Usage: `basename $0` номер PID" >&2 # Сообщение об ошибке >stderr.
exit $E_WRONGARGS
fi
# Альтернативно:
# if ! ps $1 > /dev/null 2>&1
# then # не запущен процесс соответствующий заданному pid.
# echo "Нет такого запущенного процесса."
# exit $E_NOSUCHPROCESS
# fi
514
fi
exit 0
if [ ! -e "/proc/$pidno/$PROCFILENAME" ]
# Когда процесс запущен, то существует и "статус" файла.
then
echo "Соединение разорвано."
515
# exit $NOTCONNECTED
fi
sleep $INTERVAL
echo; echo
done
exit 0
# Упражнения:
# ---------
# Улучшите сценарий, что бы он завершался нажатием клавиши «q».
# Сделайте сценарий более удобным для пользователей иными способами.
# Исправить сценарий для работы с беспроводными/DSL соединениями.
Примечания
[1] Некоторые системные команды, такие как procinfo, free, vmstat, lsdev и
uptime сделают это не хуже.
516
Глава 30. Сетевое программирование
Сеть это среднее между проданным слоном
и белым слоном: никогда не забывается и
всегда загажена.
--Nemo
517
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
echo SERVER_PORT = $SERVER_PORT
echo REQUEST_METHOD = $REQUEST_METHOD
echo HTTP_ACCEPT = "$HTTP_ACCEPT"
echo PATH_INFO = "$PATH_INFO"
echo PATH_TRANSLATED = "$PATH_TRANSLATED"
echo SCRIPT_NAME = "$SCRIPT_NAME"
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_HOST = $REMOTE_HOST
echo REMOTE_ADDR = $REMOTE_ADDR
echo REMOTE_USER = $REMOTE_USER
echo AUTH_TYPE = $AUTH_TYPE
echo CONTENT_TYPE = $CONTENT_TYPE
echo CONTENT_LENGTH = $CONTENT_LENGTH
exit 0
_test_CGI_
Может быть полезным для определения IP-адресов, которые пытались получить доступ к
компьютеру.
Пример 30-2. IP адреса
#!/bin/bash
# [Link]
# Список IP-адресов, с которыми соединялся ваш компьютер.
518
# Сценарий Bledsoe присваивает вывод отфильтрованного списка IP (аналогично
#+ строкам 19-22, выше) переменной.
# Он проверяет нескольких подключений с одного IP-адреса,
# затем использует:
#
# iptables -I INPUT -s $ip -p tcp -j REJECT --reject-with tcp-reset
#
# ... в пределах цикличной 60-секундной задержки для отброса пакетов DDOS атак.
# Упражнение:
# --------
# Используя команду «iptables» расширьте этот сценарий, чтобы пресекать
#+ попытки подключения от IP известных спамерских доменов.
519
Глава 31. О Zero и Nulls
Безупречная ошибочность, холодное
постоянство, прекрасное ничто...
Мертвое совершенство; не более.
Использование /dev/null
Думайте об /dev/null как о черной дыре. По существу это эквивалент файла доступного
только для записи. Все, что записано в него - исчезает. Попытки чтения или вывод из него не
дадут результата. И все же /dev/null может быть весьма полезным в командной строке и в
сценариях.
Подавление stdout.
520
cat $filename >/dev/null
# Содержимое файла не выводится в stdout.
Удаление содержимого файла, но сохранение самого файла, со всеми правами (из Примера
2-1 и Примера 2-3):
cat /dev/null > /var/log/messages
# : > /var/log/messages имеет тот же эффект, но
#+ не порождает новый процесс.
Автоматически удаляет содержимое лог файла (особенно хорошо для борьбы с этими
противными «cookies», посылаемыми коммерческими веб-сайтами):
ln -s /dev/null ~/.netscape/cookies
# Все cookie теперь направляются в черную дыру раньше, чем сохраняются
#+ на диске.
Использование /dev/zero
521
(двоичные нули, а не вида ASCII). Вывод записываемый в /dev/zero исчезает, и,
действительно, довольно трудно прочитать нули направляемые в него, хотя это можно
сделать при помощи od или hex редактора. Основное использование /dev/zero - это
создание инициализированного фиктивного файла заданной длины, используемого в качестве
временного файла подкачки.
#!/bin/bash
# Создание swap файла.
FILE=/swap
BLOCKSIZE=1024
MINBLOCKS=40
SUCCESS=0
######################################################################
echo "Создание swap файла размером $blocks блоков (KB)."
dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks # Обнуление файла.
mkswap $FILE $blocks # Назначение его swap файлом.
swapon $FILE # Активация swap файла.
retcode=$? # Все работает?
# Обратите внимание, что если одна или несколько из этих команд не
#+ выполняются, то возникают проблемы.
522
######################################################################
# Упражнение:
# Перепишите блок кода выше, так, что если он не
#+ выполнился успешно, то:
# 1) сообщение об ошибке выводилось бы в stderr,
# 2) все временные файлы очищались, а
# 3) сценарий упорядоченно завершался с соответствующим кодом
#+ ошибки.
exit $retcode
Еще одним применением /dev/zero является «обнуление» файла указанного размера для
особых целей, например, монтирования файловой системы на устройство loopback (см.
Пример 17-8) или «безопасного» удаления файла (см. Пример 16-61).
#!/bin/bash
# [Link]
username=`id -nu`
if [ "$username" != "$ROOTUSER_NAME" ]
then
echo "Нужно запускать с правами root \"`basename $0`\"."
exit $E_NON_ROOT_USER
fi
##############################################################################
523
dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE # Обнуление ОЗУ.
# Почему это необходимо?
mke2fs $DEVICE # Создание файловой системы ext2.
mount $DEVICE $MOUNTPT # Монтирование.
chmod 777 $MOUNTPT # Разрешение обычным пользователям получать
#+ доступ к ramdisk.
# Но, отмонтировать его сможет только root.
##############################################################################
# Необходимо проверять успешное завершение команд выше.
#+ Иначе возникнут проблемы.
# Упражнение: измените этот сценарий, чтобы он был более безопасным.
exit 0
--Brian Kernighan
524
отладки. Синтаксические ошибки или опечатки в сценариях создают сообщения об ошибках,
которые часто не помогают при отладке не работающего сценария.
Пример 32-1. Сценарий с ошибками
#!/bin/bash
# [Link]
a=37
if [$a -gt 27 ]
# ^^ Нет пробела
then
echo $a
fi
exit $? # 0! Почему?
Вывод сценария:
./[Link]: [37: command not found
for a in 1 2 3
do
echo "$a"
# done # Нужно ключевое слово «done», закомментированное в строке 8.
# === #
Вывод из сценария:
[Link]: line 10: syntax error: unexpected end of file (неожидаемый
конец файла)
525
Что делать, если сценарий выполняется, но работает не как ожидается? Это всем очень
хорошо знакомая логическая ошибка.
Пример 32-3. test24: другой сценарий с ошибками
#!/bin/bash
# Попробуйте так:
# echo "$badname"
rm "$badname"
exit 0
526
Еще лучше -echo, которое производит вывод на экран только во время
отладки.
DEBUG=on
Whatever=whatnot
debecho $Whatever # whatnot
DEBUG=
Whatever=notwhat
debecho $Whatever # (Не будет выводить на экран.)
527
# sh [Link]
# [Link]: line 6: unset_var: unbound variable (бессвязная переменная)
#######################################################################
assert () # Если условие false,
{ #+ то выход из сценария
#+ с выводом сообщения об ошибке.
E_PARAM_ERR=98
E_ASSERT_FAILED=99
lineno=$2
if [ ! $1 ]
then
echo "Assertion failed: \"$1\""
echo "Файл \"$0\", строка $lineno" # Задаем имя файла
#+ и номер строки.
exit $E_ASSERT_FAILED
# else
# return
# и продолжение исполнения сценария.
fi
}
# Аналогично, функцию assert() помещают в сценарий, который необходимо
#+ отладить.
#######################################################################
a=5
b=4
condition="$a -lt $b" # Сообщение об ошибке и выход из сценария.
# Попробуйте установить «условием» что-то еще
#+ и посмотрим, что получится.
# Какие-то команды.
# Какие-то другие команды ...
echo "Вывод на экран только в случае, если \"assert\" не неудачна."
# ...
# Другие команды ...
528
exit $?
6. Перехват на выходе.
Команда exit в сценарии вызывает сигнал 0, завершая процесс, то есть,
непосредственно сценарий. [1] Зачастую полезно перехватывать exit, заставляя, к
примеру, «выводить» переменные. trap должна быть первой командой в сценарии.
Перехват сигналов
trap
Сигнал - это сообщение посылаемое процессом, либо ядром или другим процессом,
которое рассказывает о некотором заданном действии (обычно прекратить). Например
нажатие CTRL-C отправляет сигнал INT, т.е. прерывание пользователем запущенной
программы.
Простой случай:
trap '' 2
# Игнорирование прерывания 2 (Control-C), без определенного действия.
a=39
b=36
529
exit 0
# Обратите внимание, что даже закомментировав команду «exit» не будет никакой
#+ разницы, сценарий завершится в любом случае после запуска команд.
TRUE=1
LOGFILE=/var/log/messages
# Обратите внимание, что $LOGFILE должен быть читаемым
#+ (для root, chmod 644 /var/log/messages).
TEMPFILE=temp.$$
# Создаем "уникальное" имя временного файла, с помощью id процесса сценария.
# Используйте 'mktemp', как альтернативу.
# Например:
# TEMPFILE=`mktemp [Link]`
KEYWORD=address
# При входе в систему, строка "remote IP address [Link]"
# добавляется в /var/log/messages.
ONLINE=22
USER_INTERRUPT=13
CHECK_LINES=100
# Количество строк лог-файла для проверки.
echo
sleep 1
done
530
#+ проверки неожиданного выхода из он-лайн.
exit 0
while true
do ifconfig ppp0 | grep UP 1> /dev/null && echo "соединено" && exit 0
echo -n "." # Печатает точки (.....) до соединения.
sleep 2
done
CHECK_INTERVAL=1
interval=1
long_interval=10
{
trap "exit" SIGUSR1
sleep $interval; sleep $interval
while true
do
echo -n '.' # Используются точки.
sleep $interval
done; } & # Запуск индикатора в фоновом режиме.
pid=$!
trap "echo !; kill -USR1 $pid; wait $pid" EXIT # Для обработки ^C.
531
echo ' Окончено!'
exit $?
#!/bin/bash
variable=29; line=$LINENO
exit 0
Вывод сценария:
Конечно, команда trap имеет и другие применения, помимо отладки, например отключение
определенных клавиш в сценарии (см. Пример A-43).
Пример 32-9. Выполнение нескольких процессов (в SMP box)
532
#!/bin/bash
# [Link]
# Выполнение нескольких процессов (в SMP box).
# Автор: Tedman Eng
function start_thread() {
if [ $PROCID -le $LIMIT ] ; then
./[Link] $PROCID&
let "PROCID++"
else
echo "Достигнут предел."
wait
exit
fi
}
while true
do
done
exit 0
#!/bin/bash
# [Link]
# Запускаем несколько процессов в SMP box.
# Этот сценарий называется [Link].
# Автор: Tedman Eng
temp=$RANDOM
index=$1
shift
let "temp %= 5"
let "temp += 4"
echo "Начало $index Время:$temp" "$@"
sleep ${temp}
echo "Завершение $index"
kill -s SIGRTMIN $PPID
533
exit 0
# ===================================================================== #
# ----------------------------------------------------------------------#
#################################################################
# Следующий оригинальный сценарий написанный Vernia Damiano.
# К сожалению, он так же нормально не работает.
#################################################################
#!/bin/bash
function avvia() {
local temp
local index
temp=$RANDOM
index=$1
shift
let "temp %= $TEMPO"
let "temp += 1"
echo "Начало $index Время:$temp" "$@"
sleep ${temp}
echo "Окончание $index"
kill -s SIGRTMIN $$
}
function parti() {
534
if [ $INDICE -gt 0 ] ; then
avvia $INDICE "${PARAMETRI[@]}" &
let "INDICE--"
else
trap : SIGRTMIN
fi
}
wait
trap - SIGRTMIN
exit $?
: <<КОММЕНТАРИИ_АВТОРА_СЦЕНАРИЯ
Мне нужно было запустить программу с помощью SMP машины с заданными параметрами
в виде нескольких различных файлов. Поэтому я подумал [и сделал] держать
запущенными указанное количество процессов и запускать новый каждый раз...,
когда предыдущий заканчивается.
1. $BASH_ARGC
2. $BASH_ARGV
535
Последний параметр командной строки, переданный сценарию, эквивалентна ${!#}
3. $BASH_COMMAND
4. $BASH_EXECUTION_STRING
5. $BASH_LINENO
6. $BASH_REMATCH
7. $BASH_SOURCE
8. $BASH_SUBSHELL
Примечания
[1] По соглашению, сигнал 0 присваивается выходу.
536
Глава 33. Опции
Варианты set, которые изменяют оболочку и/или поведение сценария.
Команда set включает опции в сценарии. В том месте сценария, где вы хотите, что бы опции
вступили в силу, используйте set -o название-опции или в краткой форме,
set -o verbose
# Выводит на экран все команды перед их выполнением.
#!/bin/bash
set -v
# Такой же эффект, что и выше.
537
set +сокращенная-опция.
#!/bin/bash
set -o verbose
# Команды выводятся на экран.
команда
...
команда
set +o verbose
# Команды перестают выводиться на экран.
команда
# Не выводится.
set -v
# Команды выводятся на экран.
команда
...
команда
set +v
# Команды перестают выводиться на экран.
команда
exit 0
Другим способом включения опций в сценарии является указание их сразу после заголовка
сценария #! .
#!/bin/bash -x
#
# Тело сценария.
Также можно включать опции сценария из командной строки. Некоторые опции, которые не
работают с set, будут, таким образом, доступны. Среди них -i, заставляющая сценарий
запуститься в интерактивном режиме.
bash -v script-name
bash -o verbose script-name
Ниже приведен список некоторых полезных опций. Они могут быть указаны в любой
сокращенной форме (должно предшествовать одно тире) или полным именем (должно
предшествовать два тире или -o).
538
Аббревиатура Имя Эффект
-B brace Включить расширение в скобках (настройка по
expansion умолчанию = включено)
+B brace Отключение расширения в скобках
expansion
-C noclobber Запретить перезапись файлов перенаправлением (могут
быть переопределены >|)
-D (нет) Список строк в двойных кавычках, отмеченных
символом $, а не выполнение команды в сценарии
-a allexport Экспорт всех определенных переменных
-b notify Уведомление о прекращении заданий, запущенных в
фоновом режиме (редко используется в сценариях)
-c ... (нет) Чтение команд из ...
checkjobs Информирует пользователя о любых открытых заданиях
при выходе из оболочки. Введено в версии 4 Bash, и до
сих пор 'экспериментальное'. Используйте:
shopt -s checkjobs (Осторожно: зависает!)
-e errexit Прерывает сценарий при первой ошибке, когда команда
завершается с не нулевым статусом (за исключением
циклов until или while, сравнений if, конструкции list)
-f noglob Отключение расширения имен файлов (символами
подстановки)
globstar globbing Включает оператор подстановки ** (версия 4 Bash).
starmatch Используйте: shopt -s globstar
-i interactive Сценарий запускается в интерактивном режиме
-n noexec Читать команды в сценарии, но не запускать их
(проверка синтаксиса)
-o Option- (нет) Вызов опции Option-Name
Name
-o posix POSIX Изменяет поведение Bash или вызываемого сценария в
соответствии со стандартом POSIX.
-o pipefail pipe failure Вызывает возвращение статуса выхода последней
команды в конвейере, которая возвращает не нулевое
возвращаемое значение.
-p privileged Сценарий запускается под "suid" (Внимание!)
-r restricted Сценарий запускается в ограниченном режиме (см. Главу
22).
-s stdin Чтение команд из stdin
-t (нет) Выход после первой команды
-u nounset Попытка использования неопределенной переменной
выведет сообщение об ошибке и принудительный выход
-v verbose Вывод каждой команды в stdout перед ее выполнением
-x xtrace Подобно -v, но более расширенно
- (нет) Флаг окончания опции. Все остальные аргументы
539
Аббревиатура Имя Эффект
являются позиционными параметрами.
-- (нет) Отключенные позиционные параметры. Если заданы
аргументы (--arg1 arg2), позиционные параметры
становятся аргументами.
--Puccini
540
23skidoo=value1 # Так же проблемно.
# Имена переменных, начинающиеся цифрами, зарезервированы оболочкой.
# Попробуем _23skidoo=value1. Начинать имя переменной с подчеркивания -
#+ нормально.
function-whatever () # Ошибка
# Вместо этого используйте 'function_whatever ()'.
do_something=do_something
do_something do_something
541
if [ $a -le 5] # if [ $a -le 5 ] будет правильно.
# ^^ if [ "$a" -le 5 ] даже лучше.
# [[ $a -le 5 ]] то же работает.
#!/bin/bash
# Однако ...
# если $BASH_VERSION ≥ 4.2; то
if [[ ! -v uninitialized_var ]]
then
uninitialized_var=0 # Инициализирована нулем!
fi
if [ "$a" = 273 ]
then
echo "Сравнение сработало."
else
echo "Сравнение не работает."
fi # Сравнение не сработало.
542
# Кроме того, возникают проблемы при попытке использования '-eq' с
#+ нецелым значением.
echo
number=1
echo "---------------------"
lesser=5
greater=105
echo
543
exit 0
Спасибо S.C.
minimum_version=2
# Так Chet Ramey постоянно добавляет новые возможности в Bash,
# вы можете установить $minimum_version в [Link], [Link], или необходимую.
E_BAD_VERSION=80
544
...
echo "Здесь"
echo "Там"
exit 0
545
совместимости Bash. Некоторые, специфичные для Bash функции, могут оказаться
недоступными. Сценарий, которому требуется полный доступ ко всем специфичным
для Bash расширениям, должен начинаться с #!/bin/bash.
num1=12
num2=43
echo "Сумма $num1 и $num2 = $(add2 $num1 $num2)"
Это не работает.
• Сценарий не может экспортировать переменные в родительский процесс, оболочку
или окружающую среду. Так же, как мы знаем из биологии: процесс потомок может
наследовать от родителей, а не наоборот.
WHATEVER=/home/bozo
export WHATEVER
exit 0
outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo
546
# Начало subshell
# Конец subshell
)
echo
echo "inner_variable вне subshell = $inner_variable"
# Не установлено.
echo "outer_variable вне subshell = $outer_variable" # Не изменилось.
echo
exit 0
# shopt -s lastpipe
a=aaa
b=bbb
c=ccc
echo
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
# Переназначение неверно.
547
### Это новая фича Bash версии 4.2.
# ------------------------------
echo "-------"
echo "a = $a" # a = one
echo "b = $b" # b = two
echo "c = $c" # c = three
# Переназначение успешно.
# ------------------------------
echo; echo
echo "one two three" | ( read a b c;
echo "Внутри subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
# a = one
# b = two
# c = three
echo "-----------------"
echo "Вне subshell: "
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
echo
exit 0
foundone=false
find $HOME -type f -atime +30 -size 100k |
while true
do
read f
echo "$f более 100 КБ и к ней не обращались в течение 30 дней"
echo "Рассмотрите возможность перемещения файла в архив."
foundone=true
# ------------------------------------
echo "Subshell level = $BASH_SUBSHELL"
548
# Уровень Subshell = 1
# Да, мы в subshell.
# ------------------------------------
done
# найденное здесь всегда будет ложным, так как оно имеет значение true
#+ внутри subshell
if [ $foundone = false ]
then
echo "Нет файлов для архивирования."
fi
foundone=false
for f in $(find $HOME -type f -atime +30 -size 100k) # Канала нет.
do
echo "$f более 100 КБ и к ней не обращались в течение 30 дней"
echo "Рассмотрите возможность перемещения файла в архив."
foundone=true
done
if [ $foundone = false ]
then
echo "Нет файлов для архивирования."
fi
if ! $foundone
then
echo "Нет файлов для архивирования."
fi
}
#+ вывода из grep.
# Исправьте, добавив параметр "--line-buffered" в grep.
549
• Использование команд «suid» внутри сценариев является рискованным, так как
может привести к нарушению безопасности. [1]
• Использование сценариев shell для программирования CGI может быть
проблематичным. Переменные сценариев оболочки не 'типосохраняемые', а это может
привести к нежелательным последствиям. Кроме того, это проблематично в сценариях
«cracker-proof».
• Bash не справляется корректно со строкой содержащей двойной слэш (//) .
• Сценарии Bash, написанные для Linux или BSD, возможно, потребуют исправлений
при их запуске на коммерческой UNIX машине. Такие сценарии часто используют
набор команд GNU и фильтров, которые имеют большую функциональность, чем их
общие с UNIX аналоги. Это особенно верно в отношении таких утилит обработки
текста, как tr.
Остерегайтесь, остерегайтесь,
остерегайтесь, остерегайтесь.
Остерегайтесь.
Примечания
[1] Установка бита suid на сам сценарий не имеет никакого эффекта в Linux и
большинстве других UNIX.
550
Глава 35. Пишем стильные сценарии
Имейте привычку писать сценарии в структурированной и систематической манере. Даже
сценарии «написанные на обратной стороне конверта» или на коленке выиграют, если вы
найдете несколько минут, чтобы спланировать и организовать свои мысли, прежде чем сесть
за кодирование.
Здесь несколько стилистических принципов. Они не (не обязательно) являются официальным
стилем написания сценариев Shell.
#************************************************#
# [Link] #
# написан Bozo Bozeman #
# July 05, 2001 #
# #
# Очистка файлов проекта. #
#************************************************#
# --------------------------------------------------------- #
# cleanup_pfiles () #
# Удаляет все файлы в указанной директории. #
# Параметр: $целевая_директория #
# Возвращает: 0 - успех, $E_BADDIR если что-то не так. #
# --------------------------------------------------------- #
cleanup_pfiles ()
{
if [ ! -d "$1" ] # Проверка наличия целевой директории.
then
echo "$1 это не директория."
return $E_BADDIR
fi
rm -f "$1"/*
return 0 # Успешно.
}
551
cleanup_pfiles $projectdir
exit $?
# Но лучше:
LOGFILE=/var/log/messages # Только строка, которую нужно изменить.
if [ -f "$LOGFILE" ]
then
...
fi
552
GetAnswer "Названо Ваше любимое число?"
favorite_number=$?
echo $favorite_number
См. Приложение Е
Ender предполагает использование кодов выхода сценариев
/usr/include/sysexits.h, хотя они предназначаются, главным образом, для
программирования на C и C++ .
• Используйте стандартные параметры флагов при вызове сценария. Ender предлагает
следующий набор флагов.
-a Все: Возвращает всю информацию (включая скрытые файлы).
-b Краткое описание: Короткая версия, как правило, для
других сценариев.
-c Копирование, конкатенация и т.д..
-d Ежедневно: информация использующаяся ежедневно, а не просто
информация конкретного экземпляра/пользователя.
-e Расширение/разработка: (часто не включает в себя скрытые
файлы).
-h Помощь: Подробности использования w/descs, aux инфо,
обсуждения, помощь.
См. Так же -V.
-l Журнал вывода сценария.
-m Руководство: Поиск справочных страниц для основных команд.
-n Числа: Только числовые данные.
-r Рекурсивно: Все файлы в директории (и/или все поддиректории).
-s Настройка и управление файлом: Конфигурационные файлы для
этого сценария.
-u Использование: Список флагов с которыми вызывается сценарий.
-v Подробно: Читабельный вывод, более или менее
отформатированный.
-V Версия / Лицензия / Копи(райт|лефт) / Вложения (email).
553
КОМАНДА
if [ $? -eq 0 ]
...
# Избыточно и не интуитивно.
if КОМАНДА
...
# Более кратко (насколько возможно, не совсем разборчиво).
--Landon Noll
Примечания
[1] В этом контексте, «magic numbers» имеют совершенно иной смысл, чем магические
числа, используемые для обозначения типов файлов.
554
Глава 36. Разное
В действительности никому не известно,
что является грамматикой Bourne Shell.
Даже исследование исходного кода мало
помогает.
--Tom Duff
Содержание
36.1. Интерактивные и не интерактивные оболочки и сценарии
36.2. Обертка оболочки
36.3. Проверки и сравнения: альтернативы
36.4. Рекурсия: сценарий вызывающий себя
36.5. 'Раскрашивание' сценариев
36.6. Оптимизация
555
36.7. Разные советы
36.7.1. Идеи увеличения мощности сценариев
36.7.2. Виджеты
36.8. Вопросы безопасности
36.8.1. Инфицированные сценарии оболочки
36.8.2. Сокрытие источника сценария оболочки
36.8.3. Написание безопасных сценариев
36.9. Переносимость
36.10. Написание сценариев оболочки под Windows
556
Не интерактивные сценарии могут работать в фоновом режиме, а интерактивные в этом
режиме зависают, ожидая ввода, которого нет и не будет. Обрабатывает эту трудность
сценария expect или here document, встроенный в интерактивный сценарий, вводимый при
помощи канала, при работе в фоновом режиме. В простейшем случае файл, из которого
осуществляется ввод, перенаправляют оператору read (переменной read <file). Именно
эти обходные пути дают возможность сценариям общего назначения работать либо в
интерактивном, либо в не интерактивном режимах.
Если необходимо проверить работает ли сценарий в интерактивной оболочке, то просто
поищите переменную приглашения, задаваемую $PS1. (Если пользователю предлагается
что-то ввести, то сценарию необходимо вывести на экран запрос.)
if [ -z $PS1 ] # нет приглашения?
### if [ -v PS1 ] # Для Bash 4.2+ ...
then
# не интерактивный
...
else
# интерактивный
...
fi
fd=0 # stdin
557
if [[ -t "$fd" || -p /dev/stdin ]]
then
echo интерактивный
else
echo не интерактивный
fi
558
sed -e /^$/d "$1"
# То же, что и
# sed -e '/^$/d' имя_файла
# вызываемый из командной строки.
exit
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` старый_шаблон новый_шаблон файл"
exit $E_BADARGS
fi
old_pattern=$1
new_pattern=$2
if [ -f "$3" ]
then
file_name=$3
else
echo "Файл \"$3\" не существует."
exit $E_BADARGS
fi
# -----------------------------------------------
# Вот где выполняется тяжелая работа.
sed -e "s/$old_pattern/$new_pattern/g" $file_name
# -----------------------------------------------
559
# Читайте документацию по 'sed' для углубленного понимания.
DEFAULT_LOGFILE=[Link]
LOGFILE=
if [ -z "$LOGFILE" ]
then # Если не присвоено, то по умолчанию ...
LOGFILE="$DEFAULT_LOGFILE"
fi
# Их логирование.
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
# Теперь, сделаем.
exec $OPERATION "$@"
exit 0
560
# Decimal Hex Character
# ------- --- ---------
# 33 21 !
# 34 22 "
# 35 23 #
# 36 24 $
#
# . . .
#
# 122 7a z
# 123 7b {
# 124 7c |
# 125 7d }
ARGS=2
E_WRONGARGS=85
filename=$1
column_number=$2
561
# Начало сценария awk.
# -----------------------------
awk '
{ total += $'"${column_number}"'
}
END {
print total
}
' "$filename"
# -----------------------------
# Окончание сценария awk.
exit 0
echo "==============================================================="
echo "Однако сценарий также может содержать системные команды и команды
оболочки."
exit 0
562
Можно даже комбинировать сценарий Bash и сценарий Perl в одном файле. В зависимости от
того, как вызывается сценарий будет выполняться часть Bash или часть Perl.
Пример 36-7. Комбинация сценариев Bash и Perl
#!/bin/bash
# [Link]
exit
# Конец части сценария Bash.
# =======================================================
#!/usr/bin/perl
# Эта часть сценария будет вызываться
# perl -x [Link]
Конечно, можно вставлять даже более экзотические языки сценариев в обертку оболочки. К
примеру Python ...
Пример 36-8. Python встроенный в сценарий Bash
#!/bin/bash
# [Link]
echo "==============================================================="
echo "Этот сценарий может содержать системные команды и команды оболочки."
exit 0
563
Обертка сценария вокруг mplayer и сервера переводов Google, может создать то, что будет
отвечать вам голосом.
Пример 36-9. Говорящий сценарий
#!/bin/bash
# Любезно предоставлено:
# [Link]
speak()
{
local IFS=+
# Вызывается mplayer, затем соединяется с Google translation server.
/usr/bin/mplayer -ao alsa -really-quiet -noconsolecontrols \
"[Link]
# Google переводит, но может так же и произносить.
}
LINES=4
Один интересный пример сложной обертки оболочки - сценарий undvd Martin Matusiak,
который обеспечивает простой в использовании интерфейс командной строки для сложной
утилиты mencoder. Другим примером является Ext3Undel, набор сценариев для
восстановления удаленных файлов из файловой системы ext3, Itzchak Rehberg.
Примечания
[1] Целый ряд утилит Linux являются, по сути, обертками оболочки. Например:
/usr/bin/pdf2ps, /usr/bin/batch и /usr/bin/xmkmf.
564
Аналогичным образом арифметические сравнения могут воспользоваться конструкцией ((
)).
a=8
city="New York"
# И снова, все сравнения, ниже, эквивалентны.
test "$city" \< Париж && echo "Да, Париж больше, чем $city"
# Больше порядок ASCII.
/bin/test "$city" \< Париж && echo " Да, Париж больше, чем $city"
[ "$city" \< Париж ] && echo " Да, Париж больше, чем $city"
[[ $city < Париж ]] && echo " Да, Париж больше, чем $city"
# Для $city кавычки не нужны.
# Спасибо, S.C.
RANGE=10
MAXVAL=9
i=$RANDOM
let "i %= $RANGE" # Генерирование случайных чисел между 0 и $RANGE - 1.
565
exit 0
# Примечание:
# ----
# Для правильной работы сценарий должен иметь права на выполнение.
# Даже в том случае, если он вызывается командой «sh».
# Объясните почему.
# ------------------------------------------------------------------------
Пример файла данных "phonebook":
566
# Когда этому сценарию передается более одного аргумента,
#+ он выводит строки, содержащие *только* эти аргументы.
# usage: [Link]
# описание: монтируемые устройства, вызываемые пользователем должны быть
# перечислены в группе MNTUSERS в файле /etc/sudoers.
# ----------------------------------------------------------
# Это сценарий usermount, который возвращает сам себя с помощью sudo.
# Пользователь с соответствующими правами может только
# usermount /dev/fd0 /mnt/floppy
# вместо
# sudo usermount /dev/fd0 /mnt/floppy
if [ -z "$SUDO_COMMAND" ]
then
mntusr=$(id -u) grpusr=$(id -g) sudo $0 $*
exit 0
fi
exit 0
567
сегментацию.
read person
case "$person" in
# Обратите внимание, что переменная в кавычках.
"E" | "e" )
# Верхний или нижний регистр ввода.
echo
echo "Roland Evans"
echo "4321 Flash Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "revans@[Link]"
echo "Business partner & old friend"
;;
568
"J" | "j" )
echo
echo "Mildred Jambalaya"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533-2814"
echo "(212) 533-9972 fax"
echo "milliej@[Link]"
echo "Girlfriend"
echo "Birthday: Feb. 11"
;;
* )
# Опции по умолчанию.
# Здесь так же подходит пустой ввод (выпадение RETURN).
echo
echo "Пока нет в базе данных."
;;
esac
echo
exit 0
######################################################################
### Документация функции draw_box ###
569
#
# COLOR это цвет рамки окна.
# Это 5-й аргумент и не обязателен.
# 0=черный 1=красный 2=зеленый 3=охристый 4=синий 5=пурпурный 6=голубой
# 7=белый.
# Если функции переданы плохие аргументы, она просто выйдет с кодом 65,
#+ а сообщения не будут выведены в stderr.
#
# Очистите терминал, прежде чем вы начнете рисовать поля.
# Команда clear не включена в функцию.
# Это позволяет пользователю рисовать несколько полей, даже перекрывающих.
draw_box(){
#=============#
HORZ="-"
VERT="|"
CORNER_CHAR="+"
MINARGS=4
E_BADARGS=65
#=============#
570
}
# Начинаем рисовать
count=1
c=`expr $2 + $BOX_WIDTH`
for (( r=$1; count<=$BOX_HEIGHT; r++)); do
plot_char $r $c $VERT
let count=count+1
done
count=1
r=`expr $1 + $BOX_HEIGHT`
for (( c=$2; count<=$BOX_WIDTH; c++)); do
plot_char $r $c $HORZ
let count=count+1
done
echo -e "\E[${P_ROWS};1H"
}
exit 0
# Упражнение:
# --------
571
# Добавьте параметр вывода текста в нарисованном поле.
572
"\E[" начинает управляющую последовательность. Разделенные точкой с запятой COLOR1
и COLOR2 указывают цвет переднего плана и цвет фона, в соответствии с приведенной
ниже таблицей. (Порядок чисел не имеет значения, так как числа переднего и заднего плана
попадают в не перекрывающиеся диапазоны.) 'm' завершает управляющую
последовательность, и текст начинается сразу после нее.
Числа в таблице, ниже, работают в терминале rxvt. Для других эмуляторов терминала они
могут отличаться.
Таблица 36-1. Числа представляющие цвета в управляющей последовательности
black='\E[30;47m'
red='\E[31;47m'
green='\E[32;47m'
yellow='\E[33;47m'
blue='\E[34;47m'
magenta='\E[35;47m'
cyan='\E[36;47m'
white='\E[37;47m'
573
# Аргумент $1 = сообщение
# Аргумент $2 = цвет
{
local default_msg="Нет переданных сообщений."
# Локальная переменная, на самом деле, не нужна.
echo -e "$color"
echo "$message"
Reset # Сброс на нормальный.
return
}
# Теперь попытка.
# ----------------------------------------------------
cecho "Грустный синий..." $blue
cecho "Пурпурный выглядит лучше фиолетового." $magenta
cecho "Зеленая зависть." $green
cecho "Видишь красный?" $red
cecho "Голубой, более известный как Аква." $cyan
cecho "Нет переданного цвета (по умолчанию черный)."
# Отсутствует аргумент $color.
cecho "\"Пустой\" передан цвет (по умолчанию черный)." ""
# Пустой аргумент $color.
cecho
# Отсутствуют аргументы $message и $color.
cecho "" ""
# Пустые аргументы $message и $color.
# ----------------------------------------------------
echo
exit 0
# Упражнения:
# ---------
# 1) Добавьте атрибут "bold" в функцию 'cecho ()'.
# 2) Добавьте опции расцвечивания фона.
574
################################################################
# Цели сценария:
# игра с управляющими последовательностями и цветами терминала.
#
# Упражнение:
# Измените сценарий, чтобы сделать его работу менее случайной, создайте
#+ фальшивую букмекерскую контору...
# Умм... умм... это начинает напоминать мне о фильме...
#
# Сценарий дает одной из случайных лошадей фору.
# Коэффициенты вычисляются по забегу лошади и выражаются в
#+ европейском(?) стиле.
# Например, odds=3.75 означает, что если вы поставили $1 и выиграли,
#+ вы получите $3.75.
#
# Сценарий был проверен на ОС GNU/Linux, с использованием xterm,
#+ rxvt и консольно.
# На компьютере с процессором AMD 900 МГц среднее время гонки
#+ составляет 75 секунд.
# На более быстрых компьютерах время будет меньше.
# А, если вы хотите увеличить интригу, сбросьте переменную USLEEP_ARG.
#
# Сценарий Stefano Palmeri.
################################################################
E_RUNERR=65
# Эта функция перемещает курсор в строку $1 колонки $2, а затем выводит $3.
# Например: "move_and_echo 5 10 linux" эквивалентно
575
#+ "tput cup 4 9; echo linux", но с помощью одной команды вместо двух.
# Примечание: "tput cup" определяет 0 0 верхний левый угол терминала, echo
#+ определяет 1 1 верхний левый угол терминала.
move_and_echo() {
echo -ne "\E[${1};${2}H""$3"
}
clear
576
move_and_echo 2 5 "|"
move_and_echo 2 80 "|"
move_and_echo 4 5 "| 2"
move_and_echo 4 80 "|"
move_and_echo 5 5 "V 3"
move_and_echo 5 80 "V"
# Отрисовка барьеров.
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"
tput cup 15 0
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"
577
# Установка первого типа сценария нарисованной «лошади».
HORSE_TYPE=2
# Рассчет шансов.
case $HANDICAP in
1) ODDS=`echo $HANDICAP \* 0.25 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
2 | 3) ODDS=`echo $HANDICAP \* 0.40 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
4 | 5 | 6) ODDS=`echo $HANDICAP \* 0.55 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
7 | 8) ODDS=`echo $HANDICAP \* 0.75 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
9) ODDS=`echo $HANDICAP \* 0.90 + 1.25 | bc`
echo $ODDS > odds_${HN}
esac
done
# Вывод шансов.
print_odds() {
tput cup 6 0
echo -ne '\E[30;42m'
for HN in `seq 9`; do
echo "#$HN odds->" `cat odds_${HN}`
done
}
578
echo /\\$HN/\\" "
done
}
print_odds
# --------------------------------------------------------
# Начало гонки.
draw_horses
echo -ne '\E[37;47m'
move_and_echo 18 1 $BLANK80
echo -ne '\E[30m'
move_and_echo 18 1 Starting...
sleep 1
MOVE_HORSE=0
ADD_POS=1
# Проверка, что текущая позиция является позицией забега.
if seq 10 7 68 | grep -w $COL &> /dev/null; then
if grep -w $MOVE_HORSE $COL &> /dev/null; then
ADD_POS=0
grep -v -w $MOVE_HORSE $COL > ${COL}_new
rm -f $COL
mv -f ${COL}_new $COL
else ADD_POS=1
fi
579
else ADD_POS=1
fi
COL=`expr $COL + $ADD_POS`
echo $COL > horse_${MOVE_HORSE}_position # Сохранение новой позиции.
# Когда все лошади вышли за рамки поля строки 15, перепечатка шансов.
touch fieldline15
if [ $COL = 15 ]; then
echo $MOVE_HORSE >> fieldline15
fi
if [ `wc -l fieldline15 | cut -f1 -d " "` = 9 ]; then
print_odds
: > fieldline15
fi
done
580
echo -en '\E[25m'
tput cup 19 0
exit 0
См. также Пример A-21, Пример А-44, Пример А-52-и Пример A-40.
Утилита ansi цветов Alister (основанная на утилите Moshe Jacobson) значительно упрощает
использование управляющих последовательностей ANSI. Она заменяет чистым и логическим
синтаксисом неуклюжие конструкции, которые мы только что обсудили.
Henry также создал утилиту ([Link] для упрощения создания
раскрашенных сценариев.
Примечания
[1] ANSI - это, конечно, акроним для American National Standards Institute. Этот высокий
орган устанавливает и поддерживает различных технические и промышленные
стандарты.
581
36.6. Оптимизация
Большинство сценариев оболочки - это быстрые и черновые решения не сложных проблем.
Поэтому оптимизация их скорости не является сложной задачей. Хотя давайте рассмотрим
случай, когда сценарий выполняет важную задачу, делает это хорошо, но работает слишком
медленно. Переписывание его на компилируемый язык не является приемлемым вариантом.
Проще исправить, переписав части сценария, которые его тормозят. Можно ли применять
принципы оптимизации кода в скромном сценарии оболочки?
Проверьте циклы в сценарии. Время, затрачиваемое на повторяющиеся операции, быстро
растет. Если это возможно, то удалите трудоемкие операции внутри цикла.
Используйте встроенные команды вместо системных команд. Встроенные выполняются
быстрее и обычно не запускают подоболочку при вызове.
Избегайте ненужных команд, особенно в конвейере.
cat "$file" | grep "$word"
Если вам не нужна поддержка Unicode, вы сможете увеличить скорость примерно в 2 раза,
просто установив переменную LC_ALL.
export LC_ALL=C
real 0m20.483s
user 1m34.470s
sys 0m12.869s
С [поддержкой Unicode]:
582
erik@erik-desktop:~/capture$ time ./[Link]
[Link] > [Link]
real 0m50.232s
user 3m51.118s
sys 0m11.221s
Математические сравнения
583
Проверка с if [], используя -eq:
real 0m0.457s
user 0m0.456s
sys 0m0.000s
Назначение испытаний
584
Чтение записи разреженного числового индекса массива
real 0m0.087s
user 0m0.084s
sys 0m0.000s
Примечания
[1] Обычно означает либеральное использование функций.
585
E_WRONGARGS=65
then
echo "Usage: название название-сценария"
# ^^^^^^^^^^^^^^^^^ Еще псевдо-код.
exit $E_WRONGARGS
fi
...
exit 0
# Строка 6 станет:
if [ $# -ne "$ARGCOUNT" ]
# Строка 12 станет:
echo "Usage: `название basename $0`"
• Оператор >> добавляет строки в файл. Что делать, если вы хотите, чтобы предварять
строку в существующем файле, то есть, чтобы вставлять ее в начале?
file=[Link]
title="***Это титульная строка данных текстового файла***"
586
Это упрощенный вариант сценария Примера 19-13, данного ранее. И, конечно же,
также это может сделать sed.
• Сценарий оболочки может выступать в качестве встроенной команды внутри другого
сценария оболочки, сценарий Tcl или wish, или даже Makefile. Он может быть
вызван как внешняя команда оболочки в программе Cи с помощью вызова system(),
т.е., system('имя_сценария');.
# Примечание:
# Здесь нет "#!".
# Так же нет "живого кода".
# Функции
587
CreateTempfileName () # Создание "уникального" временного файла.
{ # Из примера "[Link]".
prefix=temp
suffix=`eval date +%s`
Tempfilename=$prefix.$suffix
}
case $1 in
*[!a-zA-Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac # Thanks, S.C.
}
return $absval
}
return
588
# echo "$newvar" # набор смешанных букв
#
# Упражнение:Перепишите эту функцию для изменения передаваемых строчных
# аргументов в прописные ... toupper() [легко].
}
#+ Продолжение строки.
# Это первая строка
#+ многострочного комментария,
#+ а это конечная строка.
#* Примечание.
#o Элемент списка.
BAR_WIDTH=50
BAR_CHAR_START="["
BAR_CHAR_END="]"
BAR_CHAR_EMPTY="."
BAR_CHAR_FULL="="
BRACKET_CHARS=2
LIMIT=100
print_progress_bar()
{
# Подсчет количества полных символов.
let "full_limit = ((($1 - $BRACKET_CHARS) * $2) / $LIMIT)"
# Подготовка панели.
bar_line="${BAR_CHAR_START}"
for ((j=0; j<full_limit; j++)); do
bar_line="${bar_line}${BAR_CHAR_FULL}"
589
done
bar_line="${bar_line}${BAR_CHAR_END}"
echo ""
exit
COMMENT_BLOCK=
# Попробуйте присвоить переменной выше некоторое значение
if [ $COMMENT_BLOCK ]; then
Блок комментариев --
=================================
Строка комментария.
Другая строка комментария.
Еще одна строка комментария.
=================================
fi
exit 0
590
параметр только цифры и может ли он рассматривать его как целое число.
#!/bin/bash
SUCCESS=0
E_BADINPUT=85
if [ $? -ne "$SUCCESS" ]
then
echo "Usage: `basename $0` вводимое_целое_число"
exit $E_BADINPUT
fi
exit 0
local product=1
mult1=15383; mult2=25211
val1=`multiply $mult1 $mult2`
# Присваиваем переменной val1 функцию stdout (echo).
591
echo "$mult1 X $mult2 = $val1" # 387820813
exit 0
• Этот же способ также работает для буквенно-цифровых строк. Это означает, что
функция может «возвращать» не числовые значения.
capitalize_ichar () # Инициализация прописными символами
{ #+ передаваемой строки аргумента(ов).
#+ прописной буквы.
• С помощью этого метода функция может " возвращать" даже несколько значений.
echo
echo "Введите первое число"
read first
592
echo
echo "Введите второе число"
read second
echo
exit 0
sum_and_product ()
{
echo "Это функция sum_and_product." # Это испортит то, что выше!
echo $(( $1 + $2 )) $(( $1 * $2 ))
}
...
retval=`sum_and_product $first $second` # Присваивает вывод функции.
# Теперь это не будет работать правильно.
• Далее в нашем пакете приемов - это способы для передачи массива в функцию, а
затем «возвращение» массива обратно в основную часть сценария.
Передача массива включает в себя загрузку элементов массива, разделенных
пробелами в переменную подстановкой команды. Для возвращения массива, как
возвращаемого значения из функции, использует ранее упомянутая стратегия вывода
массива в функцию, а затем вызов подстановки команды и оператора (...) для
назначения массива.
Пример 36-20. Передача и возвращение массива
#!/bin/bash
# [Link]: Передача массива в функцию и ...
# "возвращение" массива из функции
Pass_Array ()
{
local passed_array # Локальная переменная!
passed_array=( `echo "$1"` )
echo "${passed_array[@]}"
# Объявляется список всех элементов нового массива
593
#+ и присваивается функции.
}
echo
echo "original_array = ${original_array[@]}"
# Список всех элементов настоящего массива.
echo "============================================================="
echo
############################################
ret_array ()
{
for element in {11..20}
do
echo "$element " # Вывод на экран отдельных элементов,
done #+ которые будут собраны в массив.
}
594
echo ${arr[@]} # 11 12 13 14 15 16 17 18 19 20
echo
exit 0
# Ищем анаграммы...
LETTERSET=etaoinshrdlu
FILTER='.......' # Минимальное количество букв
# 1234567
595
# [Link]
# [Link]
bash$ sh [Link]
islander
isolate
isolead
isotheral
# Упражнения:
# ---------
# Измените этот сценарий, взяв LETTERSET как параметр командной строки.
# Параметризуйте фильтры в строках 11-13 (как с $FILTER), что бы они
#+ могли задаваться путем передачи аргументов в функцию.
# Измените подход к анаграммированию,
#+ см. сценарий [Link].
596
then echo "Файл \"nonexistent_filename\" не существует."
fi
• Если вам необходим абсолютный доступ к переменной подоболочки вне ее, вот способ
это осуществить.
TMPFILE=tmpfile # Создаем временный файл для хранения
#+ переменной.
# Теперь ...
read inner_variable <$TMPFILE # Читаем обратно переменную оболочки.
rm -f "$TMPFILE" # Избавляемся от временного файла.
echo "$inner_variable" # Это некрасивый ляп, но это работает.
597
36.7.2. Виджеты
Было бы неплохо иметь возможность ссылаться на виджеты X-Windows из сценария
оболочки. К счастью существует несколько пакетов, которые помогают сделать это, а именно
Xscript, Xmenu и widtools. Первые два из них, кажется, больше не поддерживаются. К
счастью все еще возможно получить widtools.
if [ "$?" -eq 0 ]
# Хорошая привычка — проверять статус выхода.
598
then
echo "\"Диалоговое окно\" выполнено без ошибок."
else
echo "\"Диалоговое окно\ " выполнено с ошибкой (ами)."
# Или, кликните "Cancel", вместо кнопки "OK".
rm $OUTFILE
exit $E_INPUT
fi
exit $?
599
36.8. Вопросы безопасности
36.8.1. Инфицированные сценарии оболочки
Это краткое предупреждение о безопасности сценария. Сценарий может содержать червей,
трояны или даже вирусы. По этой причине никогда не запускайте сценарий из-под root (или с
установленными правами на сценарий запуска системы /etc/rc.d), если только вы не
получили его из надежного источника или тщательно проанализировали его на предмет
безопасности.
Различные исследователи в Bell Labs и других, включая M. Douglas McIlroy, Tom Duff и Fred
Cohen, изучили последствия вирусов сценария оболочки. Они делают вывод, что написать
«детский сценарий» слишком легко даже для новичка. [1]
Вот еще одна причина, чтобы изучать сценарии. Вы будете в состоянии видеть и понимать,
как сценарии могут защитить вашу систему от заражения больными сценариями.
Примечания
[1] См. статью Marius van Oers, Unix Shell Scripting Malware, а так же Denning reference в
600
библиографии.
36.9. Переносимость
Легче портировать оболочку, чем сценарий
оболочки.
--Larry Wall
Эта книга конкретно касается сценариев Bash в системе GNU/Linux. Все же, пользователи sh
и ksh найдут здесь много полезного .
Как это случается, многие из различных оболочек и языков сценариев стремятся
соответствовать стандарту POSIX 1003.2. Вызов Bash с опцией --posix или вставка в
заголовок сценария set -o posix заставляет Bash очень близко соответствовать этому
стандарту. Другой альтернативой является использование в сценарии заголовка sha-bang
#!/bin/sh, а не #!/bin/bash. [1] Обратите внимание, что в Linux и некоторых других UNIX
/bin/sh представляет собой ссылку на /bin/bash, и сценарий, вызванный таким
образом, отключает расширенную функциональность Bash.
Большинство сценариев Bash будут работать под ksh и наоборот, так как Chet Ramey
оперативно портировал в ksh возможности последних версий Bash.
На коммерческих UNIX-машинах, сценарии, использующие особенности стандартных
команд GNU, могут не работать. В последние несколько лет стало меньше проблем, так как
довольно много утилит GNU перенесено даже на «big iron» UNIX.
Bash имеет определенные особенности, которых в традиционном Bourne shell не хватает.
Среди них:
• Некоторые опции расширенного вызова
601
• Некоторые операции управления строками
• Процесс подстановки
• Сопроцессы
echo
echo -n "Testing "
sleep 1; echo -n ". "
sleep 1; echo -n ". "
sleep 1; echo ". "
echo
# Двойные скобки
String="Двойные скобки поддерживаются?"
echo -n "Тест двойных скобок: "
if [[ "$String" = "Двойные скобки поддерживаются?" ]]
then
echo "PASS"
else
echo "FAIL"
fi
# Массивы
test_arr=$default_option # НЕУДАЧНО
Array=( Если массивы поддерживаются будет выведено PASS )
test_arr=${Array[5]}
602
echo "Array test: $test_arr"
# Подстановка команд
csub_test ()
{
echo "PASS"
}
test_csub=$default_option # НЕУДАЧНО
test_csub=$(csub_test)csub_test (){}
echo "Тест подстановки команд: $test_csub"
echo
exit $?
Примечания
[1] Или, еще лучше, #!/bin/env sh
603
Глава 37. Bash, версии 2, 3 и 4
Содержание
37.1. Bash, версия 2
37.2. Bash, версия 3
37.2.1. Bash, версия 3.1
[Link], версия 3.2
37.3. Bash, версия 4
37.3.1. Bash, версия 4.1
37.3.2. Bash, версия 4.2
604
В обновленной версии 2, классического языка сценариев Bash, среди особенностей
добавлены массив переменных, расширение строк и параметров и улучшен способ
косвенных ссылок на переменные.
Пример 37-1. Расширение строки
#!/bin/bash
# Расширение строки.
# Введено в версии 2 Bash.
exit
a=letter_of_alphabet
letter_of_alphabet=z
echo
t=table_cell_3
table_cell_3=24
echo "t = ${!t}" # t = 24
table_cell_3=387
echo "Значение t изменяется на ${!t}" # 387
# Нет необходимости в 'eval'.
605
#+ а так же для имитации многомерного массива.
# Возможность индексации (по аналогии с указателями) была
#+ бы кстати. Вздох.
exit 0
# ============================================================== #
# Данные
B1723_value=470 # Омы
B1723_powerdissip=.25 # Ватты
B1723_colorcode="yellow-violet-brown" # Цвет полос
B1723_loc=173 # Где они
B1723_inventory=78 # Количество
B1724_value=1000
B1724_powerdissip=.25
B1724_colorcode="brown-black-red"
B1724_loc=24N
B1724_inventory=243
B1725_value=10000
B1725_powerdissip=.125
B1725_colorcode="brown-black-orange"
B1725_loc=24N
B1725_inventory=89
# ============================================================== #
echo
echo
echo
echo "Номер из каталоге $catalog_number:"
# Теперь получим значение, используя косвенные ссылки.
echo "Имеется ${!Inv} по [${!Val} ом / ${!Pdissip} ватт]\
резисторов на складе." # ^ ^
606
# Как и в Bash 4.2, можно заменить "омы" на \u2126 (с помощью echo -e).
echo "Находящиеся в корзине # ${!Loc}."
echo "Их цветной код \"${!Ccode}\"."
break
done
echo; echo
# Упражнения:
# ---------
# 1) Перепишите этот сценарий для чтения данных из внешнего файла.
# 2) Перепишите этот сценарий используя массивы,
#+ а не косвенные ссылки на переменную.
# Какой способ является более простым и интуитивно понятным?
# Какой способ легче записать кодом?
# Примечания:
# -----
# Сценарии оболочки неуместны нигде, за исключением самых простых приложений
#+ баз данных, и даже тогда они включают в себя обходные пути и кладжи.
# Намного лучше использовать языки с родной поддержкой структур данных,
#+ таких как C++ или Java (или даже Perl).
exit 0
UNPICKED=0
PICKED=1
DUPE_CARD=99
LOWER_LIMIT=0
UPPER_LIMIT=51
CARDS_IN_SUIT=13
CARDS=52
declare -a Deck
declare -a Suits
declare -a Cards
# Было бы проще реализовать и более интуитивно с единственным 3-х мерным
#+ массивом.
# Возможно, будущая версия Bash будет поддерживать многомерные массивы.
initialize_Deck ()
{
i=$LOWER_LIMIT
until [ "$i" -gt $UPPER_LIMIT ]
do
607
Deck[i]=$UNPICKED # Устанавливаем каждую карту "Deck" как не розданную.
let "i += 1"
done
echo
}
initialize_Suits ()
{
Suits[0]=C #Крести
Suits[1]=D #Буби
Suits[2]=H #Черви
Suits[3]=S #Пики
}
initialize_Cards ()
{
Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
# Еще один способ инициализации массива
}
pick_a_card ()
{
card_number=$RANDOM
let "card_number %= $CARDS" # Ограничиваем диапазон 0 - 51, т.е., 52 карты.
if [ "${Deck[card_number]}" -eq $UNPICKED ]
then
Deck[card_number]=$PICKED
return $card_number
else
return $DUPE_CARD
fi
}
parse_card ()
{
number=$1
let "suit_number = number / CARDS_IN_SUIT"
suit=${Suits[suit_number]}
echo -n "$suit-"
let "card_no = number % CARDS_IN_SUIT"
Card=${Cards[card_no]}
printf %-4s $Card
# Вывод карт аккуратными столбцами.
}
deal_cards ()
{
echo
cards_picked=0
while [ "$cards_picked" -le $UPPER_LIMIT ]
do
pick_a_card
t=$?
608
if [ "$t" -ne $DUPE_CARD ]
then
parse_card $t
u=$cards_picked+1
# Переключаемся обратно на 1 индексации, временно. Почему?
let "u %= $CARDS_IN_SUIT"
if [ "$u" -eq 0 ] # Вложеная проверка условий if/then.
then
echo
echo
fi # Каждая рука отделяется пустой строкой.
echo
return 0
}
# Структурное программирование:
# Вся логика программы модулирована в функции.
#===============
seed_random
initialize_Deck
initialize_Suits
initialize_Cards
deal_cards
#===============
exit
# Упражнение 1:
# Добавьте комментарии для тщательного документирования этого сценария.
# Упражнение 2:
# Добавление обычный (функция) вывод каждой руки, отсортированный по мастям.
# Если хотите, можете добавить любые няшки и красивки.
# Упражнение 3:
# Упростите и рационализируйте логику сценария.
609
• Новый, более обобщенный оператор скобок расширения {а...z}.
#!/bin/bash
for i in {1..10}
# Проще и еще проще, чем
#+ for i in $(seq 10)
do
echo -n "$i "
done
echo
# 1 2 3 4 5 6 7 8 9 10
echo {a..z} # a b c d e f g h i j k l m n o p q r s t u v w x y z
echo {e..m} # e f g h i j k l m
echo {z..a} # z y x w v u t s r q p o n m l k j i h g f e d c b a
# Срабатывает обратно.
echo {25..30} # 25 26 27 28 29 30
echo {3..-2} # 3 2 1 0 -1 -2
echo {X..d} # X Y Z [ ] ^ _ ` a b c d
# Показывает (некоторые) символы ASCII между Z и а,
#+ но не полагай1тесь на этот тип поведения, потому что...
echo {]..a} # {]..a}
# Почему?
start=0
end=10
610
for index in $(eval echo {$start..$end})
do
echo -n "$index " # 0 1 2 3 4 5 6 7 8 9 10
done
echo
echo ${!Array[@]} # 0 1 2 3
# Все элементы Array.
for i in ${!Array[@]}
do
echo ${Array[i]} # элемент-ноль
# элемент-один
# элемент-два
# элемент-три
#
# Все элементы в Array.
done
echo "$variable"
611
input=$1
if [[ "$input" =~ "[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]" ]]
# ^ ПРИМЕЧАНИЕ: Кавычки не обязательны, начиная с версии
#+ 3.2 of Bash.
# NNN-NN-NNNN (где каждая N является цифрой).
then
echo "Social Security number."
# Процесс SSN.
else
echo "Не Social Security number!"
# Или, попросить правильно ввести.
fi
Как это часто случается, пару сценариев в руководстве Advanced Bash Scripting
нужно исправить (например Пример 9-4).
a=1
echo $a # 1
a+=Hello
echo $a # 15Hello
612
Здесь =+ функционирует в качестве оператора объединения. Обратите внимание, что
его поведение в данном контексте отлично от конструкции let.
a=1
echo $a # 1
Jeffrey Haemer указывает на то, что этот оператор объединения может быть весьма
полезен. В случае добавления директории в $PATH.
bash$ echo $PATH
/usr/bin:/bin:/usr/local/bin:/usr/X11R6/bin/:/usr/games
bash$ PATH+=:/opt/bin
613
37.3. Bash, версия 4
Chet Ramey анонсировал версию 4 Bash 20 февраля, 2009. Этот релиз содержит ряд важных
новых возможностей, а также некоторые важные исправления.
Среди новых свойств:
• Ассоциативные массивы. [1]
declare -A address
# Опция -A объявляет ассоциативный массив.
echo
SUCCESS=0
E_DB=99 # Код ошибки для отсутствующей записи.
declare -A address
# Опция -A объявляет ассоциативный массив.
614
store_address ()
{
address[$1]="$2"
return $?
}
fetch_address ()
{
if [[ -z "${address[$1]}" ]]
then
echo "Адрес $1 в базе отсутствует."
return $E_DB
fi
# Упражнение:
# Перепишите store_address выше, что бы вызывать для чтения данные из
#+ файла и затем присваивать в массиве имени - поле , а адресу - поле 2.
# Каждая строка в файле должна иметь формат, как указано выше.
# С помощью цикла while-read читайте из файла, а с sed или awk
#+ анализируйте поля.
615
Пример 37-7. Проверка символов
#!/bin/bash4
test_char ()
{
case "$1" in
[[:print:]] ) echo "$1 это печатаемый символ.";;& # |
# Завершение ;;& продолжает проверку следующего шаблона. |
[[:alnum:]] ) echo "$1 это буквенный/цифровой символ.";;& # v
[[:alpha:]] ) echo "$1 это буквенный символ.";;& # v
[[:lower:]] ) echo "$1 это символ строчной буквы.";;&
[[:digit:]] ) echo "$1 это символ цифра.";& # |
# Завершение ;& выполняет следующий оператор ... # |
%%%@@@@@ ) echo "********************************";; # v
# ^^^^^^^^ ... даже дурацкого шаблона.
esac
}
echo
test_char 3
# 3 это печатаемый символ.
# 3 это буквенный/цифровой символ.
# 3 это цифровой символ.
# ********************************
echo
test_char m
# m это печатаемый символ.
# m это буквенный/цифровой символ.
# m это буквенный символ.
# m это символ строчной буквы.
echo
test_char /
# / это печатаемый символ.
echo
616
Это означает, что он может быть вызван явно в сценарии, а не просто
закулисным механизмом используемым Bash.
Сопроцессы используют дескрипторы файлов. Файловые дескрипторы позволяют
процессам и конвейерам взаимодействовать.
#!/bin/bash4
# Взаимодействие сопроцесса с циклом while-read.
Но будьте осторожны!
#!/bin/bash4
echo; echo
a=aaa
b=bbb
c=ccc
# a = one
# b = two
# c = three
# И т.д., прекрасно, но ...
echo "-----------------"
echo "Вне цикла while-read: "
echo "a = $a" # a =
echo "b = $b" # b =
echo "c = $c" # c =
echo "файловый дескриптор coproc: ${COPROC[0]}"
echo
# coproc все еще запущен, а ...
#+ до сих пор не включено наследование родительским процессом
#+ переменных из дочернего процесса, цикла while-read.
# Отрывок из сценария "[Link]".
617
Сопроцесс является асинхронным, а это может вызвать проблемы. Он
может прекратить общение до завершения другого процесса.
#!/bin/bash4
echo $cpname_PID
exit
618
• Встроенное read получило незначительное изменение. Опция timeout -t теперь
принимает дробные значения (десятичные) [3], а опция -i позволяет редактировать
предустановки буфера. [4] К сожалению эти улучшения все еще в разработке и их
(пока) нельзя использовать в сценариях.
• Подстановка параметров получила выбор операторов.
#!/bin/bash4
var=veryMixedUpVariable
echo ${var} # veryMixedUpVariable
echo ${var^} # VeryMixedUpVariable
# * Первый символ --> прописной.
echo ${var^^} # VERYMIXEDUPVARIABLE
# ** Все символы --> прописные.
echo ${var,} # veryMixedUpVariable
# * Первый символ --> строчный.
echo ${var,,} # verymixedupvariable
# ** Все символы --> строчные.
echo {40..60..2}
# 40 42 44 46 48 50 52 54 56 58 60
# Все четные числа, между 40 и 60.
echo {60..40..2}
# 60 58 56 54 52 50 48 46 44 42 40
# Все четные числа, между 40 и 60, обратный отсчет.
# Эффект уменьшения.
echo {60..40..-2}
# Тот же самый вывод. Знак минус не обязателен.
619
echo {X..d}
# X Y Z [ ] ^ _ ` a b c d
# Не выводит \ который экранирует пустое место.
E_BADPARAMS=99
if [ -z "$1" ]
then
echo "Usage $0 param1 ..."
exit $E_BADPARAMS
fi
echo ${@:0}
# $0 $1 $2 $3
620
# Опция о globstar новелла в версии 4 Bash.
exit
Использование *
allmyfiles
filelist.bash4
--------------
Исользование **
allmyfiles
allmyfiles/[Link]
allmyfiles/my_music
allmyfiles/my_music/[Link]
allmyfiles/my_music/[Link]
allmyfiles/my_music/[Link]
allmyfiles/my_pictures
allmyfiles/my_pictures/[Link]
allmyfiles/my_pictures/[Link]
filelist.bash4
command_not_found_handle ()
{ # Принимает неявные параметры.
echo "Следующая команда является не правильной: \""$1\"""
echo "Следующие аргументы: \""$2\"" \""$3\""" # $4, $5 ...
} # $1, $2, и т.д. не переданы функции явно.
621
Редакционный комментарий
num_chars=61
#!/bin/bash
# Требуется версия Bash -ge 4.1 ...
num_chars=61
622
#!/bin/bash
# [Link]
# Требуется версия Bash -ge 4.1 ...
echo "$multi_line_var"
unicode_var='\u2640'
echo -e $unicode_var # Женский символ
printf "$unicode_var \n" # Женский символ и новая строка
623
# А другие разрабатываются ...
symbol[script_E]='\u2130'
symbol[script_F]='\u2131'
symbol[script_J]='\u2110'
symbol[script_M]='\u2133'
symbol[Rx]='\u211E'
symbol[TEL]='\u2121'
symbol[FAX]='\u213B'
symbol[care_of]='\u2105'
symbol[account]='\u2100'
symbol[trademark]='\u2122'
echo
624
echo
array=( ноль один два три четыре пять ) # Шести элементный массив.
# 0 1 2 3 4 5
# -6 -5 -4 -3 -2 -1
# И ...
index=0
let "neg_element_count = 0 - ${#array[*]}"
# Количество элементов, преобразуемых в отрицательное число.
echo
625
• Извлечение Substring с помощью отрицательного параметра длины для указания
смещения от конца целевой строки.
Пример 37-12. Отрицательный параметр в конструкции извлечения string
#!/bin/bash
# Bash, version -ge 4.2
# Отрицательный индекс длины при извлечении substring.
# Важно: Это изменяет интерпретацию этой конструкции!
stringZ=abcABC123ABCabc
# abcABC123ABCabc
# Позиции в строке: 0123....6543210
echo ${stringZ:3:-6} # ABC123
# ^
# 3 индекса символов вперед от начала и 6 символов назад от конца,
#+ и извлекаем все между ними.
# ${string:смещение от начала:смещение от конца}
# Когда параметр "длина" является отрицательным,
#+ он становится параметром смещения от конца.
Примечания
[1] Более конкретно, Bash 4+ имеет ограниченную поддержку ассоциативных массивов.
Заметим, однако, что ассоциативные массивы в Bash, выполняются быстрее и более
эффективно, чем численно-индексированные массивы.
626
Глава 38. Заключение
Содержание
38.1. Примечания автора
38.2. Об авторе
38.3. Куда обращаться за помощью
38.4. Инструменты, использованные при написании этой книги
38.4.1. Оборудование
38.4.2. Программное обеспечение и печать
38.5. Благодарности
38.6. Правовая оговорка
Как я решил написать книгу о сценариях? Это странная история. Кажется, несколько лет
назад мне нужно было узнать как пишутся сценарии оболочки --а что может быть лучше, чем
прочитать хорошую книгу по этому вопросу? Я стал искать, чтобы купить учебник и
627
справочник, охватывающей все аспекты этого вопроса. Я искал книгу, которая будет
объяснять сложные концепции, показывать их изнанку и мучительно подробно, с хорошо
прокомментированными примерами, объяснять их. На самом деле, я искал что-то подобное
этой самой книге, или даже более, чем она. К сожалению ее не существовало, и если бы я
смог, я бы написал ее.
Это напоминает мне рассказ о безумном профессоре. Парень был сумасшедшим, психом.
Видя книги, любую книгу – в библиотеке, в книжном магазине, где угодно--он становился
одержим идеей, что он мог бы написать ее, он должен написать ее – и написать ее лучше. Он
спешил домой и воплощал это, писал книгу с точно таким же названием. Когда, несколько
лет спустя, он умер, у него нашлись несколько тысяч книг, вероятно он даже переложил
Азимова. Книги не дают никакой пользы тому кто знает, но другим они действительно
важны? Этот парень жил своими мечтами, даже был одержим ими, движим ими..., и я не
переставая любовался старым глупцом.
38.2. Об авторе
Кто же этот парень?
Автор утверждает, что не имеет ученой степени или специальной квалификации, а имел
только желание писать. Эта книга является своего рода форком (ответвлением от его крупной
работы, HOW-2 Meet Women: The Shy Man's Guide to Relationships. Он также написал
Software-Building HOWTO. В последнее время, он пробует себя в фантастике (тяжелой): Dave
Dawson Over Berlin (первое произведение), Dave Dawson Over Berlin (второе произведение) и
Dave Dawson Over Berlin (третье произведение). Он также написал несколько Инструкций.
Пользователь Linux с 1995 года ( Slackware 2.2, ядро 1.2.1), автор некоторого программного
обеспечения, включая утилиту одноразового шифрования cruft, mcalc - ипотечный
калькулятор, экспертный Scrabble® adjudicator, пакет словаря yawl и пакет аннаграммных
игр Quacky. Он начал с компьютерной игры -- программирования FORTRAN IV на CDC
3800 -- и это не последняя часть ностальгии по тем дням.
Живя в не согласии с женой и рыжей полосатой кошкой, он дорожит человеческой
слабостью, особенно его собственной.
Если вам нужна помощь с освоением, прочитайте соответствующие разделы этого и других
628
справочников. Сделайте все возможное, чтобы решить проблему используя свои
собственные ресурсы и ум. Пожалуйста, не тратьте время автора. Вы не получите ни
помощи, ни сочувствия. [1]
Пожалуйста, обратите внимание, что автор не будет отвечать о сценариях для систем
Solaris/Sun/Oracle или Apple. Эти фирмы используют судебные тяжбы, как оружие против
сообщества Open Source Community. Пользователи Solaris или Apple, нуждающиеся в
помощи сценарии, в виду прямой их озабоченности для обслуживания корпоративных
клиентов.
Примечания
[1] Ну если вы очень настаиваете, вы можете попробовать изменить Пример A-44 для
удовлетворения ваших потребностей.
629
38.5. Благодарности
Участие Сообщества сделало возможным этот проект. Автор с благодарностью признает,
что написание этой книги были бы немыслимо без помощи и обратной связи с этими
людьми.
Philippe Martin перевел первую версию (0.1) настоящего документа на DocBook/SGML.
Хотя и работает в небольшой французской компании, как разработчик программного
обеспечения, он пользуется рабочей документацией GNU/Linux и программным
обеспечением, читая литературу и слушая музыку для его успокоения. Вы можете запустить
через него что-нибудь для Франции или страны Басков, или отправить ему сообщение на
feloy@[Link].
Philippe Martin так же отметил, что позиционные параметры после $9 возможно
использовать в нотации {скобок}. (См. Пример 4-5).
630
Rick Boivie внес восхитительный сценарий рекурсии [Link] (Пример, 36-11), пересмотренный
сценарий [Link] (Пример A-16) и предложил улучшение производительности для сценария
[Link] (Пример, 16-47).
Florian Wisser просветил меня в некоторых тонкостях проверки строк (см. Пример 7-6) и по
другим вопросам.
Oleg Philon направил предложения относительно cut и pidof.
631
Очень особая благодарность трудолюбивым добровольцам Linux Documentation Project.
Хост LDP размещает репозиторий знаний Linux и эти знания в значительной степени и
позволили опубликовать эту книгу.
Благодарность и признательность IBM, Red Hat, Google, Free Software Foundation и всем
хорошим людям борющимся за то, что бы исходный код программного обеспечения был
свободным и открытым.
Запоздалое спасибо мой четвертой учительнице, Мисс Спенсер, за эмоциональную
поддержку и убеждение во мне того, что, может быть, я еще не совсем потерян.
Благодарю, прежде всего, мою жену, Anita, за ее поощрение, вдохновение и эмоциональную
поддержку.
Библиография
Те, кто не понимает UNIX, обречены на его
изобретение.
--Henry Spencer
Edited by Peter Denning, Computers Under Attack: Intruders, Worms, and Viruses, ACM Press,
1990, 0-201-53067-8.
Этот сборник содержит несколько статей о вирусах в сценариях shell.
*
632
Ken Burtch, Linux Shell Scripting with Bash, 1st edition, Sams Publishing (Pearson), 2004,
0672326426.
Охватывает большую часть материала ABS Guide, хотя и в другом стиле.
*
Daniel Goldman, Definitive Guide to Sed, 1st edition, 2013.
Эта электронная книга представляет собой отличное введение в sed. Вместо печатного
объема, она был специально разработана и отформатирована для просмотра и чтения
электронных книг. Хорошо написана, информативна и полезна как в качестве ссылки,
так и в качестве учебного пособия. Очень рекомендую.
*
Dale Dougherty and Arnold Robbins, Sed and Awk, 2nd edition, O'Reilly and Associates, 1997, 1-
156592-225-5.
Развертывание сценариев оболочки на полную мощность требует, по крайней мере,
знакомства с sed и awk. Это классическое руководство. Оно включает в себя
прекрасное введение в Регулярные выражения. Рекомендую.
*
Jeffrey Friedl, Mastering Regular Expressions, O'Reilly and Associates, 2002, 0-596-00289-0.
Одно из лучших изданий о Регулярных выражениях.
*
Aeleen Frisch, Essential System Administration, 3rd edition, O'Reilly and Associates, 2002, 0-596-
00343-9.
В этом превосходном руководстве содержится прекрасное введение в язык сценариев
командной оболочки с точки зрения системного администратора. Она включает в себя
исчерпывающие объяснения запуска и инициализации сценариев в системе UNIX.
*
Stephen Kochan and Patrick Wood, Unix Shell Programming, Hayden, 1990, 067248448X.
Все еще стандартное издание, хотя несколько устаревшее, и, стилистически, немного
'деревянное'. [1] На самом деле, эта книга была первым руководством автора ABS
Guide по сценариям оболочки UNIX, но много лет назад.
*
Neil Matthew and Richard Stones, Beginning Linux Programming, Wrox Press, 1996, 1874416680.
Удивительно хорошее, углубленное, освещение различных языков программирования
для Linux, где так же довольно сильны главы о сценариях оболочки.
*
633
Herbert Mayer, Advanced C Programming on the IBM PC, Windcrest Books, 1989, 0830693637.
Отличное освещение алгоритмов и общей практики программирования. Настоятельно
рекомендуется, но к сожалению больше не издается.
*
David Medinets, Unix Shell Programming Tools, McGraw-Hill, 1999, 0070397333.
Довольно хорошее издание о сценариях оболочки, с примерами, с кратким введением
в Tcl и Perl.
*
Cameron Newham and Bill Rosenblatt, Learning the Bash Shell, 2nd edition, O'Reilly and
Associates, 1998, 1-56592-347-2.
Это - отважное усилие в приличном учебнике для начинающих, но печально
несовершенное в охвате написания сценариев и не достаточное в примерах.
*
Anatole Olczak, Bourne Shell Quick Reference Guide, ASP, Inc., 1991, 093573922X.
Очень удобное карманное издание, несмотря на отсутствие освещения специфических
особенностей Bash.
*
Jerry Peek, Tim O'Reilly, and Mike Loukides, Unix Power Tools, 3rd edition, O'Reilly and
Associates, Random House, 2002, 0-596-00330-7.
Содержит пару частей очень информативных, углубленных статей о shell-
программировании, но не может претендовать на самоучитель. Она воспроизводит
большую часть знаний о Регулярных выражениях из книги Dougherty и Robbins, выше.
Всеобъемлющий охват команд UNIX делает эту книгу достойной места на вашей
полке.
*
Clifford Pickover, Computers, Pattern, Chaos, and Beauty, St. Martin's Press, 1990, 0-312-04123-
3.
Сокровищница идей и рецептов для компьютерного исследования математических
странностей.
*
George Polya, How To Solve It, Princeton University Press, 1973, 0-691-02356-5.
Классический учебник по методам решения проблем (алгоритмов), с уделением
особого внимания их изучению.
*
634
Chet Ramey and Brian Fox, The GNU Bash Reference Manual, Network Theory Ltd, 2003, 0-
9541617-7-7.
Это руководство является основным изданием по GNU Bash. Авторы данного
руководства, Chet Ramey и Brian Fox, являются оригинальными разработчиками GNU
Bash. С каждой проданой копии издатель жертвует $1 в Фонд свободного
программного обеспечения (Free Software Foundation).
*
Arnold Robbins, Bash Reference Card, SSC, 1998, 1-58731-010-5.
Отличный карманный справочник по Bash(не выходите без него из дома, особенно
если вы системный администратор). Стоит $4.95, и, к сожалению, больше не доступен
для бесплатного скачивания.
*
Arnold Robbins, Effective Awk Programming, Free Software Foundation / O'Reilly and Associates,
2000, 1-882114-26-4.
Справочник и абсолютный лучший учебник по awk. Бесплатная электронная версия
этой книги является частью документации awk, а печатные копии последней версии
доступны в O'Reilly and Associates.
Эта книга послужила источником вдохновения для автора ABS Guide.
*
Bill Rosenblatt, Learning the Korn Shell, O'Reilly and Associates, 1993, 1-56592-054-6.
Хорошо написаная книга содержащая несколько прекрасных указаний по сценариям
оболочки в целом.
*
Paul Sheer, LINUX: Rute User's Tutorial and Exposition, 1st edition, , 2002, 0-13-033351-4.
Очень подробное и легко читаемое введение в системной администрирование Linux.
Книга доступна в бумажном варианте или в варианте он-лайн.
*
Ellen Siever and the staff of O'Reilly and Associates, Linux in a Nutshell, 2nd edition, O'Reilly and
Associates, 1999, 1-56592-585-8.
Лучший и полнейший справочник по командам Linux. Он даже имеет раздел по Bash.
*
Dave Taylor, Wicked Cool Shell Scripts: 101 Scripts for Linux, Mac OS X, and Unix Systems, 1st
edition, No Starch Press, 2004, 1-59327-012-7.
635
Содержит очень много из того, что обещает название...
*
The UNIX CD Bookshelf, 3rd edition, O'Reilly and Associates, 2003, 0-596-00392-7.
Собрание из семи книг об UNIX на CD ROM, включающее UNIX Power Tools, Sed and
Awk, и Learning the Korn Shell. Полный набор всех справочников и учебников по
UNIX, которые вам могут когда-либо понадобиться, стоимость около $130. Купите,
даже если нужно залезть в долг и не платить арендную плату.
Новость: Кажется пропало из печати. Хорошо. Но Вы все еще можете купить издания
этих книг по отдельности.
*
Книги O'Reilly по Perl. (А так же, другие издания O'Reilly.)
***
Другие источники
(Перевод предоставляю самим интересующимся)
Fioretti, Marco, "Scripting for X Productivity," Linux Journal, Issue 113, September, 2003,
pp. 86-9.
Ben Okopnik's well-written introductory Bash scripting articles in issues 53, 54, 55, 57, and
59 of the Linux Gazette, and his explanation of "The Deep, Dark Secrets of Bash" in issue
56.
Chet Ramey's Bash - The GNU Shell, a two-part series published in issues 3 and 4 of the
Linux Journal, July-August 1994.
Mike G's Bash-Programming-Intro HOWTO.
Richard's Unix Scripting Universe.
Chet Ramey's Bash FAQ.
Greg's WIKI: Bash FAQ.
Example shell scripts at Lucc's Shell Scripts .
Example shell scripts at SHELLdorado .
Example shell scripts at Noah Friedman's script site.
Examples from the The Bash Scripting Cookbook, by Albing, Vossen, and Newham.
Example shell scripts at zazzybob.
Steve Parker's Shell Programming Stuff. In fact, all of his shell scripting books are highly
recommended. See also Steve's Arcade Games written in a shell script.
636
An excellent collection of Bash scripting tips, tricks, and resources at the Bash Hackers
Wiki.
Giles Orr's Bash-Prompt HOWTO.
The Pixelbeat command-line reference.
Very nice sed, awk, and regular expression tutorials at The UNIX Grymoire.
The GNU sed and gawk manuals. As you recall, gawk is the enhanced GNU version of awk.
Many interesting sed scripts at the seder's grab bag.
Tips and tricks at Linux Reviews.
Trent Fisher's groff tutorial.
David Wheeler's Filenames in Shell essay.
"Shelltris" and "shellitaire" at Shell Script Games.
YongYe's wonderfully complex Tetris game script.
Mark Komarinski's Printing-Usage HOWTO.
The Linux USB subsystem (helpful in writing scripts affecting USB peripherals).
There is some nice material on I/O redirection in chapter 10 of the textutils documentation at
the University of Alberta site.
Rick Hohensee has written the osimpa i386 assembler entirely as Bash scripts.
dgatwood has a very nice shell script games site, featuring a Tetris® clone and solitaire.
Aurelio Marinho Jargas has written a Regular expression wizard. He has also written an
informative book on Regular Expressions, in Portuguese.
Ben Tomkins has created the Bash Navigator directory management tool.
William Park has been working on a project to incorporate certain Awk and Python features
into Bash. Among these is a gdbm interface. He has released bashdiff on [Link]. He
has an article in the November, 2004 issue of the Linux Gazette on adding string functions to
Bash, with a followup article in the December issue, and yet another in the January, 2005
issue.
Peter Knowles has written an elaborate Bash script that generates a book list on the Sony
Librie e-book reader. This useful tool facilitates loading non-DRM user content on the
Librie (and the newer PRS-xxx-series devices).
Tim Waugh's xmlto is an elaborate Bash script for converting Docbook XML documents to
other formats.
Philip Patterson's logforbash logging/debugging script.
AuctionGallery, an application for eBay "power sellers" coded in Bash.
637
Of historical interest are Colin Needham's original International Movie Database (IMDB)
reader polling scripts, which nicely illustrate the use of awk for string parsing.
Unfortunately, the URL link is broken.
---
Fritz Mehner has written a bash-support plugin for the vim text editor. He has also also come
up with his own stylesheet for Bash. Compare it with the ABS Guide Unofficial Stylesheet.
---
Penguin Pete has quite a number of shell scripting tips and hints on his superb site. Highly
recommended.
The excellent Bash Reference Manual, by Chet Ramey and Brian Fox, distributed as part of
the bash-2-doc package (available as an rpm). See especially the instructive example scripts
in this package.
John Lion's classic, A Commentary on the Sixth Edition UNIX Operating System.
The [Link] newsgroup.
The dd thread on Linux Questions.
The [Link] FAQ.
Assorted [Link] FAQs.
The Wikipedia article covering dc.
The manpages for bash and bash2, date, expect, expr, find, grep, gzip, ln,
patch, tar, tr, bc, xargs. The texinfo documentation on bash, dd, m4, gawk, and sed.
Примечания
[1] Это, очевидно, трудно переводимый каламбур. Эта книга является довольно
приличным введением в основные понятия сценариев оболочки.
638
# [Link] (ver. 1.1): Форматирование сообщений e-mail.
# =================================================================
# Обычная проверка аргумента(ов) сценария
ARGS=1
E_BADARGS=85
E_NOFILE=86
# =================================
# Переменная может содержать сценарий sed.
# Это полезный прием.
sedscript='s/^>//
s/^ *>//
s/^ *//
s/ *//'
# =================================
exit $?
639
#
# Утилита "ren", Vladimir Lanin (lanin@[Link]),
#+ работает гораздо лучше.
ARGS=2
E_BADARGS=85
ONE=1 # Получаеn единственное/множественное
#+ число (см. ниже).
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` старый_шаблон новый_шаблон"
# Как и в "rn gif jpg", которая переименовывает все файлы gif
#+ в рабочей директории в jpg.
exit $E_BADARGS
fi
exit $?
# Упражнения:
# ---------
# С какими типами файлов этот сценарий не будет работать?
# Как это можно исправить?
640
number=0 # Отслеживает, сколько переименовано файлов.
FOUND=0 # Возвращаемое значение при успехе.
exit 0
E_BADARGS=85
if [ -z "$1" ]
then
echo "Usage: `basename $0` имя_файла"
exit $E_BADARGS
fi
Server="XXX"
Directory="YYY" # Измените имя сервера, выше, и директории.
641
#+ [Link]
#+ [Link] [16k]
exit 0
642
echo "Удалить образ файла (y/n)? " # Возможно большой файл.
read answer
case "$answer" in
[yY]) rm -f $OF
echo "$OF удален."
;;
*) echo "$OF не удален.";;
esac
echo
# Упражнение:
# Измените объявление "case" на согласие "yes" и "Yes" с ввода.
exit 0
MAX_ITERATIONS=200
# Для больших исходных чисел (>32000), попробуйте увеличить MAX_ITERATIONS.
echo
echo "C($h) -*- $MAX_ITERATIONS повторений"
echo
643
# табуляция
# printf сделает это получше ...
COLWIDTH=%7d
printf $COLWIDTH $h
done
echo
exit 0
644
day= # Объявление глобальных дня, месяца, года.
month=
year=
645
echo $Days
Parse_Date $1
check_date $day $month $year # Проверяем правильность даты.
Parse_Date $2
check_date $day $month $year
strip_leading_zero $day
day=$?
strip_leading_zero $month
month=$?
echo $diff
exit 0
646
# Упражнение:
# --------
# Если указать только один параметр командной строки, то пусть сценарий
#+ использует сегодняшнюю дату в качестве второго параметра.
E_BADARGS=85
exit $?
647
Пример A-9. Преобразование Soundex
#!/bin/bash
# [Link]: Расчет кода "soundex" для имен
# =======================================================
# Сценарий Soundex
# Mendel Cooper
# [Link]@[Link]
# reldate: 23 January, 2002
#
# Размещен с Public Domain.
#
# Различные легкие версии этого сценария размещены
#+ Ed Schaefer July, 2002 "Shell Corner" column
#+ в "Unix Review" on-line,
#+ [Link]
# =======================================================
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` имя"
exit $E_WRONGARGS
fi
val1=bfpv # 'b,f,p,v' = 1
val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2
val3=dt # и т.д.
val4=l
val5=mn
val6=r
input_name="$1"
echo
648
echo "Имя = $input_name"
assign_value $name
s1=$value
assign_value $name1
s2=$value
assign_value $char1
s3=$value
s3=9$s3 # Если первая буква имени гласная
#+ или 'w' или 'h',
#+ то ее "значение" будет null (не
#+ присвоено). Поэтому присваиваем ей
#+ 9, иначе значение невозможно будет
#+ проверить.
649
soun=$prefix$suffix$padding # Место с нулями.
echo
# Примеры:
# Smith и Smythe оба будут в soundex "S-530".
# Harrison = H-625
# Hargison = H-622
# Harriman = H-655
# Упражнение:
# --------
# Упростите часть "Исключения" этого сценария.
exit 0
# ##################################################################### #
# Этот сценарий Bash является версией John Conway "Game of Life". #
# "Life" это простая реализация клеточного автомата. #
# --------------------------------------------------------------------- #
# Прямоугольная сетка, на которой каждая "ячейка" может быть #
# "живой" или "мертвой". #
# Обозначаем живую ячейку точкой, а мертвую пустым пространством. #
# Выберите произвольно на сетке ячейку с точкой и пустую, #
650
#+ и пусть это будет началом создания: generation 0. #
# Определяйте каждую последующую генерацию по следующим правилам #
# 1) Каждая ячейка имеет 8 соседних, смежных ячеек #
#+ слева, справа, сверху, снизу и 4 по диагонали. #
# #
# 123 #
# 4*5 * является рассматриваемой ячейкой. #
# 678 #
# #
# 2) Живая ячейка с 2 или 3 живыми соседними ячейками - остается живой. #
SURVIVE=2 #
# 3) Мертвая ячейка с 3 живыми соседними оживает, "рождается". #
BIRTH=3 #
# 4) Все остальные случаи являются мертвыми ячейками для #
# следующих генераций. #
# ##################################################################### #
############################################
# Сценарий прерывается, если «startfile» не указан
#+ и
#+ отсутствует файл по умолчанию "gen0".
E_NOSTARTFILE=86
if [ ! -e "$startfile" ]
then
echo "Начальный файл \""$startfile"\" отсутствует!"
exit $E_NOSTARTFILE
fi
############################################
ALIVE1=.
DEAD1=_
# Представляем живые и мертвые ячейки в начальном файле.
# -----------------------------------------------------#
# Этот сценарий использует сетку 10 x 10 (можно изменить,
#+ но с большой сеткой выполняться будет медленно).
ROWS=10
COLS=10
# Измените две переменные выше в соответствии с желаемым размером сетки.
# -----------------------------------------------------#
651
FALSE=1
ALIVE=0
DEAD=1
# =================================================================
display ()
{
declare -a arr
arr=( `echo "$1"` ) # Преобразование переданных аргументов в массив.
element_count=${#arr[*]}
local i
local rowcheck
cell=${arr[i]}
if [ "$cell" = . ]
then
let "alive += 1"
fi
return
652
fi
local row
local lower_limit=0 # Запрещаем отрицательные координаты.
local upper_limit
local left
local right
row=$2
let "left = $row * $COLS" # Ограничение слева.
let "right = $left + $COLS - 1" # Ограничение справа.
653
local r
local row
local i
local t_top
local t_cen
local t_bot
local count=0
local ROW_NHBD=3
done
if [ ${array[$cell_number]} = "$ALIVE1" ]
then
let "count -= 1" # Убедитесь, что значение самих проверенных
fi #+ ячеек не подсчитано.
654
return $count
local array
local i=0
if [ "$alive" -eq 0 ]
then
echo
echo "Преждевременный выход: нет больше живых ячеек!"
exit $NONE_ALIVE # Окончание,
fi #+ если нет живых ячеек.
# =========================================================
# main ()
# {
echo # Название
655
setterm -reverse on
echo "======================="
setterm -reverse off
echo " Генерация $GENERATIONS"
echo " закончена"
echo "\"Life in the Slow Lane\""
setterm -reverse on
echo "======================="
setterm -reverse off
echo
# }
exit 0 # CEOF:EOF
656
# Упражнение: Измените этот сценарий, что бы он мог определять
#+ размер сетки из файла «gen0» и задавать любые
#+ переменные, необходимые для запуска сценария.
# Это сделало бы ненужным любые замены переменных в
#+ сценарии при изменении сетки.
#
# Упражнение: Оптимизируйте этот сценарий.
# Он имеет избыточный код.
+++
Следующий сценарий Mark Moraes из университета Торонто. См. файл Moraes-
COPYRIGHT о правах и ограничениях.
if [ $# -eq 0 ]; then
# ==> Если нет аргумента командной строки, то
#+ перенаправляется из файла на stdin.
sed -e '1,/^$/d' -e '/^[ ]*$/d'
# --> Удаляются пустые строки и все строки до
# --> строки начинающейся с отступа.
657
else
# ==> Если есть аргумент командной строки, то из указанного файла.
for i do
sed -e '1,/^$/d' -e '/^[ ]*$/d' $i
# --> То же самое, как выше.
done
fi
exit
/*
* Copyright University of Toronto 1988, 1989.
* Written by Mark Moraes
*
* Permission is granted to anyone to use this software for any purpose on
* any computer system, and to alter it and redistribute it freely, subject
* to the following restrictions:
*
* 1. The author and the University of Toronto are not responsible
* for the consequences of use of this software, no matter how awful,
* even if they arise from flaws in it.
*
* 2. The origin of this software must not be misrepresented, either by
* explicit claim or by omission. Since few users ever read sources,
* credits must appear in the documentation.
*
* 3. Altered versions must be plainly marked as such, and must not be
* misrepresented as being the original software. Since few users
* ever read sources, credits must appear in the documentation.
*
* 4. This notice may not be removed or altered.
*/
+
Antek Sawicki предоставил следующий сценарий, реализующий параметр подстановки
операторов, обсуждающегося в Разделе 10.2.
Пример A-13. password: Создание случайного 8-символьного пароля
#!/bin/bash
#
#
# Генератор случайных паролей для Bash 2.x+
#+ Antek Sawicki <tenox@[Link]>,
#+ который великодушно дал разрешение использование в ABS Guide.
#
# ==> Комментарии добавленные автором документа ==>
MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
# ==> Пароль содержит буквенно-цифровые символы.
LENGTH="8"
658
# ==> Для увеличения пароля 'LENGTH' можно изменить.
# ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}
# ==> возвращает расширение MATRIX случайной позиции, длиной 1.
# ==> См подстановку параметров {var:pos:len} в Главе 9.
# ==> и сравни примеры.
let n+=1
# ==> При следующем проходе 'n' увеличивается.
done
exit 0
+
James R. Van Zandt предоставил этот сценарий, использующий именованные каналы и, с его
слов , это "хорошее упражнение для кавычек и экранирования."
659
# Убедитесь, что /pipe действительно является каналом, а не просто файлом.
rm -rf /pipe
mkfifo /pipe # ==> Создается "именованный канал", назвав "/pipe" ...
exit 0
+
Stéphane Chazelas использует следующий сценарий для демонстрации создания простых
чисел без помощи массивов.
Пример A-15. Создание простых чисел с помощью оператора модуль
#!/bin/bash
# [Link]: Создание простых чисел без использования массива.
# Сценарий предоставлен Stephane Chazelas.
Primes()
{
(( n = $1 + 1 )) # Переход к следующему целому числу.
shift # Следующий параметр в списке.
# echo "_n=$n i=$i_"
if (( n == LIMIT ))
then echo $*
return
fi
660
return
done
Primes 1
exit $?
+
Rick Boivie пересмотрел сценарий tree, написаный Jordi Sanfeliu.
Пример A-16. tree: Вывод на экран дерева директорий
#!/bin/bash
# [Link]
search () {
for dir in `echo *`
# ==> `echo *` Список всех файлов в текущей рабочей директории,
#+ ==> без пробелов.
# ==> Подобный эффект дает for dir in *
# ==> но "dir in `echo *`" не будет обрабатывать имена с пробелами.
do
if [ -d "$dir" ] ; then # ==> Если это директория (-d)...
zz=0 # ==> Временная переменная, следит
# за уровнем директории.
while [ $zz != $1 ] # Следит за вложенным внутрь цикла.
do
echo -n "| " # ==> Выводит вертикальную черту,
# ==> с 2 пробелами & без перевода строки
# для задания отступа.
zz=`expr $zz + 1` # ==> zz увеличивается.
661
done
if [ $# != 0 ] ; then
cd $1 # Переходим в указанную директорию.
#else # Остаемся в текущей директории
fi
search 0
echo "Всего директорий = $numdirs"
exit 0
662
TOP=5 # Список 5 крупнейших (под)директорий.
MAXRECURS=5 # Максимальная глубина 5 поддиректорий/рекурсий.
E_BL=80 # Возвращение пустой строки.
E_DIR=81 # Директория не задана.
cnt=0
num=`echo "$LIST" | wc -l` # Количество записей в списке.
## Основной цикл
echo "$LIST" | while read size name; do
((cnt+=1)) # Счетчик числа записей.
bname=`basename "$name"` # В записи нам необходимо только basename.
[ -d "$name" ] && bname="$bname/"
# Если это директория, добавляется слэш.
echo "`dot $size`$prefix +-$bname"
# Выводится результат.
# Вызывает себя рекурсивно, если это директория и
#+ мы не слишком глубоко вошли($MAXRECURS).
# Переход рекурсии: $((recurs+1))
# Префикс получает пробел, если это последняя запись
#+ или канал, если еще есть записи.
# Минимальный размер файла/директории становится
#+ десятой долей их родителя: $((size/10)).
# Последний аргумент это проверка полного имени директории.
if [ -d "$name" -a $recurs -lt $MAXRECURS ]; then
[ $cnt -lt $num ] \
|| (tree $((recurs+1)) "$prefix " $((size/10)) "$name") \
&& (tree $((recurs+1)) "$prefix |" $((size/10)) "$name")
fi
done
663
### ###
### Основная программа ###
### ###
rootdir="$@"
[ -d "$rootdir" ] ||
{ echo "$SELF: Usage: $SELF <directory>" >&2; exit $E_DIR; }
# Должно вызываться с именем директории.
rm "$TMP" 2>/dev/null
# Очищаем файл TMP.
exit $?
# Комментарии:
# Код:
#:docstring strcat:
# Usage: strcat s1 s2
#
# Strcat добавляет значение variable s2 в variable s1.
#
# Пример:
# a="foo"
# b="bar"
# strcat a b
# echo $a
# => foobar
#
664
#:end docstring:
#:docstring strncat:
# Usage: strncat s1 s2 $n
#
# Подобна strcat, но strncat добавляет максимально n символов из значения
# variable s2. Если значение variable s2 меньше, чем n символов,
# то копируется меньше символов. Выводит результат на stdout.
#
# Пример:
# a=foo
# b=barbaz
# strncat a b 3
# echo $a
# => foobar
#
#:end docstring:
###;;;autoload
function strncat ()
{
local s1="$1"
local s2="$2"
local -i n="$3"
local s1_val s2_val
eval "$s1"=\'"${s1_val}${s2_val}"\'
# ==> eval $1='${s1_val}${s2_val}' позволяет избежать проблем,
# ==> Если каждая переменная находится внутри одинарных кавычек.
}
#:docstring strcmp:
# Usage: strcmp $s1 $s2
#
# Strcmp сравнивает аргументы и возвращает целое число, меньше,равно,
# или больше, чем ноль, в зависимости от указанного для string s1
# лексикографического знака: меньше чем, равно, или больше чем string s2.
#:end docstring:
###;;;autoload
function strcmp ()
{
665
[ "$1" = "$2" ] && return 0
return 1
}
#:docstring strncmp:
# Usage: strncmp $s1 $s2 $n
#
# Подобна strcmp, но делает сравнение проверкой максимума n
# символов (n меньше чем или равно нулю следующего равенства).
#:end docstring:
###;;;autoload
function strncmp ()
{
if [ -z "${3}" -o "${3}" -le "0" ]; then
return 0
fi
#:docstring strlen:
# Usage: strlen s
#
# Strlen возвращает количество символов буквы s в строке.
#:end docstring:
###;;;autoload
function strlen ()
{
eval echo "\${#${1}}"
# ==> Возвращает длину значения переменной,
# ==> имя которой передано в качестве аргумента.
}
#:docstring strspn:
# Usage: strspn $s1 $s2
#
# Strspn возвращает максимальную длину исходного сегмента строки s1,
# который полностью состоит из символов строки s2.
#:end docstring:
###;;;autoload
function strspn ()
{
# Не объявленный IFS позволяет обрабатывать пробелы, как обычные символы.
local IFS=
local result="${1%%[!${2}]*}"
echo ${#result}
666
}
#:docstring strcspn:
# Usage: strcspn $s1 $s2
#
# Strcspn возвращает максимальную длину исходного сегмента строки s1,
# которая состоит исключительно из символов не входящих в строку s2.
#:end docstring:
###;;;autoload
function strcspn ()
{
# Не объявленный IFS позволяет обрабатывать пробелы как обычные символы.
local IFS=
local result="${1%%[${2}]*}"
echo ${#result}
}
#:docstring strstr:
# Usage: strstr s1 s2
#
# Strstr выводит substring, начиная с первого вхождения строки s2 в
# строке s1, или ничего, если s2 не встречается в строке. Если s2 это
# строка нулевой длинны, то strstr выводит s1.
#:end docstring:
###;;;autoload
function strstr ()
{
# Если s2 это строка нулевой длины, strstr выводит s1
[ ${#2} -eq 0 ] && { echo "$1" ; return 0; }
#:docstring strtok:
# Usage: strtok s1 s2
#
# Strtok считает строки s1, состоящие из последовательностей нулей или других
# текстовых маркеров, разделяя диапазоны одним или более символов из
# строки s2. Первой вызывается строка (с указанной не пустой строкой s1)
# содержащая первый маркер, выводимая на stdout. Эта функция отслеживает
# свою позицию в строке s1 между вызовами, поэтому последующие
# вызовы, сделанные с первым аргументом пустой строки, будут работать сразу
# после этого маркера построчно. Таким образом последующие вызовы будут
# обрабатывать построчно s1 до тех пор, пока не останется маркеров.
# Разделитель строки s2 может отличаться от вызова к вызову. Когда
# заканчиваются маркеры в строке s1, на stdout выводится пустое значение.
#:end docstring:
667
###;;;autoload
function strtok ()
{
:
}
#:docstring strtrunc:
# Usage: strtrunc $n $s1 {$s2} {$...}
#
# Используется многими функциями, типа strncmp для уменьшения количества
# аргументов для сравнения.
# Выводит первые n символов каждой строки s1 s2 ... на stdout.
#:end docstring:
###;;;autoload
function strtrunc ()
{
n=$1 ; shift
for z; do
echo "${z:0:$n}"
done
}
# предоставленная строка
# ========================================================================== #
# ==> Все ниже добавлено автором документа.
# strcat
string0=one
string1=two
echo
echo "Проверка функции \"strcat\":"
echo "Изначально \"string0\" = $string0"
echo "\"string1\" = $string1"
strcat string0 string1
echo "Новая \"string0\" = $string0"
echo
# strlen
echo
echo "Проверка функции \"strlen\":"
str=123456789
echo "\"str\" = $str"
echo -n "Длина \"str\" = "
strlen str
echo
# Упражнение:
# --------
# Добавьте код для проверки всех других строковых функций выше.
668
exit 0
# Управление
# Если переопределяются аргументы командной строки, то они должны быть в
#+ следующем порядке:
# Arg1: "Дескриптор директории"
# Arg2: "Исключенные пути"
# Arg3: "Исключенные директории"
#
# Параметры среды переопределяют значения по умолчанию.
# Команды аргументов переопределяют параметры среды.
669
размер день месяц дата hh:mm:ss год путь
2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core
ListDirectory()
{
local -a T
670
local -i of=0 # Возвращение переменной по умолчанию
# OLD_IFS=$IFS # В BASH по умолчанию ' \t\n'
case "$#" in
3) case "$1" in
-of) of=1 ; shift ;;
* ) return 1 ;;
esac ;;
2) : ;; # "continue"
*) return 1 ;;
esac
case $of in
# Назначаем Т обратно массиву, имя которого было передано как $2
0) eval $2=\( \"\$\{T\[@\]\}\" \) ;;
# Пишем T в файл переданный как $2
1) echo "${T[@]}" > "$2" ;;
esac
return 0
}
IsNumber()
{
local -i int
if [ $# -eq 0 ]
then
return 1
else
(let int=$1) 2>/dev/null
return $? # Статус выхода потока let
fi
}
: <<IndexListDoc
Проход по массиву полей директории произведенный ListDirectory
Каждая строка получает два индекса, первого элемента каждой строки (inode) и
элемента, содержащего путь к файлу.
Первая запись пары индексов (Строка-номер==0) информационная:
671
Index-Array-Name[0] : Количество индексированных "Строк"
Index-Array-Name[1] : "Текущая строка" указанная в Index-Array-Name
IndexList()
{
local -a LIST # Локальное переданное имя списка
local -a -i INDEX=( 0 0 ) # Локальный возвращаемый индекс
local -i Lidx Lcnt
local -i if=0 of=0 # Имя переменной по умолчанию
Lcnt=${#LIST[@]}
Lidx=0
until (( Lidx >= Lcnt ))
do
if IsNumber ${LIST[$Lidx]}
then
local -i inode name
local ft
inode=Lidx
local m=${LIST[$Lidx+2]} # Поле жестких ссылок
ft=${LIST[$Lidx+1]:0:1} # Быстрое состояние
case $ft in
b) ((Lidx+=12)) ;; # Блочное устройство
c) ((Lidx+=12)) ;; # Символьное устройство
*) ((Lidx+=11)) ;; # Что-то еще
esac
name=Lidx
case $ft in
-) ((Lidx+=1)) ;; # Легкий
672
b) ((Lidx+=1)) ;; # Блочное устройство
c) ((Lidx+=1)) ;; # Символьное устройство
d) ((Lidx+=1)) ;; # Другой легкий
l) ((Lidx+=3)) ;; # Еще по КРАЙНЕЙ МЕРЕ два поля
# A здесь более элегантная обработка каналов,
#+ сокетов, затем - удаляемых файлов
*) until IsNumber ${LIST[$Lidx]} || ((Lidx >= Lcnt))
do
((Lidx+=1))
done
;; # Не нужно
esac
INDEX[${#INDEX[*]}]=$inode
INDEX[${#INDEX[*]}]=$name
INDEX[0]=${INDEX[0]}+1 # Найдена еще одна "строка"
# echo "Строка: ${INDEX[0]} Тип: $ft Ссылки: $m Иноды: \
# ${LIST[$inode]} Имя: ${LIST[$name]}"
else
((Lidx+=1))
fi
done
case "$of" in
0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;;
1) echo "${INDEX[@]}" > "$2" ;;
esac
return 0 # Что может пойти не так?
}
DigestFilesDoc
673
DigestFile()
{
local if=0 # Имя переменной, по умолчанию
local -a T1 T2
case "$#" in
3) case "$1" in
-if) if=1 ; shift ;;
* ) return 1 ;;
esac ;;
2) : ;; # "continue"
*) return 1 ;;
esac
case $if in
0) eval T1=\( \"\$\{$1\[@\]\}\" \)
T2=( $(echo ${T1[@]} | md5sum -) )
;;
1) T2=( $(md5sum $1) )
;;
esac
case ${#T2[@]} in
0) return 1 ;;
1) return 1 ;;
2) case ${T2[1]:0:1} in # SanScrit-2.0.5
\*) T2[${#T2[@]}]=${T2[1]:1}
T2[1]=\*
;;
*) T2[${#T2[@]}]=${T2[1]}
T2[1]=" "
;;
esac
;;
3) : ;; # Допустим это сработало
*) return 1 ;;
esac
local -i len=${#T2[0]}
if [ $len -ne 32 ] ; then return 1 ; fi
eval $2=\( \"\$\{T2\[@\]\}\" \)
}
# # # # # Расположение файла # # # # #
#
# LocateFile [-l] FileName Location-Array-Name
# или
# LocateFile [-l] -of FileName Location-Array-FileName
# # # # #
674
[2] Число находящихся блоков (по 512 байт)
[3] Тип файла и права доступа (hex)
[4] ID пользователя
[5] ID группы
[6] Количество оборудования
[7] Количество Inode
[8] Число жестких ссылок
[9] Основной тип оборудования (если inode оборудования)
[10] Младший тип оборудования (если inode оборудования)
[11] Время последнего доступа
Может быть отключено в «mount» noatime atime в файлах
измененных exec, read, pipe, utime atime mknod (mmap?),
в директориях, измененных путем добавления/удаления файлов
[12] Время последней модификации
mtime файлов модифицированных write, truncate, utime, mknod
mtime директорий измененных добавлением/удалением файлов
[13] Время последнего изменения
ctime отражает время изменения информации inode (владелец,
группа права, количество ссылок)
-*-*- Для:
Возвращаемый код: 0
Размер массива: 14
Содержимое массива
Элемент 0: /home/mszick
Элемент 1: 4096
Элемент 2: 8
Элемент 3: 41e8
Элемент 4: 500
Элемент 5: 500
Элемент 6: 303
Элемент 7: 32385
Элемент 8: 22
Элемент 9: 0
Элемент 10: 0
Элемент 11: 1051221030
Элемент 12: 1051214068
Элемент 13: 1051214068
675
[10] Свободные inode
-*-*- Для:
Возвращаемый код: 0
Размер массива: 11
Содержимое массива
Элемент 0: /home/mszick
Элемент 1: 0
Элемент 2: 0
Элемент 3: 255
Элемент 4: ef53
Элемент 5: 2581445
Элемент 6: 2277180
Элемент 7: 2146050
Элемент 8: 4096
Элемент 9: 1311552
Элемент 10: 1276425
StatFieldsDoc
LocateFile()
{
local -a LOC LOC1 LOC2
local lk="" of=0
case "$#" in
0) return 1 ;;
1) return 1 ;;
2) : ;;
*) while (( "$#" > 2 ))
do
case "$1" in
-l) lk=-1 ;;
-of) of=1 ;;
*) return 1 ;;
esac
shift
done ;;
esac
# Далее Sanscrit-2.0.5
# LOC1=( $(stat -t $lk $1) )
# LOC2=( $(stat -tf $lk $1) )
# Если в системе установлена команда "stat", раскомментируйте
#+ две строки выше.
LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11}
${LOC2[@]:1:2} ${LOC2[@]:4:1} )
case "$of" in
0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;;
1) echo "${LOC[@]}" > "$2" ;;
esac
return 0
# Что получите (если вам повезло, и установлена «stat»)
# -*-*- Расположение дескриптора -*-*-
# Возвращаемый код: 0
# Размер массива: 15
676
# Содержимое массива
# Элемент 0: /home/mszick Имя 20th Century
# Элемент 1: 41e8 Тип и права доступа
# Элемент 2: 500 Пользователь
# Элемент 3: 500 Группа
# Элемент 4: 303 Устройство
# Элемент 5: 32385 inode
# Элемент 6: 22 Количество ссылок
# Элемент 7: 0 Главное устройство
# Элемент 8: 0 Младшее устройство
# Элемент 9: 1051224608 Последний доступ
# Элемент 10: 1051214068 Последняя модификация
# Элемент 11: 1051214068 Последнее состояние
# Элемент 12: 0 UUID (может быть)
# Элемент 13: 0 Значение Label (может быть)
# Элемент 14: ef53 Тип ФС
}
declare -a CUR_DIR
# Для небольших массивов
ListDirectory "${PWD}" CUR_DIR
ListArray CUR_DIR
declare -a DIR_DIG
DigestFile CUR_DIR DIR_DIG
echo "Новое \"имя\" (контрольная сумма) для ${CUR_DIR[9]} это ${DIR_DIG[0]}"
declare -a DIR_ENT
# BIG_DIR # Для очень больших массивов — используем временный файл на ramdisk
# BIG-DIR # ListDirectory -of "${CUR_DIR[11]}/*" "/tmpfs/junk2"
ListDirectory "${CUR_DIR[11]}/*" DIR_ENT
declare -a DIR_IDX
# BIG-DIR # IndexList -if "/tmpfs/junk2" DIR_IDX
IndexList DIR_ENT DIR_IDX
declare -a IDX_DIG
# BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) )
# BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG
DigestFile DIR_ENT IDX_DIG
# Маленькие (должны) будут распараллеленны IndexList & DigestFile
# Большие (должны) будут распараллеленны IndexList & DigestFile & назначение
677
echo "\"Имя\" (контрольная сумма) для содержимого ${PWD} это ${IDX_DIG[0]}"
declare -a FILE_LOC
LocateFile ${PWD} FILE_LOC
ListArray FILE_LOC
exit 0
# Ограничения:
# * Поддерживаются только глобальные переменные.
# * Каждый экземпляр хэш создает одно значение одной глобальной переменной.
# * Возможны коллизии имен переменных, если вы
#+ определяете переменную как __hash__hashname_key
# * Ключи должны использовать символы которые могут быть частью имен
#+ переменных Bash (без тире, точек, и т.д.).
# * Хэш создается как переменная:
# ... hashname_keyname
# Так что если кто-то создаст хэши как:
# myhash_ + mykey = myhash__mykey
# myhash + _mykey = myhash__mykey ,
# то может произойти коллизия.
# (Это не должно стать серьезной проблемой.)
Hash_config_varname_prefix=__hash__
# Эмуляция: хэш[ключ]=значение
#
# Параметры:
# 1 - хэш
# 2 - ключ
# 3 - значение
function hash_set {
eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\""
}
# Эмуляция: значение=хэш[ключ]
#
# Параметры:
# 1 - хэш
# 2 - ключ
# 3 - значение (имя присвоенное глобальной переменной)
function hash_get_into {
678
eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\""
}
function hash_echo {
eval "echo $3 \"\$${Hash_config_varname_prefix}${1}_${2}\""
}
# Эмуляция: хэш[ключ1]=хэш[ключ2]
#
# Параметры:
# 1 - хэш1
# 2 - ключ1
# 3 - хэш2
# 4 - ключ2
function hash_copy {
eval "${Hash_config_varname_prefix}${1}_${2}\
=\"\$${Hash_config_varname_prefix}${3}_${4}\""
}
# Эмуляция: хэш[ключN-1]=хэш[ключ2]=...хэш[ключ1]
#
# Копирование первого ключа в остальные ключи.
#
# Параметры:
# 1 - хэш1
# 2 - ключ1
# 3 - ключ2
# . . .
# N - ключN
function hash_dup {
local hashName="$1" keyName="$2"
shift 2
until [ ${#} -le 0 ]; do
eval "${Hash_config_varname_prefix}${hashName}_${1}\
=\"\$${Hash_config_varname_prefix}${hashName}_${keyName}\""
shift;
done;
}
function hash_unset {
eval "unset ${Hash_config_varname_prefix}${1}_${2}"
}
679
#
# Параметры:
# 1 - хэш
# 2 - ключ
# 3 - ref — Имя присвоенное глобальной переменной.
function hash_get_ref_into {
eval "$3=\"${Hash_config_varname_prefix}${1}_${2}\""
}
function hash_echo_ref {
eval "echo $3 \"${Hash_config_varname_prefix}${1}_${2}\""
}
function hash_call {
local hash key
hash=$1
key=$2
shift 2
eval "eval \"\$${Hash_config_varname_prefix}${hash}_${key} \\\"\\\$@\\\"\""
}
function hash_is_set {
eval "if [[ \"\${${Hash_config_varname_prefix}${1}_${2}-a}\" = \"a\" &&
\"\${${Hash_config_varname_prefix}${1}_${2}-b}\" = \"b\" ]]
then return 1; else return 0; fi"
}
# Эмуляция типа:
# foreach($хэш как $ключ => $значение) { fun($ключ,$значение); }
#
# Можно писать различные вариации для этой функции.
# Здесь с помощью функции она производится «в общей форме».
#
# Параметры:
# 1 - хэш
680
# 2 — имя функции
function hash_foreach {
local keyname oldIFS="$IFS"
IFS=' '
for i in $(eval "echo \${!${Hash_config_varname_prefix}${1}_*}"); do
keyname=$(eval "echo \${i##${Hash_config_varname_prefix}${1}_}")
eval "$2 $keyname \"\$$i\""
done
IFS="$oldIFS"
}
# $1 - ключ
# $2 - значение
try_colors() {
echo -en "$2"
echo "Это строка $1."
}
hash_foreach colors try_colors
hash_echo colors reset_color -en
681
hash_unset colors $i
done
hash_foreach colors try_colors
hash_echo colors reset_color -en
exit $?
#----------------------------------------------------------------
# псевдо-хэш, основанный на косвенных параметрах расширения
# API: доступ через функции:
#
# Создаем хэш:
#
# newhash Lovers
#
# добавляем записи (обратите внимание на одинарные кавычки для пробелов)
#
# addhash Lovers Tristan Isolde
# addhash Lovers 'Romeo Montague' 'Juliet Capulet'
#
# значение ключа доступа
#
# gethash Lovers Tristan ----> Isolde
#
# показываем все ключи
#
# keyshash Lovers ----> 'Tristan' 'Romeo Montague'
#
#
# Соглашение: вместо синтаксиса perl' foo{bar} = boing',
# используем
# '_foo_bar=boing' (два подчеркивания, без пробелов)
682
#
# 1) сохраняем ключи в _NAME_keys[]
# 2) сохраняем значения в _NAME_values[] используя тот же целочисленный индекс.
# Целочисленным индексом последней записи является _NAME_ptr
#
# ПРИМЕЧАНИЕ: Это не ошибка или проверка здравомыслия, просто голый скелет.
function _inihash () {
# Локальная функция
# вызываемая в начале каждой процедуры
# определений: _keys _values _ptr
#
# Usage: _inihash NAME
local name=$1
_keys=_${name}_keys
_values=_${name}_values
_ptr=_${name}_ptr
}
function newhash () {
# Usage: newhash NAME
# NAME не содержит пробелов или точек.
# На самом деле: должна быть переменной Bash с правильным именем.
# Полагаем, что Bash автоматически распознает массивы.
local name=$1
local _keys _values _ptr
_inihash ${name}
eval ${_ptr}=0
}
function addhash () {
# Usage: addhash NAME KEY 'VALUE с пробелами'
# аргументы с пробелами должны быть заключены в одинарные кавычки ''
local name=$1 k="$2" v="$3"
local _keys _values _ptr
_inihash ${name}
function gethash () {
# Usage: gethash NAME KEY
# Возвращает ERR=0 если запись существует, в противном случае 1
# Это не надлежащий хэш --
#+ Мы просто ищем построчно через ключи.
local name=$1 key="$2"
local _keys _values _ptr
local k v i found h
_inihash ${name}
683
h="\${${_keys}[${i}]}' ' # Сохраняем в два этапа,
eval k=${h} #+ особенно с пробелами в кавычках.
if [ "${k}" = "${key}" ]; then found=1; break; fi
done;
function keyshash () {
# Usage: keyshash NAME
# Возвращает список всех ключей определенных для имен хэш.
local name=$1 key="$2"
local _keys _values _ptr
local k i h
_inihash ${name}
# -----------------------------------------------------------------------
# Конечный вывод.
echo
gethash Lovers Tristan # Isolde
echo
keyshash Lovers # 'Tristan' 'Romeo Montague'
echo; echo
exit 0
# Упражнение:
# --------
Теперь сценарий, который устанавливает и монтирует эти милые USB брелки твердотельных
«жестких дисков.»
Пример A-23. Монтирование USB оборудования хранения данных
#!/bin/bash
684
# ==> [Link]
# ==> Сценарий монтирования и установки USB брелка устройств хранения.
# ==> Запускается как root при запуске системы (см. ниже).
# ==>
# ==> Новые дистрибутивы Linux (2004 или позже) автоопределяют
# ==> и устанавливают USB носители, и не нуждаются в этом сценарии.
# ==> Но, он все еще полезен.
# This code is free software covered by GNU GPL license version 2 or above.
# Please refer to [Link] for the full license text.
#
# Some code lifted from usb-mount by Michael Hamilton's usb-mount (LGPL)
#+ см. [Link]
#
# ИНСТАЛЯЦИЯ
# -------
# Поместите его в /etc/hotplug/usb/diskonkey.
# Затем загляните в /etc/hotplug/[Link], и скопируйте все записи usb-
#+ носителей в /etc/hotplug/[Link], заменяя "usb-storage" на "diskonkey".
# В противном случае этот код выполняется только во время вызова/удаления
#+ модуля ядра(по крайней мере в моих тестах).
#
# TODO (Что еще надо реализовать)
# ----
# Обработка более одного устройства одновременно (т.е. /dev/diskonkey1
#+ и /mnt/diskonkey1). Самой большой проблемой здесь является обработка в
#+ devlabel, которую я еще не пробовал.
#
# АВТОР и ПОДДЕРЖКА
# ------------------
# Konstantin Riabitsev, <icon linux duke edu>.
# Отправляйте сообщения о любой проблеме на мой адрес электронной почты.
#
# ==> Комментарии добавлены автором ABS Guide.
SYMLINKDEV=/dev/diskonkey
MOUNTPOINT=/mnt/diskonkey
DEVLABEL=/sbin/devlabel
DEVLABELCONFIG=/etc/sysconfig/devlabel
IAM=$0
##
# Функции внесены почти дословно из кода монтирования USB.
#
function allAttachedScsiUsb {
find /proc/scsi/ -path '/proc/scsi/usb-storage*' -type f |
xargs grep -l 'Attached: Yes'
}
function scsiDevFromScsiUsb {
echo $1 | awk -F"[-/]" '{ n=$(NF-1);
print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+1, 1) }'
}
685
if [ -f /var/run/[Link] ]; then
CONSOLEOWNER=`cat /var/run/[Link]`
elif [ -f /var/lock/[Link] ]; then
CONSOLEOWNER=`cat /var/lock/[Link]`
else
CONSOLEOWNER=
fi
for procEntry in $(allAttachedScsiUsb); do
scsiDev=$(scsiDevFromScsiUsb $procEntry)
# Какие-то проблемы с носителем usb?
# Разделы не находятся в /proc/partitions/, пока к ним не обращались.
/sbin/fdisk -l $scsiDev >/dev/null
##
# Большинство устройств имеют информацию разметки, поэтому данные
#+ будут на /dev/sd?1. Тем не менее, некоторые из них не имеют никакой
#+ разметки и используют все устройство для хранения данных. Попытаемся
#+ угадать, есть ли у нас /dev/sd?1, и если нет, то используется
#+ весь носитель и надеемся на лучшее.
#
if grep -q `basename $scsiDev`1 /proc/partitions; then
part="$scsiDev""1"
else
part=$scsiDev
fi
##
# Смена владельца раздела пользователя консоли,что бы
#+ примонтировать ее.
#
if [ ! -z "$CONSOLEOWNER" ]; then
chown $CONSOLEOWNER:disk $part
fi
##
# Проверка, есть ли у нас UUID определенного devlabel. Если нет,
# тогда устройство добавляется в список.
#
prodid=`$DEVLABEL printid -d $part`
if ! grep -q $prodid $DEVLABELCONFIG; then
# Скрестим наши пальцы и будем надеяться, что это сработает
$DEVLABEL add -d $part -s $SYMLINKDEV 2>/dev/null
fi
##
# Проверка, существует ли точка монтирования, если нет — создаем ее.
#
if [ ! -e $MOUNTPOINT ]; then
mkdir -p $MOUNTPOINT
fi
##
# О легком монтировании позаботится /etc/fstab.
#
if ! grep -q "^$SYMLINKDEV" /etc/fstab; then
# Добавляем запись в fstab
echo -e \
"$SYMLINKDEV\t\t$MOUNTPOINT\t\tauto\tnoauto,owner,kudzu 0 0" \
>> /etc/fstab
fi
done
if [ ! -z "$REMOVER" ]; then
##
# Убеждаемся, что этот сценарий запускается при удалении устройства.
#
686
mkdir -p `dirname $REMOVER`
ln -s $IAM $REMOVER
fi
elif [ "${ACTION}" = "remove" ]; then
##
# Если устройство примонтировано, то правильно его отключает.
#
if grep -q "$MOUNTPOINT" /etc/mtab; then
# отмонтирование
umount -l $MOUNTPOINT
fi
##
# Удаление из /etc/fstab, если отмонтировано.
#
if grep -q "^$SYMLINKDEV" /etc/fstab; then
grep -v "^$SYMLINKDEV" /etc/fstab > /etc/.[Link]
mv -f /etc/.[Link] /etc/fstab
fi
fi
exit 0
# Предположения:
# 1) Абзацы в текстовом файле разделены пустыми строками.
# 2) Образы Jpeg (*.jpg) находятся в поддиректории "images".
# В целевом файле имя образа заключено в квадратные скобки,
# например, [[Link]].
# 3) Выделенные (курсивом) фразы начинаются с Пробел + Подчеркивание или первый
#+ символ в строке является знаком подчеркивания, а конечный с
#+ Подчеркиванием + Пробел или Подчеркиванием + Конец строки.
# Установки
FNTSIZE=2 # Мелко-средний размер шрифта
IMGDIR="images" # Директория Image
# Заголовки
HDR01='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
HDR02='<!-- Конвертирование HTML сценарием ***[Link]*** -->'
HDR03='<!-- автор сценария: M. Leo Cooper <[Link]@[Link]> -->'
HDR10='<html>'
HDR11='<head>'
HDR11a='</head>'
HDR12a='<title>'
HDR12b='</title>'
HDR121='<META NAME="GENERATOR" CONTENT="[Link] script">'
HDR13='<body bgcolor="#dddddd">' # Изменяет цвет фона.
687
HDR14a='<font size='
HDR14b='>'
# Окончания
FTR10='</body>'
FTR11='</html>'
# Тэги
BOLD="<b>"
CENTER="<center>"
END_CENTER="</center>"
LF="<br>"
write_headers ()
{
echo "$HDR01"
echo
echo "$HDR02"
echo "$HDR03"
echo
echo
echo "$HDR10"
echo "$HDR11"
echo "$HDR121"
echo "$HDR11a"
echo "$HDR13"
echo
echo -n "$HDR14a"
echo -n "$FNTSIZE"
echo "$HDR14b"
echo
echo "$BOLD" # Все жирным шрифтом (более читабельным).
}
process_text ()
{
while read line # Считываем одну строку за раз
do
{
if [ ! "$line" ] # Пустая строка?
then # Значит начинается новый абзац.
echo
echo "$LF" # Вставляем два тэга <br>.
echo "$LF"
echo
continue # Пропускаем проверку подчеркиваний.
else # Иначе ...
fi
688
then
# ===================================================
# Преобразуем подчеркнутую фразу в курсив.
temp=$( echo "$line" |
sed -e 's/ _/ <i>/' -e 's/_/<\/i> /' |
sed -e 's/^_/<i>/' -e 's/_/<\/i>/' )
# Только подчеркивания пробела в начале,
#+ или в начале или в конце строки.
# Подчеркивания внутри слова не преобразуются!
line="$temp"
# Сценарий исполняется медленно. Как можно оптимизировать?
# ===================================================
fi
# echo
echo "$line"
# echo
# Дополнительные пустые строки в созданном тексте не желательны!
} # Окончание цикла while
done
} # Окончание process_text ()
# main () {
# =========
write_headers
process_text
write_footers
# =========
# }
exit $?
# Упражнение:
# ---------
# 1) Исправьте: Проверить подчеркивания закрытые запятой или точкой.
# 2) Добавьте проверку на наличие закрытых подчеркиваний во фразах,
#+ которые будут выделены курсивом.
Вот то, что согреет сердца вебмастеров: сценарий, который сохраняет логи.
Пример A-25. Сохранение веблогов
#!/bin/bash
# [Link] v1.0
689
#
# Этот сценарий сохранит обычно обновляемые и удаляемые вебблоги
#+ из установки по умолчанию RedHat/Apache
# Будут сохраняться сжатые файлы с отметкой даты и времени в имени файла
#+ в заданную директорию.
#
# Запускается из crontab ночью, когда все выключено:
# 0 2 * * * /opt/sbin/[Link]
PROBLEM=66
# Мы root?
USER=`$ID -u`
if [ "X$USER" != "X0" ]; then
echo "PANIC: Только root может запускать этот сценарий!"
exit $PROBLEM
fi
exit 0
690
Как предохранить оболочку от расширения и интерпретации текстовых строк.
Пример A-26. Защита буквенных строк
#! /bin/bash
# protect_literal.sh
# set -vx
:<<-'_Protect_Literal_String_Doc'
Применение:
_protect_literal_str 'Ваши ${фантазии} удовлетворяются независимо
от строки'
Просто выведите аргумент на стандартный вывод, заключив в жесткие
кавычки.
Как:
Правая сторона присваивания сохраняется в жестких кавычках,
защищая буквальное содержимое во время присваивания.
Примечания:
Странные имена (_*) используются для избежания уничтожения выбранных
пользовательских имен, когда они выступают как источник библиотек.
_Protect_Literal_String_Doc
_protect_literal_str() {
691
# Версия с укороченным именем.
_pls() {
local IFS=$'x1B' # Символ \ESC (не требуется)
echo $'\x27'$@$'\x27' # Глобальный параметр в жестких кавычках
}
# :<<-'_Protect_Literal_String_Test'
# # # Удаляем "# " выше для удаления этого кода. # # #
# Что выводится:
# - - Первая проверка - -
# 'Привет $user' величиной 14 символов.
# 'Привет "${username}"' величиной 22 символа.
# Что выводится:
# - - Вторая проверка - -
# Элемент 0: zero длиной: 4. # Элемент образец
# Элемент 1: 'Hello ${Me}' длиной: 13. # Наш "$(_pls '...' )"
# Элемент 2: Hello ${You} длиной: 12. # Кавычки отсутствуют
# Элемент 3: \'Pass: \' длиной: 10 . # ${pw} ничем не расширено
# Что выводится:
# - - Третья проверка - -
# Элемент 0: zero длиной: 4. # Элемент образец.
692
# Элемент 1: Hello ${Me} длиной: 11. # Предполагаемый результат.
# Элемент 2: Hello длиной: 5. # ${You} ничем не расширено.
# Элемент 3: 'Pass: длиной: 6. # Разделяется пробелом.
# Элемент 4: ' длиной: 1. # Здесь конечная кавычка.
# _Protect_Literal_String_Test
# # # Удалите "#" выше для отключения этого кода. # # #
exit 0
# set -vx
:<<-'_UnProtect_Literal_String_Doc'
Применение:
Дополнение к функции "$(_pls 'Literal String')".
(См. пример protect_literal.sh.)
693
StringVar=$(_upls ProtectedSringVariable)
Действие:
Примененяется с права от оператора присваивания;
производит встраивание подстановки в защищенную строку.
Примечания:
Странные имена (_*) используются для избежания уничтожения выбранных
пользовательских имен, когда они выступают как источник библиотек.
_UnProtect_Literal_String_Doc
_upls() {
local IFS=$'x1B' # Символ \ESC (не требуется)
eval echo $@ # Подстановка шаблона.
}
# :<<-'_UnProtect_Literal_String_Test'
# # # Удалите "# " выше для удаления этого кода. # # #
_pls() {
local IFS=$'x1B' # Символ \ESC (не требуется)
echo $'\x27'$@$'\x27' # Шаблоны параметра в жестких кавычках
}
# Что присваиваем:
# - - Третья проверка - -
# Элемент 0: zero длинной: 4 # Образец.
# Элемент 1: Hello ${Me} длинной: 11 # Предполагаемый результат .
# Элемент 2: Hello длинной: 5 # ${You} ничем не расширяется.
# Элемент 3: 'Pass: длинной: 6 # Разделяющий пробел.
# Элемент 4: ' длиной: 1 # Заключительная кавычка.
# set -vx
# Смотрим содержимое.
echo $newVar
694
echo $newerVar
# Почему?
# Присвоение содержимому строковой переменной, содержащей последовательности
#+ символов, которые имеют смысл в Bash, является общей проблемой в написании
#+ сценария.
#
# Эта проблема теперь решена в восьми строках кода
#+ (и четырех страницах описания).
# _UnProtect_Literal_String_Test
# # # Удалите "#" выше для удаления этого кода. # # #
exit 0
#######################################################
# Документация
# См. Так же "Quickstart" в конце сценария.
#######################################################
695
:<<-'__is_spammer_Doc_'
Не терпится?
Код приложения: переход к коду "# # # Hunt the Spammer' # # #"
Пример вывода: ":<<-'_is_spammer_outputs_'"
Использование: Введите название сценария без аргументов.
Или перейдите к "Quickstart" в конце сценария.
Обеспечение
При вводе задается доменное имя или адрес IP(v4):
Требования
Работающее соединение Интернет.
(Упражнение: Добавьте проверку/или сброс при отсутствии соединения при
запуске сценария.)
Bash с массивами (2.05b+).
Использование
Сценарию требуется единственный аргумент, которым может быть:
1) Доменное имя;
2) IP(v4) адрес;
3) Файл, с одним именем или адресом в каждой строке.
Возвращаемые коды
0 - Все OK
1 — Сбой сценария
2 — Что то занесено в Blacklist
696
Дополнительные переменные среды
SPAMMER_TRACE
Если задан файл для записи,
сценарий будет вести журнал выполнения потока трассировки.
SPAMMER_DATA
Если задан файл для записи, сценарий будет собирать
обнаруженные данные в виде файла GraphViz
См.: [Link]
SPAMMER_LIMIT
Ограничение глубины трассировки.
Дополнительная документация
Загрузите архив сценариев
объясняющих и иллюстрирующих функции содержащиеся в этом сценарии.
[Link]
Расширение проекта
Добавьте обратный просмотр деталей обнаруженной информации.
__is_spammer_Doc_
#######################################################
697
# Поля десятичного представления IP адреса разделены точкой
ADR_IFS=${NO_WSP}'.'
698
pend_mark() {
pend_func pend_stop_mark
}
699
for (( x = 0; x < ${_ee_cnt} ; x++ ))
do
_ee_x=${_ee_Excludes[$x]}
for (( n = 0 ; n < ${_ee_len} ; n++ ))
do
_ee_t=${_ee_Target[$n]}
if [ x"${_ee_t}" == x"${_ee_x}" ]
then
unset _ee_Target[$n] # Отменяем сравнение.
[ $# -eq 2 ] && break # Если 2 аргумента, то делаем.
fi
done
done
eval $2=\( \$\{_ee_Target\[@\]\} \)
set +f
return 0
}
700
local _ul_tmp
local IFS=${NO_WSP}
set -f
eval _ul_in=\( \$\{$1\[@\]\} \)
_ul_cnt=${#_ul_in[@]}
for (( _ul_pos = 0 ; _ul_pos < ${_ul_cnt} ; _ul_pos++ ))
do
if [ ${_ul_in[${_ul_pos}]:=} ] # Если присвоен & не записан
then
_ul_tmp=${_ul_in[${_ul_pos}]}
_ul_out[${#_ul_out[@]}]=${_ul_tmp}
for (( zap = _ul_pos ; zap < ${_ul_cnt} ; zap++ ))
do
[ ${_ul_in[${zap}]:=} ] &&
[ 'x'${_ul_in[${zap}]} == 'x'${_ul_tmp} ] &&
unset _ul_in[${zap}]
done
fi
done
eval $2=\( \$\{_ul_out\[@\]\} \)
set +f
return 0
}
701
# Не каждая функция использует точки в качестве разделителей (например APNIC).
# Эта функция описана в to_dot.bash
# to_dot <строка>
to_dot() {
[ $# -eq 1 ] || return 1
echo ${1//[#|@|%]/.}
return 0
}
702
eval $3=\(\ \$\{_dns_ip\[@\]\}\ \)
fi
return 0
}
703
for (( _cp = 0 ; _cp < _cp_cnt ; _cp++ ))
do
_cp_pos=1
IFS=${NO_WSP}$'\x20'
_cp_line=( ${_cp_inp[${_cp}]} )
IFS=${NO_WSP}
for (( _cpf = 0 ; _cpf < ${#_cp_line[@]} ; _cpf++ ))
do
_cp_tab=${_cp_spc[${_cpf}]:${_cp_pos}}
if [ ${#_cp_tab} -lt ${_cp_mcnt} ]
then
_cp_tab="${_cp_min}"
fi
echo -n "${_cp_tab}"
(( _cp_pos = ${_cp_pos} + ${#_cp_tab} ))
_cp_fld="${_cp_line[${_cpf}]}"
echo -n ${_cp_fld}
(( _cp_pos = ${_cp_pos} + ${#_cp_fld} ))
done
echo
done
set +f
return 0
}
704
#+ с отрицательными ответами и подавлением неудачных проверок.
declare -a list_server
705
printf "${_dda_form}" \
"${_dda}" "${_dda_tmp[${_dda}]}" >>${_dot_file}
done
fi
}
if [ ${#known_name[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Известные узлы доменных имен' >>${_dot_file}
_dd_cnt=${#known_name[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' N%04u [label="%s"] ;\n' \
"${_dd}" "${known_name[${_dd}]}" >>${_dot_file}
done
fi
if [ ${#known_address[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Известные узлы адресов' >>${_dot_file}
_dd_cnt=${#known_address[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' A%04u [label="%s"] ;\n' \
"${_dd}" "${known_address[${_dd}]}" >>${_dot_file}
done
fi
echo >>${_dot_file}
echo '/*' >>${_dot_file}
echo ' * Известные отношения :: пользователя преобразуется' >>${_dot_file}
echo ' * Нужна ручная или программная графическая форма.' >>${_dot_file}
echo ' *' >>${_dot_file}
if [ ${#auth_chain[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Значение поддерживаемой ссылки & исходное поле.' >>${_dot_file}
dump_to_dot auth_chain AC
fi
if [ ${#ref_chain[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Имя поддерживаемой ссылки и исходного поля.' >>${_dot_file}
dump_to_dot ref_chain RC
fi
if [ ${#name_address[@]} -gt 0 ]
then
echo >>${_dot_file}
706
echo '# Known name->address edges' >>${_dot_file}
dump_to_dot name_address NA
fi
if [ ${#name_srvc[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Known name->service edges' >>${_dot_file}
dump_to_dot name_srvc NS
fi
if [ ${#name_resource[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Known name->resource edges' >>${_dot_file}
dump_to_dot name_resource NR
fi
if [ ${#parent_child[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Known parent->child edges' >>${_dot_file}
dump_to_dot parent_child PC
fi
if [ ${#list_server[@]} -gt 0 ]
then
echo >>${_dot_file}
echo '# Известные узлы Blacklist' >>${_dot_file}
_dd_cnt=${#list_server[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' LS%04u [label="%s"] ;\n' \
"${_dd}" "${list_server[${_dd}]}" >>${_dot_file}
done
fi
707
# Функция для заполнения журнала трассировки.
trace_logger() {
_trace_log[${#_trace_log[@]}]=${_pend_current_}
}
708
_sf_cnt=${#_sf_reply[@]}
for (( _sf = 0 ; _sf < ${_sf_cnt} ; _sf++ ))
do
[ 'x'${_sf_reply[${_sf}]:0:2} == 'x;;' ] &&
unset _sf_reply[${_sf}]
done
eval $2=\( \$\{_sf_reply\[@\]\} \)
fi
return 0
}
709
[ 'x'${_st_reply[${_st}]:0:2} == 'x;;' ] &&
unset _st_reply[${_st}]
done
eval $2=\( \$\{_st_reply\[@\]\} \)
fi
return 0
}
710
# echo 'lrev: '${1}
_lr_reply=( $(
dig +noall +nofail +answer +authority +additional \
${_lr_dns} -t soa ${_lr_dns} -t any 2>/dev/null) )
_lr_rc=$?
if [ ${_lr_rc} -ne 0 ]
then
_trace_log[${#_trace_log[@]}]='# Deleg lkp error '${_lr_rc}' on '${1}' #'
# [ ${_lr_rc} -ne 9 ] && pend_drop
return ${_lr_rc}
else
# Некоторые версии 'dig' возвращают предупреждения на stdout.
_lr_cnt=${#_lr_reply[@]}
for (( _lr = 0 ; _lr < ${_lr_cnt} ; _lr++ ))
do
[ 'x'${_lr_reply[${_lr}]:0:2} == 'x;;' ] &&
unset _lr_reply[${_lr}]
done
eval $2=\( \$\{_lr_reply\[@\]\} \)
fi
return 0
}
711
fi
;;
esac
echo ${_nf_str}
return 0
}
# Ограничитель рекурсии
# limit_chk() <next_level>
limit_chk() {
local -i _lc_lmt
# Проверка прямых ограничений.
if [ ${indirect} -eq 0 ] || [ $# -eq 0 ]
then
# Выбор 'делать всегда'
echo 1 # Любое значение будет сделано.
return 0 # OK для продолжения.
else
# Ограничение действует.
if [ ${indirect} -lt ${1} ]
then
echo ${1} # Без разницы.
return 1 # Здесь прекращается.
else
_lc_lmt=${1}+1 # Увеличиваем заданный предел.
echo ${_lc_lmt} # Выводим его на экран.
return 0 # OK для продолжения.
fi
712
fi
}
if ! _ein_cnt=$(limit_chk ${1})
then
return 0
fi
713
# Зависание expand_input_name.
# Повторяется до тех пор, пока не обнаружится новое
# expand_input_address <indirection_limit>
expand_input_address() {
[ ${#uc_address[@]} -gt 0 ] || return 0
local -a _eia_addr
local -a _eia_name
local -a _eia_new
local -i _uca_cnt
local -i _eia_cnt
local _eia_tst
unique_lines uc_address _eia_addr
unset uc_address[@]
edit_exact been_there_addr _eia_addr
_uca_cnt=${#_eia_addr[@]}
[ ${_uca_cnt} -gt 0 ] &&
been_there_addr=( ${been_there_addr[@]} ${_eia_addr[@]} )
714
local -a _den_rev # Обратные пары найденные здесь
local -a _den_tmp # Анализируемая строка
local -a _den_auth # Анализируемое соединение SOA
local -a _den_new # Ответ зоны
local -a _den_pc # Родитель-потомок, быстрое большое получение
local -a _den_ref # Делает ссылку связей
local -a _den_nr # Имя-Источник может быть большое
local -a _den_na # Имя-Адрес
local -a _den_ns # Имя-Служба
local -a _den_achn # Управляющая связь
local -i _den_cnt # Детали подсчета имен
local -i _den_lmt # Косвенное ограничение
local _den_who # Обработанные имена
local _den_rec # Запись типа обработки
local _den_cont # Домен соединения
local _den_str # Исправление имени строки
local _den_str2 # Обратное исправление
local IFS=${WSP_IFS}
715
_den_name[${#_den_name[@]}]=${_den_str}
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str}' SOA'
# Оригинальный SOA — запись доменного имени основной зоны
if _den_str2=$(name_fixup ${_den_tmp[4]})
then
_den_name[${#_den_name[@]}]=${_den_str2}
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str2}' SOA.O'
fi
# Ответная сторона адреса электронной почты (возможно фиктивная).
# Возможность игнорировать [Link]@[Link].
set -f
if _den_str2=$(name_fixup ${_den_tmp[5]})
then
IFS=${ADR_IFS}
_den_auth=( ${_den_str2} )
IFS=${WSP_IFS}
if [ ${#_den_auth[@]} -gt 2 ]
then
_den_cont=${_den_auth[1]}
for (( _auth = 2 ; _auth < ${#_den_auth[@]} ; _auth++ ))
do
_den_cont=${_den_cont}'.'${_den_auth[${_auth}]}
done
_den_name[${#_den_name[@]}]=${_den_cont}'.'
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_cont}'. SOA.C'
fi
fi
set +f
fi
;;
716
_den_pc[${#_den_pc[@]}]=${_den_str}' '${_den_str2}
fi
fi
;;
717
# Имя хоста
if _den_str=$(name_fixup ${_den_tmp[4]})
then
_den_name[${#_den_name[@]}]=${_den_str}
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' CHOST'
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str}
fi
;;
# TXT)
# ;;
esac
fi
done
else # Поиск ошибки == 'A' запись 'неизвестный адрес'
_den_pair[${#_den_pair[@]}]='[Link] '${_den_who}
fi
done
718
IFS=${WSP_IFS}
fi
719
fi
return 0
}
if [ ${#auth_chain[@]} -gt 0 ]
then
echo
echo 'Известная цепь управления.'
col_print auth_chain 2 5 30 55
fi
if [ ${#reverse_pair[@]} -gt 0 ]
then
echo
echo 'Известные обратные пары.'
col_print reverse_pair 2 5 55
fi
return 0
}
720
do
_cl_lkup=${_cl_dns_addr}${list_server[${_cl}]}
if short_text ${_cl_lkup} _cl_reply
then
if [ ${#_cl_reply[@]} -gt 0 ]
then
echo ' Записи из '${list_server[${_cl}]}
address_hits[${#address_hits[@]}]=${1}' '${list_server[${_cl}]}
_hs_RC=2
for (( _clr = 0 ; _clr < ${#_cl_reply[@]} ; _clr++ ))
do
echo ' '${_cl_reply[${_clr}]}
done
fi
fi
done
return 0
}
721
Если arg#1 -целое число, ограничение устанавливается в данное значение
и потом применяются правила аргумента выше.
_usage_statement_
}
declare -a default_servers
# См.: [Link] (Постоянный, хорошо сопровождаемый)
default_servers[0]='[Link]'
# См.: [Link] (Открытые почтовые переключения)
default_servers[1]='[Link]'
# См.: [Link] (Сообщайте о спаммерах сюда)
default_servers[2]='[Link]'
# См.: [Link] (Система 'раннего обнаружения')
default_servers[3]='[Link]'
# См.: [Link]
default_servers[4]='[Link]'
# См.: [Link] (Различные списки почтовых переключений)
default_servers[5]='[Link]'
default_servers[6]='[Link]'
default_servers[7]='[Link]'
722
# Пользовательская переменная окружения SPAMMER_TRACE
live_log_die() {
if [ ${SPAMMER_TRACE:=} ] # Нужен журнал трассировки?
then
if [ ! -e ${SPAMMER_TRACE} ]
then
if ! touch ${SPAMMER_TRACE} 2>/dev/null
then
pend_func echo $(printf '%q\n' \
'Невозможно создать логфайл >'${SPAMMER_TRACE}'<')
pend_release
exit 1
fi
_log_file=${SPAMMER_TRACE}
_pend_hook_=trace_logger
_log_dump=dump_log
else
if [ ! -w ${SPAMMER_TRACE} ]
then
pend_func echo $(printf '%q\n' \
'Невозможно записать >'${SPAMMER_TRACE}'<')
pend_release
exit 1
fi
_log_file=${SPAMMER_TRACE}
echo '' > ${_log_file}
_pend_hook_=trace_logger
_log_dump=dump_log
fi
fi
return 0
}
723
}
local -a _la_lines
set -f
local IFS=${NO_WSP}
eval _la_lines=\(\ \$\{$1\[@\]\}\ \)
echo
echo "Подсчет элементов "${#_la_lines[@]}" array "${1}
local _ln_cnt=${#_la_lines[@]}
724
for (( _i = 0; _i < ${_ln_cnt}; _i++ ))
do
echo 'Элемент '$_i' >'${_la_lines[$_i]}'<'
done
set +f
return 0
}
# # # Предоставляется пользователю # # #
live_log_die # Настройка журнала отладки трассировки.
data_capture # Настройка файла сбора данных.
echo
do_user_args $@
725
done
fi
fi
pend_release
$_dot_dump # Графический дамп файла
$_log_dump # Трассировка выполнения
echo
##############################
# Пример вывода сценария #
##############################
:<<-'_is_spammer_outputs_'
./is_spammer.bash 0 [Link]
_is_spammer_outputs_
exit ${_hs_RC}
#########################################################################
726
# Сценарий игнорирует все отсюда вниз из-за команды «exit», чуть выше. #
#########################################################################
Quickstart
==========
Условия
Дополнительные условия
Quick Start
Детали использования
1. Выбор сервера черного списка.
727
i. Создаем пустой файл (касается [Link])
Выбираете файл.
i. export SPAMMER_LIMIT=1
или любое нужное ограничение.
728
ii. С ограничением поиска командной строкой: Файл как
второй аргумент сценария.
7. Отмена решения.
нажмите RETURN
ждите (не обязательно, увидите точки и запятые).
Пока вы не измените этот дескриптор файла, для описания того, что вы хотите
увидеть, все, что вы получите - это кучу имен и адресов узлов.
Необходимое редактирование, для того, чтобы провести линию между парой узлов из
информации в файле дескриптора, может быть сделано с помощью текстового
редактора.
N0000 [label="[Link]."] ;
N0002 [label="[Link]."] ;
A0000 [label="[Link]"] ;
/*
729
# Известный родитель->вложенный потомок
*/
N0000 [label="[Link]."] ;
N0002 [label="[Link]."] ;
A0000 [label="[Link]"] ;
N0000->N0002 ;
N0002->A0000 ;
/*
*/
# Завершение Quickstart.
730
Примечание
========== ====
if [ -e "$OUTFILE" ]
then
rm -f "$OUTFILE"
echo "Устаревший выходной файл \"$OUTFILE\" удален."; echo
731
fi
# Проверка исправности.
# (Этот раздел требует больше усилий)
# ===============================
if [ -z "$IPADDR" ]
# Нет ответа.
then
echo "Узел не найден!"
exit $E_NOHOST
fi
if [[ "$IPADDR" =~ ^[;;] ]]
# ;; Время соединения истекло; нет доступа к серверам.
then
echo "Время поиска узла истекло!"
exit $E_TIMEOUT
fi
if [[ "$IPADDR" =~ [(NXDOMAIN)]$ ]]
# Узел [Link] не найден: 3(NXDOMAIN)
then
echo "Узел не найден!"
exit $E_NOHOST
fi
if [[ "$IPADDR" =~ [(SERVFAIL)]$ ]]
# Узел [Link] не найден: 2(SERVFAIL)
then
echo "Узел не найден!"
exit $E_NOHOST
fi
AFRINICquery() {
# Определяет запрашивающую функцию AFRINIC. Выводит уведомление на экран,
#+ а затем делает фактический запрос, перенаправляя вывод в $OUTFILE.
732
fi
}
APNICquery() {
echo "Поиск $IPADDR на [Link]"
whois -h [Link] "$IPADDR" > $OUTFILE
ARINquery() {
echo "Поиск $IPADDR на [Link]"
whois -h [Link] "$IPADDR" > $OUTFILE
LACNICquery() {
echo "Поиск $IPADDR на [Link]"
733
whois -h [Link] "$IPADDR" > $OUTFILE
RIPEquery() {
echo "Поиск $IPADDR на [Link]"
whois -h [Link] "$IPADDR" > $OUTFILE
}
if [ $slash8 == 0 ]; then
echo $IPADDR это пространство '"Данной сети"'\; Не запрашивается
elif [ $slash8 == 10 ]; then
echo $IPADDR это пространство RFC1918\; Не запрашивается
elif [ $slash8 == 14 ]; then
echo $IPADDR это пространство '"Public Data Network"'\; Не запрашивается
elif [ $slash8 == 127 ]; then
echo $IPADDR это пространство loopback\; Не запрашивается
elif [ $slash16 == 169.254 ]; then
echo $IPADDR это пространство локальной ссылки\; Не запрашивается
elif [ $slash8 == 172 ] && [ $octet2 -ge 16 ] && [ $octet2 -le 31 ];then
echo $IPADDR это пространство RFC1918\; Не запрашивается
734
elif [ $slash16 == 192.168 ]; then
echo $IPADDR это пространство RFC1918\; Не запрашивается
elif [ $slash8 -ge 224 ]; then
echo $IPADDR это многоадресное или зарезервированное пространство\; Не
запрашивается
elif [ $slash8 -ge 200 ] && [ $slash8 -le 201 ]; then LACNICquery "$IPADDR"
elif [ $slash8 -ge 202 ] && [ $slash8 -le 203 ]; then APNICquery "$IPADDR"
elif [ $slash8 -ge 210 ] && [ $slash8 -le 211 ]; then APNICquery "$IPADDR"
elif [ $slash8 -ge 218 ] && [ $slash8 -le 223 ]; then APNICquery "$IPADDR"
else
ARINquery "$IPADDR"
if grep "[Link]" "$OUTFILE"; then
AFRINICquery "$IPADDR"
elif grep -E "^OrgID:[ ]+RIPE$" "$OUTFILE"; then
RIPEquery "$IPADDR"
elif grep -E "^OrgID:[ ]+APNIC$" "$OUTFILE"; then
APNICquery "$IPADDR"
elif grep -E "^OrgID:[ ]+LACNIC$" "$OUTFILE"; then
LACNICquery "$IPADDR"
fi
fi
#@ ---------------------------------------------------------------
# Попробуйте так же:
# wget [Link]
#@ ---------------------------------------------------------------
cat $OUTFILE
# Или "less $OUTFILE" ...
exit 0
735
# ==> Он так же может дополнительно внести некоторые изменения в комментариях.
# Это wgetter2 --
#+ сценарий Bash делающий Wget немного более дружественным, и сохраняющий
#+ набранный текст.
# =======================================================================
# --------------------------------------------------------------------
# Устанавливаем переменные и объясняем их.
pattern=" -A .jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.htm,.html,.shtml,.php"
# опции wget позволяющие получать только определенные виды
#+ файлов.
# Закомментируйте не нужное
today=`date +%F` # Применяется для имени файла.
home=$HOME # Присваиваем HOME внутренней переменной.
# Если используете другой путь, измените.
depthDefault=3 # Присваиваем чувствительность рекурсии по умолчанию.
Depth=$depthDefault # В противном случае обратная связь с пользователем будет
#+ не правильной.
RefA="" # Устанавливаем пустую ссылку на страницу.
Flag="" # По умолчанию не сохраняем ничего,
#+ или то, что потом посчитаем нужным.
lister="" # Используется для передачи списка url прямо в wget.
Woptions="" # Используется для передачи wget других своих опций.
inFile="" # Используется для запуска функции.
newFile="" # Используется для запуска функции.
savePath="$home/w-save"
Config="$home/.wgetter2rc"
# Здесь могут быть сохранены некоторые параметры,
#+ окончательно измененные в сценарии.
Cookie_List="$home/.cookielist"
# Итак, мы знаем, где хранятся cookie ...
cFlag="" # Часть процедуры выбора файла cookie.
736
list=l # Передать wget опции -i и списка URL.
runn=r # Запустить сохраненные команды в качестве опции аргумента.
inpu=i # Запустить сохраненные команды интерактивно.
wopt=w # Ввод параметров, передавая непосредственно в wget.
# --------------------------------------------------------------------
if [ -z "$1" ]; then # Убедитесь, что у нас есть что-то для ввода в wget.
echo "Нужно ввести, по крайней мере, URL или опцию!"
echo "Используйте -$help."
exit $E_NO_OPTS
fi
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# добавлено добавлено добавлено добавлено добавлено добавлено добавлено
if [ ! -e "$Cookie_List" ]; then
# Настройка списка файлов cookie, если нет ни одного.
echo "Собираем cookie ..."
find -name [Link] >> $Cookie_List # Создаем список файлов cookie.
fi # Изолируйте у себя 'if',
#+ в случае, если поиск прерывается.
737
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# Другие переменные.
# Могут или не могут быть предметом изменения.
# Немного похоже на мелкий шрифт.
CookiesON=$Cookie
# echo "файл cookie это $CookiesON" # Для отладки.
# echo "домашний это ${home}" # Для отладки.
wopts()
{
echo "Введите опции для передачи в wget."
echo "Предполагается вы знаете, что делаете."
echo
echo "Можно так же здесь передать аргументы опции."
# То есть все помещенное сюда передается в wget.
read Wopts
# Читение опций передаваемых в wget.
Woptions=" $Wopts"
# ^ Зачем начальный пробел?
# Назначение другой переменной.
# Просто для удовольствия, или ...
return
}
save_func()
{
echo "Настройки будут сохранены."
if [ ! -d $savePath ]; then # Проверяем существование директории.
mkdir $savePath # Создаем директорию, если она не существовала,
#+ для сохранения.
fi
Flag=S
# Последняя часть кода предлагает выбрать действие.
# В основном надо установить флаг.
return
}
738
echo ""
echo "-$help : Вывод справки."
echo "-$save : Сохранение команды в файл $savePath/wget-($today) \
вместо его запуска."
echo "-$runn : Запуск сохраненных команд wget вместо запуска новых -"
echo "Для этой опции введите имя файла как аргумент."
echo "-$inpu : Интерактивный запуск сохраненных команд wget --"
echo "Сценарий будет запрашивать у вас имя файла."
echo "-$cook : Смена файла cookies для данной сессии."
echo "-$list : Указывает wget использовать URL из списка, а не \
из командной строки."
echo "-$wopt : Передает любые другие опции прямо в wget."
echo ""
echo "См. Справочные страницы wget о дополнительных опциях, \
которые можно передать в wget."
echo ""
739
fi
done
}
run_func()
{
if [ -z "$OPTARG" ]; then
# Проверка, чтобы увидеть: используется параметр командной строки или запрос.
if [ ! -d "$savePath" ]; then # Если директория не существует ...
echo "$savePath не существует."
echo "Пожалуйста укажите путь и файл сохраненных команд wget:"
read newFile
until [ -f "$newFile" ]; do # Продолжаем до получения.
echo "Извините, этот файл не существует. Попробуйте снова."
# Попробуйте, действительно трудно получить что-то.
read newFile
done
# -----------------------------------------------------------------------
# if [ -z ( grep wget ${newfile} ) ]; then
# Предположим, нужный файл не получен.
# echo "Извините, этот файл не содержит команд wget. Сброс."
# exit
# fi
#
# Это фиктивный код.
# Он фактически не работает.
# Если хотите — исправьте его!
# -----------------------------------------------------------------------
filePath="${newFile}"
else
echo "Путь сохранения $savePath"
echo "Введите имя файла который хотите использовать."
echo "У вас есть выбор:"
ls $savePath # Задаем выбор.
read inFile
until [ -f "$savePath/$inFile" ]; do # Продолжаем до получения
#+ чего-нибудь.
if [ ! -f "${savePath}/${inFile}" ]; then # Если файл не существует.
echo "Этот файл не существует. Выберите из:"
ls $savePath # Если допущена ошибка.
read inFile
fi
done
filePath="${savePath}/${inFile}" # Создаем одну переменную ...
fi
else filePath="${savePath}/${OPTARG}" # Которая может быть любой ...
fi
740
eval $REPLY
echo "Завершено: $REPLY"
done < $filePath # Поток указанного файла для использования в цикле 'while'.
exit
}
URLS=" $@"
# Список URL может быть изменен, если мы установим опцию in в цикле.
while [ 1 ]; do
# Запрос наиболее часто используемых опций.
# (Главным образом не измененных после версии 1 wgetter)
if [ -z $curDepth ]; then
Current=""
else Current=" Текущее значение $curDepth"
fi
echo "Как глубоко зайдем? \
(Целое число: По умолчанию это $depthDefault.$Current)"
read Depth # Рекурсия — Как глубоко заходить?
inputB="" # Сброс пробела при каждом проходе цикла.
echo "Введите имя ссылки на страницу ( по умолчанию — none)."
read inputB # Нужно для некоторых сайтов.
741
case $noHide in
y|Y ) hide="";;
n|N ) hide=" -b";;
* ) hide="";;
esac
if [ -z ${Depth} ]; then
# Пользователь принял умолчание или текущую глубину,
#+ в любом случае Depth пуста.
if [ -z ${curDepth} ]; then
# Смотрим установленную глубину предыдущей итерации.
Depth="$depthDefault"
# Устанавливаем глубину рекурсии по умолчанию,
#+ если не надо ничего применять.
else Depth="$curDepth" # Иначе, остается предыдущая установка.
fi
fi
Recurse=" -l $Depth" # Устанавливаем желаемую глубину.
curDepth=$Depth # Запоминаем настройки.
if [ ! -z $inputB ]; then
RefA=" --referer=$inputB" # Опция использующейся ссылки на страницу.
fi
WGETTER="${CommandA}${pattern}${hide}${RefA}${Recurse}\
${CookiesON}${lister}${Woptions}${URLS}"
# Только совместная целая строка ...
# NB: без пробелов.
# Она состоит из отдельных элементов, поэтому, если какие-либо из них
#+ пустые, мы не получим дополнительных пробелов.
742
echo ""
echo "Запускается over$Saver."
echo "Если желаете выйти, нажмите q."
echo "в ином случае введите URL:"
# Теперь снова. Сообщает об устанавливаемом варианте сохранения.
read
case $REPLY in
# Нужно изменить положение «ловушки».
q|Q ) exit $E_USER_EXIT;; # Упражнение для читателя?
* ) URLS=" $REPLY";;
esac
echo ""
done
exit 0
# [Link]:
# By Linc 10/1/2004
# Ищите последнюю версию сценария на
#+ [Link]
# Last revision 12/14/2004 - Many Contributors!
# If you use this and have made improvements or have comments
#+ drop me an email at linc dot fessenden at gmail dot com
# I'd appreciate it!
# ==> Автор этого сценария любезно предоставил право на включение в ABS Guide.
# ==> ################################################################
#
# ==> Что такое "podcasting"?
# ==> ################################################################
743
# Проверяем создание datadir если необходимо:
if test ! -d $datadir
then
mkdir $datadir
fi
exit 0
# См: [Link]
# Сохраняем как: $HOME/bin/nightly-backup_firewire-[Link]
744
# Известные баги:
# ----------
# i) В идеале мы хотим исключить ~/.tmp и кэш браузера.
745
Директории именованные 'backup.\$i' будут в итоге УДАЛЕНЫ.
Бэкап производится каждый день в течении 7 дней (1-8),
каждую неделю в течении 4 недель (9-12),
каждый месяц в течении 3 месяцев (13-15).
# Анализируем опции.
# ==================
746
# Например: такое правило udev :
# BUS="scsi", KERNEL="sd*", SYSFS{vendor}="WDC WD16",
# SYSFS{model}="00JB-00GVA0 ", NAME="%k", SYMLINK="lacie_1394d%n"
for ((i=1;i<=15;i++)); do
if [ ! -d $BACKUP_DEST_DIR/backup.$i ]; then
if /bin/mkdir -p $BACKUP_DEST_DIR/backup.$i ; then
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Нет [ ] скобок проверки.
#+ Почему?
echo "Внимание: директория $BACKUP_DEST_DIR/backup.$i отсутствует,"
echo "или не инициализирована. (Пере-)Создайте ее."
else
echo "ОШИБКА: директория $BACKUP_DEST_DIR/backup.$i"
echo "отсутствует или не была создана."
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
# Прежде, чем выйти, отмонтируйтесь от точки монтирования.
cd
sudo umount $MOUNT_POINT &&
echo "$MOUNT_POINT снова отмонтирована. Отказ."
fi
exit $E_UNMOUNTED
fi
fi
done
747
echo "ОШИБКА: Не установлены права доступа $BACKUP_DEST_DIR в 700."
exit $E_UNMOUNTED
fi
if [ $? != 0 ]; then
BACKUP_JUSTINCASE=backup.`date +%F_%T`.justincase
echo "ВНИМАНИЕ: процесс rsync полностью не совершен."
echo "Возможно где-то ошибка."
echo "Сохранение дополнительной копии: $BACKUP_JUSTINCASE"
echo "ВНИМАНИЕ: Если это происходит регулярно, будет задействовано много"
echo "пространства, даже несмотря на то, что это просто жесткие ссылки!"
fi
748
# Проверяем это перед rsync, возможно, есть шанс.
DISK_FULL_PERCENT=`/bin/df $BACKUP_DEST_DIR |
tr "\n" ' ' | awk '{print $12}' | grep -oE [0-9]+ `
echo "Проверка дискового пространства раздела бэкап \
$MOUNT_POINT $DISK_FULL_PERCENT% full."
if [ $DISK_FULL_PERCENT -gt 90 ]; then
echo "Внимание: Диск заполнен более чем на 90%."
fi
if [ $DISK_FULL_PERCENT -gt 98 ]; then
echo "Ошибка: Диск заполнен! Откат."
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
# Прежде, чем выйти, отмонтируйте от точки монтирования.
cd; sudo umount $MOUNT_POINT &&
echo "$MOUNT_POINT снова отмонтирован. Откат."
fi
exit $E_UNMOUNTED
fi
749
/bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 &&
/bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 &&
/bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9
else
echo -e "\nУдаление старых ежедневных бэкапов: $BACKUP_DEST_DIR/backup.8" &&
/bin/rm -rf $BACKUP_DEST_DIR/backup.8
fi &&
SUCCESS=true
750
#
# Существует неограниченный стек записей и стек записи событий.
# Стек записей сохраняет последние директории cd_maxhistory,
# которые использовались. Записи событий могут быть назначены
# часто используемым директориям.
#
# Записи событий назначаются заранее, установкой переменной
# среды CDSn или с помощью команды -u или — U.
#
# Ниже приводится файл .profile:
#
# . cdll # Настройка команды cd
# alias cd='cd_new' # Замена команды cd
# cd -U # Загрузка предварительно назначенных
# #+ записей стека и записей событий
# cd -D # Установка режима non-default
# alias @="cd_new @" # Позволяет использовать @ для получения
# #+ истории
#
# Для справки:
#
# cd -h или
# cd -H
#
#
###########################################################################
#
# Version 1.2.1
#
# Автор Phil Braham - Realtime Software Pty Ltd
# (realtime@[Link])
# Please send any suggestions or enhancements to the author (also at
# phil@[Link])
#
############################################################################
cd_hm ()
{
${PRINTF} "%s" "cd [dir] [0-9] [@[s|h] [-g [<dir>]] [-d] \
[-D] [-r<n>] [dir|0-9] [-R<n>] [<dir>|0-9]
[-s<n>] [-S<n>] [-u] [-U] [-f] [-F] [-h] [-H] [-v]
<dir> Переход в директорию
0-n Переход в предыдущую директорию (0 предыдущая, 1 следующая
перед ней, и т.д.)
n это вершина максимума записи историй (умолчание 50)
@ Список записей истории и записей событий
@h Список записей истории
@s Список записей событий
-g [<dir>] Переход к буквальному имени (обходя специальные имена)
Дает доступ к директориям с именами '0','1','-h' и т.д.
-d Изменение действия по умолчанию - подробно. (См. примечание)
-D Изменение действия по умолчанию - просто. (См. примечание)
-s<n> Переход к записи события <n>*
-S<n> Переход к записи события <n> и изменение ее текущей dir*
751
-f [<file>] Содержимое файла <file>.
-u [<file>] Обновление записи в <file>.
Если имя файла не указано, то по умолчанию используется файл
вида (${CDPath}${2:-"$CDFile"}) с -F и -U
-v Вывод номера версии
-h Справка
-H Подробная справка
752
CDL_PROMPT_PRE - Устанавливает в строке префикс приглашения.
По умолчанию:
не-root: \"\\[\\e[01;34m\\]\" (синий цвет).
root: \"\\[\\e[01;31m\\]\" (красный цвет).
CDL_PROMPT_POST - Устанавливает в строке суффикс строки
приглашения.
По умолчанию:
non-root: \"\\[\\e[00m\\]$\"
(сбрасывает цвет и выводит $).
root: \"\\[\\e[00m\\]#\"
(сбрасывает цвет и выводит #).
CDPath — Установка пути по умолчанию для опций -f и -u.
Умолчание — домашняя директория
CDFile — Установка по умолчанию имени файла для опций -f и -u.
Умолчание cdfile
"
cd_version
cd_version ()
{
printf "Версия: ${VERSION_MAJOR}.${VERSION_MINOR} Дата: ${VERSION_DATE}\n"
}
#
# Обрезается справа.
#
# параметры:
# p1 - строка
# p2 — размер обрезания
#
# возвращает строку в tcd
#
cd_right_trunc ()
{
local tlen=${2}
local plen=${#1}
local str="${1}"
local diff
local filler="<--"
if ${TEST} ${plen} -le ${tlen}
then
tcd="${str}"
else
let diff=${plen}-${tlen}
elen=3
if ${TEST} ${diff} -le 2
then
let elen=${diff}
fi
tlen=-${tlen}
let tlen=${tlen}+${elen}
tcd=${filler:0:elen}${str:tlen}
fi
}
#
# Три версии производства истории:
# cd_dohistory - Пакеты истории и событий шаг за шагом
753
# cd_dohistoryH — Показать только историю
# cd_dohistoryS — Показать только события
#
cd_dohistory ()
{
cd_getrc
${PRINTF} "История:\n"
local -i count=${cd_histcount}
while ${TEST} ${count} -ge 0
do
cd_right_trunc "${CD[count]}" ${cd_lchar}
${PRINTF} "%2d %-${cd_lchar}.${cd_lchar}s " ${count} "${tcd}"
cd_dohistoryH ()
{
cd_getrc
${PRINTF} "История:\n"
local -i count=${cd_maxhistory}
while ${TEST} ${count} -ge 0
do
${PRINTF} "${count} %-${cd_flchar}.${cd_flchar}s\n" ${CD[$count]}
count=${count}-1
done
}
cd_dohistoryS ()
{
cd_getrc
${PRINTF} "События:\n"
local -i count=${cd_maxspecial}
while ${TEST} ${count} -ge 0
do
${PRINTF} "S${count} %-${cd_flchar}.${cd_flchar}s\n" ${CDS[$count]}
count=${count}-1
done
}
cd_getrc ()
{
cd_flchar=$(stty -a | awk -F \;
'/rows/ { print $2 $3 }' | awk -F \ '{ print $4 }')
if ${TEST} ${cd_flchar} -ne 0
then
cd_lchar=${cd_flchar}/2-5
cd_rchar=${cd_flchar}/2-5
cd_flchar=${cd_flchar}-5
else
cd_flchar=${FLCHAR:=75}
# cd_flchar использует for для историй @s & @h
cd_lchar=${LCHAR:=35}
cd_rchar=${RCHAR:=35}
fi
}
cd_doselection ()
754
{
local -i nm=0
cd_doflag="TRUE"
if ${TEST} "${CD_MODE}" = "PREV"
then
if ${TEST} -z "$cd_npwd"
then
cd_npwd=0
fi
fi
tm=$(echo "${cd_npwd}" | cut -b 1)
if ${TEST} "${tm}" = "-"
then
pm=$(echo "${cd_npwd}" | cut -b 2)
nm=$(echo "${cd_npwd}" | cut -d $pm -f2)
case "${pm}" in
a) cd_npwd=${cd_sugg[$nm]} ;;
s) cd_npwd="${CDS[$nm]}" ;;
S) cd_npwd="${CDS[$nm]}" ; CDS[$nm]=`pwd` ;;
r) cd_npwd="$2" ; cd_specDir=$nm ; cd_doselection "$1" "$2";;
R) cd_npwd="$2" ; CDS[$nm]=`pwd` ; cd_doselection "$1" "$2";;
esac
fi
cd_chdefm ()
{
if ${TEST} "${CD_MODE}" = "PREV"
then
CD_MODE=""
if ${TEST} $1 -eq 1
then
${PRINTF} "${cd_mset}"
fi
else
755
CD_MODE="PREV"
if ${TEST} $1 -eq 1
then
${PRINTF} "${cd_mnset}"
fi
fi
}
cd_fsave ()
{
local sfile=${CDPath}${2:-"$CDFile"}
if ${TEST} "$1" = "SHOW"
then
${PRINTF} "Сохранено в %s\n" $sfile
fi
${RM} -f ${sfile}
local -i count=0
while ${TEST} ${count} -le ${cd_maxhistory}
do
echo "CD[$count]=\"${CD[$count]}\"" >> ${sfile}
count=${count}+1
done
count=0
while ${TEST} ${count} -le ${cd_maxspecial}
do
echo "CDS[$count]=\"${CDS[$count]}\"" >> ${sfile}
count=${count}+1
done
}
cd_upload ()
{
local sfile=${CDPath}${2:-"$CDFile"}
if ${TEST} "${1}" = "SHOW"
then
${PRINTF} "Загрузка из %s\n" ${sfile}
fi
. ${sfile}
}
cd_new ()
{
local -i count
local -i choose=0
cd_npwd="${1}"
cd_specDir=-1
cd_doselection "${1}" "${2}"
756
command cd "${cd_npwd}" 2>/dev/null
if ${TEST} $? -eq 1
then
${PRINTF} "Неизвестная директория: %s\n" "${cd_npwd}"
local -i ftflag=0
for i in "${cd_npwd}"*
do
if ${TEST} -d "${i}"
then
if ${TEST} ${ftflag} -eq 0
then
${PRINTF} "Предлагается:\n"
ftflag=1
fi
${PRINTF} "\t-a${choose} %s\n" "$i"
cd_sugg[$choose]="${i}"
choose=${choose}+1
fi
done
fi
fi
if ${TEST} ! -z "${CDL_PROMPTLEN}"
then
cd_right_trunc "${PWD}" ${CDL_PROMPTLEN}
cd_rp=${CDL_PROMPT_PRE}${tcd}${CDL_PROMPT_POST}
export PS1="$(echo -ne ${cd_rp})"
fi
}
#########################################################################
# #
# Инициализация #
# #
#########################################################################
#
VERSION_MAJOR="1"
VERSION_MINOR="2.1"
VERSION_DATE="24-MAY-2003"
#
alias cd=cd_new
#
# Устанавливаем команды
RM=/bin/rm
TEST=test
PRINTF=printf # Используем встроенный printf
#########################################################################
# #
# Измените, чтобы изменить умолчание до и после строки приглашения. #
# Вступит в силу при установке CDL_PROMPTLEN. #
# #
#########################################################################
if ${TEST} ${EUID} -eq 0
then
# CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="$HOSTNAME@"}
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;31m\\]"} # Root - красный
757
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]#"}
else
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;34m\\]"} # Юзеры - синий
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]$"}
fi
#########################################################################
#
# cd_maxhistory определяет максимальное количество записей истории.
typeset -i cd_maxhistory=50
#########################################################################
#
# cd_maxspecial определяет максимальное количество записи событий.
typeset -i cd_maxspecial=9
#
#
#########################################################################
#
# cd_histcount определяет число записей, выводимых на экран,
#+ истории команд.
typeset -i cd_histcount=9
#
#########################################################################
export CDPath=${HOME}/
# Измените, если используется другие #
#+ путь и имя файла #
export CDFile=${CDFILE:=cdfile} # для команд -u и -f #
#
#########################################################################
#
typeset -i cd_lchar cd_rchar cd_flchar
# Количество символов для cd_flchar #
cd_flchar=${FLCHAR:=75} #+ используемых для записей @s и @h #
# ==================================================================== #
: <<DOCUMENTATION
realtime@[Link]
=======================================================================
Эта версия cdll была протестирована на Linux с помощью Bash. Будет работать
большинстве версий Linux, но вероятно не будет работать в других оболочках без
758
изменения.
Введение
============
Установка cdll
===============
/etc/profile
~/.bash_profile
~/.bash_login
~/.profile
~/.bashrc
/etc/[Link]
759
Таким образом, сценарий выглядит примерно так:
######################################################################
# Настройка CD
######################################################################
CDL_PROMPTLEN=21 # Допускает строку длиной до 21 символа
. /usr/bin/cdll # Инициализация cdll
alias cd='cd_new' # Замена встроенной команды cd
alias @='cd_new @' # Допускает @ в командной строке для вывода истории
cd -U # Загрузка директорий
cd -D # Задает действие по умолчанию для не-posix
######################################################################
cd .
Использование
=====
cd [dir] [0-9] [@[s|h] [-g <dir>] [-d] [-D] [-r<n>]
[dir|0-9] [-R<n>] [<dir>|0-9] [-s<n>] [-S<n>]
[-u] [-U] [-f] [-F] [-h] [-H] [-v]
760
Примеры
========
/home/phil$ @
# Список записей @
History:
# Вывод команды @
.....
# Пропускаем эти записи для краткости
1 /home/phil/ummdev S1 /home/phil/perl
# Выводятся две самые последние записи истории
0 /home/phil/perl/eg S0 /home/phil/umm/ummdev
# и две записи событий
/home/phil$ cd /home/phil/utils/Cdll
# Теперь изменяем директории
/home/phil/utils/Cdll$ @
# Строка отображает директорию.
History:
# Новая история
.....
1 /home/phil/perl/eg S1 /home/phil/perl
# Запись истории 0 перемещена в 1
0 /home/phil S0 /home/phil/umm/ummdev
# и в самую последнюю запись
/home/phil/utils/Cdll$ cd 1
# Переход к записи истории 1.
/home/phil/perl/eg$
# Текущая директория теперь та, что была 1
/home/phil/perl/eg$ cd -s1
# Переход к записи события 1
/home/phil/umm/ummdev$
# Текущая директория S1
/home/phil$ cd -g 1
# -g игнорирует значение события 1
/home/phil/1$
761
события 3
$ cd -R3 4 Помещение текущей директории в запись события 3 и переход
к записи истории 4
$ cd -s3 Переход к записи события 3
ls -l ${CDS[3]}
cat ${CD[8]}/[Link]
Конфигурация
=============
762
root: "\\[\\e[00m\\]#"
(меняет цвет и выводит на экран #).
Примечание:
CDL_PROMPT_PRE & _POST только t
DOCUMENTATION
# Автор: Mkarcher
# [Link] ...
# /Script_for_configuring_the_CS4239_sound_chip_in_PnP_mode
# Автор ABS Guide сделал небольшие изменения и добавил комментарии.
# Couldn't contact script author to ask for permission to use, but ...
#+ the script was released under the FDL,
#+ so its use here should be both legal and ethical.
763
# Поиск звуковой pnp карты:
# Активация устройств:
# ThinkPad грузится с отключенными устройствами, «быстрая загрузка» отключена
#+ (в BIOS).
764
# irq 5
# dma 1
# dma 0
# ^^^ метка "bla" в первом поле (сброшена).
cport=${port1%%-*}
# ^^^^
# Нужен только _начальный_ адрес порта.
} < $CTLDEV/resources
# Загружаем модуль:
exit $?
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` FILENAME"
exit $E_WRONGARGS
fi
765
if [[ "$line" =~ ^$ ]]
then # Если строка пустая,
Flag=$ON #+ устанавливается флаг.
else
Flag=$OFF
fi
((lineno++))
done
} < $file # Файл перенаправляется на stdin функции.
file_read
exit $?
# ----------------------------------------------------------------
Это строка один примера абзаца, bla, bla, bla.
Это строка два, а строка три находится на следующей строке, но
766
DEBUG=1
read -a list <<( od -Ad -w24 -t u2 /dev/urandom ) # Случайный список.
# ^^ процесс подстановки
else
read -a list
fi
numelem=${#list[*]}
echo
echo "------"
echo $'Итог:\n'${list[@]}
exit $?
# =========================================================== #
# Подсчет общего отклонения:
#
# 1 Находим среднее арифметическое (обычное) всех элементов.
# 2 Вычитаем каждый элемент данных из среднего арифметического,
# и эту разницу возводим в квадрат.
767
# 3 Добавляем все отдельные квадраты разниц в #2.
# 4 Делим сумму в #3 на число элементов данных.
# Это известно как «отклонение».
# 5 Корень квадратный из #4 даст общее отклонение.
# =========================================================== #
if [ ! -e "$datafile" ]
then
echo "\""$datafile"\" не существует!"
exit $E_DATAFILE
fi
# -----------------------------------------------------
arith_mean ()
{
local rt=0 # Запущено всего.
local am=0 # Среднее арифметическое.
local ct=0 # Количество элементов данных.
echo $am; return $ct # Эта функция " возвращает" ДВА значения!
# Внимание: Этот трюк не будет работать, если $ct > 255!
# Для обработки большего количества элементов данных
#+ просто закомментируйте "return $ct" выше.
} <"$datafile" # Из файла данных.
sd ()
{
mean1=$1 # Среднее арифметическое (переданное в функцию).
n=$2 # Количество элементов данных.
sum2=0 # Сумма квадратов разностей ("отклонение").
avg2=0 # Среднее $sum2.
sdev=0 # Общее отклонение.
768
avg2=$(echo "scale=$SC; $sum2 / $n" | bc) # среднее суммы квадратов.
sdev=$(echo "scale=$SC; sqrt($avg2)" | bc) # Квадратный корень =
echo $sdev # общему отклонению.
# ======================================================= #
mean=$(arith_mean); count=$? # Два возвращаемых из функции!
std_dev=$(sd $mean $count)
echo
echo "Число элементов данных в \""$datafile"\" = $count"
echo "Среднее арифметическое (усредненное) = $mean"
echo "Общее отклонение = $std_dev"
echo
# ======================================================= #
exit
# ++++++++++++++++++++++++++++++++++++++++ #
# Пример файла данных ([Link]):
# 18.35
# 19.0
# 18.88
# 18.91
# 18.64
# $ sh [Link] [Link]
#######################################################
# PAD (xml) генератор файлов
#+ Написан Mendel Cooper <[Link]@[Link]>.
#+ Released to the Public Domain.
#
# Создает дескриптор файла "PAD" для условно-бесплатных
#+ пакетов, согласно спецификациям ASP.
# [Link]
#######################################################
769
# Принимает (не обязательно) сохраненное имя файла, как аргумент командной
#+ строки.
if [ -n "$1" ]
then
savefile=$1
else
savefile=save_file.xml # По умолчанию имя save_file.
fi
fill_in ()
{
if [ -z "$2" ]
then
echo -n "$1? " # Получены данные введенные пользователем.
else
echo -n "$1 $2? " # Запросить дополнительно?
fi
if [ -z "$var" ]
then
echo -e "\t\t<$1 />" >>$savefile # Отступ с 2 табуляциями.
return
else
echo -e "\t\t<$1>$var</$1>" >>$savefile
return ${#var} # Возвращается длина введенной строки.
fi
}
770
# Записываем заголовки файла в файл.
echo $HDR1 >$savefile
echo $HDR2 >>$savefile
echo $HDR3 >>$savefile
echo -e $HDR4 >>$savefile
echo -e $HDR5 >>$savefile
echo $HDR6 >>$savefile
# Company_Info
echo "ИНФОРМАЦИЯ О КОМПАНИИ"
CO_HDR="Company_Info"
echo "<$CO_HDR>" >>$savefile
fill_in Company_Name
fill_in Address_1
fill_in Address_2
fill_in City_Town
fill_in State_Province
fill_in Zip_Postal_Code
fill_in Country
# Если применимо:
# fill_in ASP_Member "[Y/N]"
# fill_in ASP_Member_Number
# fill_in ESC_Member "[Y/N]"
fill_in Company_WebSite_URL
# Contact_Info
echo "КОНТАКТЫ"
CONTACT_HDR="Contact_Info"
echo "<$CONTACT_HDR>" >>$savefile
fill_in Author_First_Name
fill_in Author_Last_Name
fill_in Author_Email
fill_in Contact_First_Name
fill_in Contact_Last_Name
fill_in Contact_Email
echo -e "\t</$CONTACT_HDR>" >>$savefile
# END Contact_Info
clear
# Support_Info
echo "ПОДДЕРЖКА"
SUPPORT_HDR="Support_Info"
echo "<$SUPPORT_HDR>" >>$savefile
fill_in Sales_Email
fill_in Support_Email
fill_in General_Email
fill_in Sales_Phone
fill_in Support_Phone
fill_in General_Phone
fill_in Fax_Phone
echo -e "\t</$SUPPORT_HDR>" >>$savefile
# END Support_Info
771
# END Company_Info
clear
# Program_Info
echo "ИНФОРМАЦИЯ О ПРОГРАММЕ"
PROGRAM_HDR="Program_Info"
echo "<$PROGRAM_HDR>" >>$savefile
fill_in Program_Name
fill_in Program_Version
fill_in Program_Release_Month
fill_in Program_Release_Day
fill_in Program_Release_Year
fill_in Program_Cost_Dollars
fill_in Program_Cost_Other
fill_in Program_Type "[Shareware/Freeware/GPL]"
fill_in Program_Release_Status "[Beta, Major Upgrade, etc.]"
fill_in Program_Install_Support
fill_in Program_OS_Support "[Win9x/Win2k/Linux/etc.]"
fill_in Program_Language "[English/Spanish/etc.]"
echo; echo
# File_Info
echo "ИНФОРМАЦИЯ О ПРОГРАММЕ"
FILEINFO_HDR="File_Info"
echo "<$FILEINFO_HDR>" >>$savefile
fill_in Filename_Versioned
fill_in Filename_Previous
fill_in Filename_Generic
fill_in Filename_Long
fill_in File_Size_Bytes
fill_in File_Size_K
fill_in File_Size_MB
echo -e "\t</$FILEINFO_HDR>" >>$savefile
# END File_Info
clear
# Expire_Info
echo "ИНФОРМАЦИЯ ОБ ОКОНЧАНИИ ДЕЙСТВИЯ"
EXPIRE_HDR="Expire_Info"
echo "<$EXPIRE_HDR>" >>$savefile
fill_in Has_Expire_Info "Y/N"
fill_in Expire_Count
fill_in Expire_Based_On
fill_in Expire_Other_Info
fill_in Expire_Month
fill_in Expire_Day
fill_in Expire_Year
echo -e "\t</$EXPIRE_HDR>" >>$savefile
# END Expire_Info
clear
# More Program_Info
echo "ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ПРОГРАММЕ"
fill_in Program_Change_Info
fill_in Program_Specific_Category
fill_in Program_Categories
fill_in Includes_JAVA_VM "[Y/N]"
772
fill_in Includes_VB_Runtime "[Y/N]"
fill_in Includes_DirectX "[Y/N]"
# END More Program_Info
clear
# Program_Description
echo "ОПИСАНИЕ ПРОГРАММЫ"
PROGDESC_HDR="Program_Descriptions"
echo "<$PROGDESC_HDR>" >>$savefile
LANG="English"
echo "<$LANG>" >>$savefile
fill_in Char_Desc_250
check_field_length 250 "$?"
fill_in Char_Desc_450
fill_in Char_Desc_2000
clear
echo "Сделано."; echo; echo
echo "Сохранено в файл: \""$savefile"\""
exit 0
773
savefile= # Глобальная переменная, используется несколькими функциями.
E_NOINPUT=90 # Отсутствие пользовательского ввода (ошибка). Может быть или
#+ не быть критической.
start ()
{
clear # Очистка экрана.
echo "ManEd"
echo "-----"
echo
echo "Простой создатель man page"
echo "Автор: Mendel Cooper"
echo "License: GPL3"
echo; echo; echo
}
progname ()
{
echo -n "Название программы? "
read name
if [ -n "$name" ]
then
savefile=""$name"."$section"" # Суффикс имени файла = раздел.
echo -n "$1 " >>$savefile
name1=$(echo "$name" | tr a-z A-Z) # Изменение на строчные,
echo -n "$name1" >>$savefile
else
echo "Ошибка! Отсутствует ввод." # Ввод обязателен.
exit $E_NOINPUT # Критично!
# Упражнение: Прерывите сценарий, если введенное имя ошибочно.
# Перепишите этот раздел, чтобы использовалось имя файла по
#+ умолчанию, если не введены входные данные.
fi
774
echo -n " \"$section\"">>$savefile # Добавление, всегда добавляем.
fill_in ()
{ # Эта функция в большей или меньшей степени скопирована из сценария "[Link]".
echo -n "$2? " # Получение ввода пользователя.
read var # Можно вставить (только одну строку!) при
#+ заполнения поля.
if [ -n "$var" ]
then
echo "$1 " >>$savefile
echo -n "$var" >>$savefile
else # Запись поля в файл не добавлено.
return $E_NOINPUT # Здесь это не критично.
fi
echo >>$savefile
end ()
{
clear
echo -n "Вы хотели бы просмотреть сохраненные страницы (y/n)? "
read ans
if [ "$ans" = "n" -o "$ans" = "N" ]; then exit; fi
exec less "$savefile" # Выход сценария и передача управления 'less' ...
#+ ... Которая форматирует исходник man page для
#+ просмотра.
}
# ---------------------------------------- #
start
progname "$TopHeader"
fill_in "$SynopsisHeader" "Синопсис"
fill_in "$DescHeader" "Подробное описание"
# Можно вставлять в текст *одной строкой*.
fill_in "$OptHeader" "Опции"
fill_in "$FilesHeader" "Файлы"
fill_in "$AuthHeader" "Автор"
fill_in "$BugsHeader" "Баги"
fill_in "$SeeAlsoHeader" "См. Так же"
# fill_in "$OtherHeader" ... при необходимости.
end # ... выход не требуется.
# ---------------------------------------- #
775
# Обратите внимание, что созданной странице обычно требуется тонкая ручная
#+ настройка с помощью текстового редактора.
# Тем не менее, намного облегчается написание исходника man с нуля или даже
#+ редактирования пустого шаблона man page.
# Основным недостатком сценария является то, что он позволяет вставлять в поля
#+ ввода только единственную текстовую строку.
# Это могут быть длинные, составленные вместе строки, которые groff
#+ автоматически складывает и расстанавливает переносы.
# Однако если вам необходимы несколько абзацев (разделенных переводами
#+ каретки), они должны быть вставлены путем ручного редактирования созданного
#+ сценария man page.
# Упражнение (трудное): Исправьте это!
#########################################################################
# Лепестки розы #
# #
# Version 0.1 Создан Serghey Rodin #
# Version 0.2 Изменен автором ABS Guide Author #
# #
# License: GPL3 #
# Используется в ABS Guide с разрешения. #
# ##################################################################### #
776
bone3[5]="| o o |"
bone3[6]="| o o |"
bone="+---------+"
# Функции
instructions () {
clear
echo -n "Вам нужны инструкции? (y/n) "; read ans
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
clear
echo -e '\E[34;47m' # Синий шрифт.
# "cat document"
cat <<INSTRUCTIONSZZZ
Игра называется Лепестки розы,
и это не просто название.
Бросаются пять костей, а вы должны угадать 'результат' каждого броска.
Это может быть ноль или другое число.
После вашего ответа, вам будет показан результат этого броска, но ...
это вся информация, которую вы получите.
fortune ()
{
RANGE=7
FLOOR=0
number=0
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # 1 - 6.
done
return $number
}
777
5 ) rose=4;;
* ) rose=0;;
esac # Упрощенный алгоритм.
# Действительно, не добраться до сути вопроса.
return $rose
}
answer=0
calc "$B1"; answer=$(expr $answer + $(echo $?))
calc "$B2"; answer=$(expr $answer + $(echo $?))
calc "$B3"; answer=$(expr $answer + $(echo $?))
calc "$B4"; answer=$(expr $answer + $(echo $?))
calc "$B5"; answer=$(expr $answer + $(echo $?))
}
game ()
{ # Создание графического отображения броска кости.
throw
echo -e "\033[1m" # Жирный.
echo -e "\n"
echo -e "$bone\t$bone\t$bone\t$bone\t$bone"
echo -e \
"${bone1[$B1]}\t${bone1[$B2]}\t${bone1[$B3]}\t${bone1[$B4]}\t${bone1[$B5]}"
echo -e \
"${bone2[$B1]}\t${bone2[$B2]}\t${bone2[$B3]}\t${bone2[$B4]}\t${bone2[$B5]}"
echo -e \
"${bone3[$B1]}\t${bone3[$B2]}\t${bone3[$B3]}\t${bone3[$B4]}\t${bone3[$B5]}"
echo -e "$bone\t$bone\t$bone\t$bone\t$bone"
echo -e "\n\n\t\t"
echo -e "\033[0m" # Выключение жирного.
echo -n "Сколько лепестков у розы? "
}
# ============================================================== #
Инструкции
778
break # Выход!
else echo "Пока у вас $hits правильных ответов."; echo
else
echo -e "\Не верно. У розы $answer лепестков.\n"
hits=0 # Сброс количества правильных ответов.
fi # Закрываем цикл if #2.
echo -n "Нажмите ENTER для следующего броска, или введите \"exit\" для
окончания. "
read
if [ "$REPLY" = "$EXIT" ]; then exit
fi
clear
done # Конец основного (while) цикла.
###
exit $?
# Источники:
# ---------
# 1) [Link]
# 2) [Link]
##############################################################
# QUACKEY: Облегченная версия Perquackey [TM]. #
# #
# Автор: Mendel Cooper <[Link]@[Link]> #
# version 0.1.02 03 May, 2008 #
# License: GPL3 #
##############################################################
WLIST=/usr/share/dict/[Link]
# ^^^^^^^^ Словарь находится здесь.
# Словарь в ASCII, в каждой строке одно слово, формат UNIX.
# Предлагаемый словарь - это пакет словаря автора "yawl".
# [Link]
# или
# [Link]
779
NULL=0 # Обнуление значения буквы (если найдена).
MINWLEN=3 # Минимальный размер слова.
MAXCAT=5 # Максимальное число слов в данной категории.
PENALTY=200 # Штраф за неприемлемые слова.
total=
E_DUP=70 # Ошибка при повторении слова.
declare -a Words
declare -a Status
declare -a Score=( 0 0 0 0 0 0 0 0 0 0 0 )
letters=( a n s r t m l k p r b c i d s i d z e w u e t f
e y e r e f e g t g h h i t r s c i t i d i j a t a o l a
m n a n o v n w o s e l n o s p a q e e r a b r s a o d s
t g t i t l u e u v n e o x y m r k )
# Таблица распределения букв нагло заимствована из игры «Wordy»,
#+ ca. 1992, написанной одним хорошим парнем по имени Mendel Cooper.
declare -a LS
numelements=${#letters[@]}
randseed="$1"
instructions ()
{
clear
echo "Добро пожаловать в QUACKEY, игру анаграмм."; echo
echo -n "Нужны инструкции? (y/n) "; read ans
ИНСТРУКЦИЯ1
echo -n "Нажмите ENTER для вывода следующей страницы инструкции. "; read az1
cat <<ИНСТРУКЦИЯ2
780
Первое 3-буквенное слово 60 очков, плюс 10 за каждое добавленное слово.
Первое 4-буквенное слово 120 очков, плюс 20 за каждое добавленное слово.
Первое 5-буквенное слово 200 очков, плюс 50 за каждое добавленное слово.
Первое 6-буквенное слово 300 очков, плюс 100 за каждое добавленное слово.
Первое 7-буквенное слово 500 очков, плюс 150 за каждое добавленное слово.
Первое 8-буквенное слово 750 очков, плюс 250 за каждое добавленное слово.
Первое 9-буквенное слово 1000 очков, плюс 500 за каждое добавленное слово.
Первое 10-буквенное слово 2000 очков, плюс 2000 за каждое добавленное слово.
ИНСТРУКЦИЯ2
echo -n "Нажмите ENTER для вывода конечной страницы инструкции. "; read az1
cat <<ИНСТРУКЦИЯ3
--------------------------------------------------
Статистика игры автоматически сохраняется в файл.
--------------------------------------------------
Для конкурсной ('повторной') игры, предыдущий набор букв может быть повторен в
сценарии параметром командной строки
Например, указывая набор букв "qky 7633"
c a d i f r h u s k ...
ИНСТРУКЦИЯ3
echo; echo -n "Нажмите ENTER для запуска игры. "; read az1
clear
seed_random ()
{ # Источник генератора случайных чисел
if [ -n "$randseed" ] # В соревновательном режиме можно задавать
then #+ для игры случайное число.
# RANDOM="$randseed"
echo "Источнику RANDOM присваивается "$randseed""
else
781
randseed="$$" # Или он получает случайное число из ID процесса.
echo "Источник RANDOM не указан, задается из ID процесса сценария ($$)."
fi
RANDOM="$randseed"
echo
}
get_letset ()
{
element=0
echo -n "Набор букв:"
echo
echo "${LS[@]}"
add_word ()
{
wrd="$1"
local idx=0
Status[0]=""
Status[3]=""
Status[4]=""
((idx++))
done
Words[idx]="$wrd"
get_score
get_score()
{
local wlen=0
local score=0
local bonus=0
local first_word=0
local add_word=0
782
local numwords=0
wlen=${#wrd}
numwords=${Score[wlen]}
Score[2]=0
Status[4]="" # Присвоение "бонуса" 0.
case "$wlen" in
3) first_word=60
add_word=10;;
4) first_word=120
add_word=20;;
5) first_word=200
add_word=50;;
6) first_word=300
add_word=100;;
7) first_word=500
add_word=150;;
8) first_word=750
add_word=250;;
9) first_word=1000
add_word=500;;
10) first_word=2000
add_word=2000;; # Эта категория отличается от первоначальной!
esac
((Score[wlen]++))
if [ ${Score[wlen]} -eq $MAXCAT ]
then # Категория завершает упрощенный подсчет бонусов!
case $wlen in
3 ) bonus=100;;
4 ) bonus=200;;
5 ) bonus=400;;
6 ) bonus=800;;
7 ) bonus=2000;;
8 ) bonus=10000;;
esac # Не нужно беспокоиться о 9-й и 10-й.
Status[4]="Категория-Завершается-$wlen-***БОНУСАМИ***"
Score[2]=$bonus
else
Status[4]="" # Обнуление.
fi
get_word ()
{
local wrd=''
read -t $TIMEOUT wrd # Значение времени чтения
783
echo $wrd
}
is_constructable ()
{ # Это наиболее сложная и трудно описываемая функция.
local -a local_LS=( "${LS[@]}" ) # Локальная копия набора букв.
local is_found=0
local idx=0
local pos
local strlen
local local_word=( "$1" )
strlen=${#local_word}
echo "$SUCCESS"
return
}
is_valid ()
{ # Удивительно просто проверяет, если слово в словаре ...
fgrep -qw "$1" "$WLIST" # ... любезным 'grep' ...
echo $?
}
check_word ()
{
if [ -z "$1" ]
then
return
fi
Status[1]=""
Status[2]=""
Status[3]=""
Status[4]=""
iscons=$(is_constructable "$1")
if [ "$iscons" ]
then
Status[1]="constructable"
v=$(is_valid "$1")
if [ "$v" -eq "$SUCCESS" ]
then
Status[2]="valid"
strlen=${#1}
784
return $NG
fi
case "$strlen" in
1 | 2 )
Status[3]="ШТРАФ-за-двух-буквенное-слово"
return $NG;;
* )
Status[3]=""
return $SUCCESS;;
esac
else
Status[3]="Не-правильно-ШТРАФ"
return $NG
fi
else
Status[3]="Не-составлено-ШТРАФ"
return $NG
fi
display_words ()
{
local idx=0
local wlen0
clear
echo "Набор букв: ${LS[@]}"
echo "Трех: Четырех: Пяти: Шести: Семи: Восьми:"
echo "------------------------------------------------------------"
play ()
{
word="Начало игры" # Образец слова, для начала ...
785
do #+ то игра заканчивается.
echo "$word: "${Status[@]}""
echo -n "Последние очки: [${Score[0]}] ВСЕГО очков: [${Score[1]}]:
Следующее слово: "
total=${Score[1]}
word=$(get_word)
check_word "$word"
display_words
done # Выход из игры.
end_of_game ()
{ # Сохранение и вывод статистики.
#######################Автосохранение##########################
savefile=[Link].$$
# ^^ PID сценария
echo `date` >> $savefile
echo "Набор букв # $randseed (случайный источник) ">> $savefile
echo -n "Набор букв: " >> $savefile
echo "${LS[@]}" >> $savefile
echo "---------" >> $savefile
echo "Слово составлено:" >> $savefile
echo "${Words[@]}" >> $savefile
echo >> $savefile
echo "Очки: $total" >> $savefile
# ---------#
инструкции
seed_random
get_letset
play
end_of_game
# ---------#
exit $?
# СДЕЛАТЬ:
#
# 1) Очистить код!
# 2) Приукрасить функцию display_words() (возможно виджетами?).
# 3) Улучшить тайм-аут... возможно изменить запись untimed,
786
#+ но с ограничением времени для общего раунда.
# 4) Будет красиво, если время будет отсчитываться на экране.
# 5) Реализация «уязвимого» режима игры для совместимости с
#+ классической версией игры.
# 6) Улучшить возможность сохранения в файл (и, возможно, сделать ее
#+ не обязательной).
# 7) Исправить баги!!!
instructions ()
{
clear
tput bold
echo "Добро пожаловать в игру Nim."; echo
echo -n "Нужна подсказка? (y/n) "; read ans
1: | | | | |
2: | | | |
3: | | |
4: | |
5: |
787
Игрок, который удаляет последний колышек проигрывает.
echo; echo -n "Нажмите ENTER для запуска игры. "; read azx
clear
tally_up ()
{
let "Rows[0] = ${Rows[1]} + ${Rows[2]} + ${Rows[3]} + ${Rows[4]} + \
${Rows[5]}" # Суммирование оставшихся колышков.
}
display ()
{
index=1 # Начинаем с верхнего ряда.
echo
# ------------------------------------------------
# Два параллельных внутренних цикла.
indent=$index
while [ "$indent" -gt 0 ]
do
echo -n " " # Ряды в шахматном порядке.
((indent--)) # Пробелы между колышками.
done
echo
((index++))
done
tally_up
rp=${Rows[0]}
if [ "$rp" -eq 1 ]
then
788
peg_msg=peg
final_msg="Игра окончена."
else # Игра еще не окончена ...
peg_msg=pegs
final_msg="" # ... Поэтому «финальное сообщение» пустое.
fi
echo
}
player_move ()
{
done
if [ -z "$num" ]
then
echo "Преждевременный выход."; echo
tput sgr0 # Восстанавливаем изображение на экране.
exit $QUIT
fi
789
break
fi
done
# СДЕЛАТЬ:
# Добавить проверку не числового ввода.
# Кроме того сценарий падает при вводе большого количества повторов
#+ вне диапазона.
# Исправьте это.
display
tally_up
if [ ${Rows[0]} -eq 1 ]
then
echo " Победил человек!"
echo " Поздравления!"
tput sgr0 # Восстановление изображения на экране.
echo
exit $WON
fi
if [ ${Rows[0]} -eq 0 ]
then # Получите поражение, а не победу . . .
echo " Дурачок!"
echo " Вы должны удалить последний колышек!"
echo " Победил бот!"
tput sgr0 # Восстановление изображения на экране.
echo
exit $LOST
fi
}
bot_move ()
{
row_b=0
while [[ $row_b -eq 0 || ${Rows[row_b]} -eq 0 ]]
do
row_b=$RANDOM # Выбираем случайный ряд.
let "row_b %= $ROWS"
done
num_b=0
r0=${Rows[row_b]}
if [ "$r0" -eq 1 ]
then
num_b=1
else
let "num_b = $r0 - 1"
# Оставляет только один колышек в ряду.
fi # Не очень хорошая стратегия,
#+ но, вероятно, намного лучше, чем совершенно случайно.
790
if [ "$num_b" -eq 1 ]
then
peg_msg=peg
else
peg_msg=pegs
fi
display
tally_up
if [ ${Rows[0]} -eq 1 ]
then
echo " Победил бот!"
tput sgr0 # Восстановление изображения на экране.
exit $WON
fi
# ================================================== #
инструкции # Если человеку нужны ...
tput bold # Жирные символы для более удобного просмотра.
display # Показывает игровое поле.
# Упражнения:
# --------
# Улучшите стратегию бота.
# Существует, по сути, стратегия Nim, которая может принести победу.
# См. раздел Wikipedia о Nim: [Link]
# Перекодируйте бот для использования этой стратегии (довольно трудно).
791
# Для разделения - знак'?' должен быть введен дважды, если перед ним
# (в той же строке) введены символы (ошибочные).
# Пока не вызывается '?', сигнал, на сильно
# загруженных системах, может слегка отставать.
# Изменения:
# V1.0, 23 Aug 2005, Initial release
# V1.1, 26 Jul 2007, Allow both splits and laps from single invocation.
# Only start timer after a key is pressed.
# Indicate lap number
# Cache programs at startup so there is less error
# due to startup delays.
# V1.2, 01 Aug 2007, Work around `date` commands that don't have
# nanoseconds.
# Use stty to change interrupt keys to space for
# laps etc.
# Ignore other input as it causes problems.
# V1.3, 01 Aug 2007, Testing release.
# V1.4, 02 Aug 2007, Various tweaks to get working under ubuntu
# and Mac OS X.
# V1.5, 27 Jun 2008, set LANG=C as got vague bug report about it.
export LANG=C
cache_progs() {
stty > /dev/null
date > /dev/null
grep . < /dev/null
(echo "import time" | python) 2> /dev/null
bc < /dev/null
sed '' < /dev/null
printf '1' > /dev/null
/usr/bin/time false 2> /dev/null
cat < /dev/null
}
cache_progs # Минимизация задержки запуска.
date +%s.%N | grep -qF 'N' && use_python=1 # Если `date` без наносекунд.
now() {
if [ "$use_python" ]; then
echo "import time; print [Link]()" 2>/dev/null | python
else
printf "%.2f" `date +%s.%N`
fi
}
fmt_seconds() {
seconds=$1
mins=`echo $seconds/60 | bc`
if [ "$mins" != "0" ]; then
seconds=`echo "$seconds - ($mins*60)" | bc`
792
echo "$mins:$seconds"
else
echo "$seconds"
fi
}
total() {
end=`now`
total=`echo "$end - $start" | bc`
fmt_seconds $total
}
stop() {
[ "$lapped" ] && lap "$laptime" "display"
total
exit
}
lap() {
laptime=`echo "$1" | sed -n 's/.*real[^0-9.]*\(.*\)/\1/p'`
[ ! "$laptime" -o "$laptime" = "0.00" ] && return
# Слишком частые сигналы.
laptotal=`echo $laptime+0$laptotal | bc`
if [ "$2" = "display" ]; then
lapcount=`echo 0$lapcount+1 | bc`
laptime=`fmt_seconds $laptotal`
echo $laptime "($lapcount)"
lapped="true"
laptotal="0"
fi
}
while true; do
trap true INT QUIT # Установка сигнала обработки.
laptime=`/usr/bin/time -p 2>&1 cat >/dev/null`
ret=$?
trap '' INT QUIT # Игнорировать сигналы сценария.
if [ $ret -eq 1 -o $ret -eq 2 -o $ret -eq 130 ]; then # SIGINT = стоп
[ ! "$start" ] && { echo >&2; exit; }
stop
elif [ $ret -eq 3 -o $ret -eq 131 ]; then # SIGQUIT = этап
if [ ! "$start" ]; then
start=`сейчас` || exit 1
echo >&2
continue
fi
lap "$laptime" "display"
else # eof = разделитель
[ ! "$start" ] && continue
total
lap "$laptime" # Обновление.
fi
done
exit $?
793
Пример A-44. Универсальность решения домашних заданий сценариев оболочки
#!/bin/bash
# [Link]: Универсальность решения домашних заданий.
# Автор: M. Leo Cooper
# Если вы замените имя автора на ваше собственное имя, то это будет плагиат,
#+ возможно меньший грех, чем обман при решении домашнего задания!
# License: Public Domain
DLA=1
P1=2
P2=4
P3=7
PP1=0
PP2=8
MAXL=9
E_LZY=99
declare -a L
L[0]="3 4 0 17 29 8 13 18 19 17 20 2 19 14 17 28"
L[1]="8 29 12 14 18 19 29 4 12 15 7 0 19 8 2 0 11 11 24 29 17 4 6 17 4 19"
L[2]="29 19 7 0 19 29 8 29 7 0 21 4 29 13 4 6 11 4 2 19 4 3"
L[3]="19 14 29 2 14 12 15 11 4 19 4 29 19 7 8 18 29"
L[4]="18 2 7 14 14 11 22 14 17 10 29 0 18 18 8 6 13 12 4 13 19 26"
L[5]="15 11 4 0 18 4 29 0 2 2 4 15 19 29 12 24 29 7 20 12 1 11 4 29"
L[6]="4 23 2 20 18 4 29 14 5 29 4 6 17 4 6 8 14 20 18 29"
L[7]="11 0 25 8 13 4 18 18 27"
L[8]="0 13 3 29 6 17 0 3 4 29 12 4 29 0 2 2 14 17 3 8 13 6 11 24 26"
L[9]="19 7 0 13 10 29 24 14 20 26"
declare -a \
alph=( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z . , : ' ' )
pt_lt ()
{
echo -n "${alph[$1]}"
echo -n -e "\a"
sleep $DLA
}
b_r ()
{
echo -e '\E[31;48m\033[1m'
}
cr ()
{
echo -e "\a"
sleep $DLA
}
restore ()
{
794
echo -e '\033[0m' # Отключаем жирный.
tput sgr0 # Включаем обычный.
}
p_l ()
{
for ltr in $1
do
pt_lt "$ltr"
done
}
# ----------------------
b_r
restore
# ----------------------
echo
exit $E_LZY
###################################################################
# Рыцарский турнир, обычные проблемы. #
# ===================================== #
# Рыцарь ходит на следующий квадрат шахматной доски, #
# но ему нельзя возвращаться на любой, уже посещенный, квадрат. #
# #
# А правда, почему Сэру Рыцарю нельзя отступать? #
# Может быть он имеет привычку развлекаться в предрассветные #
#+ утренние часы #
795
# Возможно он оставляет крошки от пиццы в постели, пустые пивные #
#+ бутылки на полу и засоряет сантехнику .... #
# #
# ------------------------------------------------------------- #
# #
# Использование: [Link] [начальная клетка] [глупость] #
# #
# Обратите внимание, что номер стартовой клетки может находиться #
#+ в диапазоне 0 - 63 ... или #
# клетка обозначается в обычной шахматной нотации, #
# как a1, f5, h3, и т.д.. #
# #
# Если стартовая клетка не указана, #
#+ то стартует со случайной клетки доски. #
# #
# "глупость" второй параметр в бестолковой стратегии. #
# #
# Примеры: #
# [Link] 23 начать с клетки #23 (h3) #
# [Link] g6 stupid начать с клетки #46, #
# используется стратегия "stupid" (не-Warnsdorff). #
###################################################################
VISITED=1
UNVISITED=-1
UNVSYM="##"
# ---------------------------------------- #
# Глобальные переменные.
startpos= # Стартовая позиция (клетки #0 - 63).
currpos= # Текущая позиция.
movenum= # Номер хода.
CRITPOS=37 # Временная стартовая позиция f5!
declare -i board
# Используется одномерный массив для имитации двумерного.
# Это может осложнить жизнь и привести к уродству; См. ниже.
declare -i moves # Смещения от текущей позиции рыцаря.
initialize_board ()
{
local idx
796
done
}
print_board ()
{
local idx
failure()
{
echo
print_board
echo
echo " О!!! Нет клеток для ходов!"
echo -n " Рыцарский турнир окончен"
echo " на $(to_algebraic $currpos) [square #$currpos]"
echo " после $movenum ходов!"
echo
exit $FAIL
}
797
local xc=$1
local yc=$2
local ix
local ix_count=0
local b_index # Индекс доски [0-63]
local alpos="$1"
798
fi
fi
for ix in a b c d e f g h
do # Преобразование колонки букв в колонку цифр.
if [ "$arow" = "$ix" ]
then
break
fi
((ix_count++)) # Увеличиваем индекс отсчета.
done
return $b_index
799
fi
let "move4 = $kt_skip + $ROWS" # 2 вправо, 1 вверх
if [[ `expr $row_pos + $kt_skip` -ge $COLS ]]
then
move4=$BADMOVE
else
((valmov++))
fi
let "move5 = $kt_skip - $ROWS" # 2 вправо, 1 вниз
if [[ `expr $row_pos + $kt_skip` -ge $COLS ]]
then
move5=$BADMOVE
else
((valmov++))
fi
let "move6 = $kt_hop - $kt_skip * $ROWS" # 1 вправо, 2 вниз
if [[ `expr $row_pos + $kt_hop` -ge $COLS ]]
then
move6=$BADMOVE
else
((valmov++))
fi
let "move7 = -$kt_hop - $kt_skip * $ROWS" # 1 влево, 2 вниз
if [[ `expr $row_pos - $kt_hop` -lt $MIN ]]
then
move7=$BADMOVE
else
((valmov++))
fi
let "move8 = -$kt_skip - $ROWS" # 2 влево, 1 вниз
if [[ `expr $row_pos - $kt_skip` -lt $MIN ]]
then
move8=$BADMOVE
else
((valmov++))
fi # Должен быть лучший способ, что бы сделать это.
local m=( $valmov $move1 $move2 $move3 $move4 $move5 $move6 $move7 $move8 )
# ${moves[0]} = число правильных ходов.
# ${moves[1]} ... ${moves[8]} = возможные ходы.
echo "${m[*]}" # Элементы массива захватаченной переменной stdout.
800
lmin=$ROWS
iex=0
squarel=
mpm=
mov=
declare -a p_moves
board[square]=$movenum
currpos=$square # Обновление текущей позиции.
((valid_moves++)); # ходы[0]=$valid_moves
aapos=$(to_algebraic $square)
echo -n "$aapos "
test $(( $Moves % $LINELEN )) -eq 0 && echo
# Вывод ходов LINELEN=21 построчно. Правильный тур выводит 3 строки.
return $valid_moves # Выбор клетки для хода!
}
801
((valid_moves++)); # ходы[0]=$valid_moves
aapos=$(to_algebraic $squareloc)
echo -n "$aapos "
test $(( $Moves % $LINELEN )) -eq 0 && echo # Вывод 21 хода/строки.
return $valid_moves # Выбор клетки для хода!
fi
done
return $FAIL
# Если во всех 8 проходах не найдено свободных клеток,
#+ то Рыцарский турнир завершается поражением.
possible_moves $squarel
mpm=$?
p_moves[mov]=$mpm
fi
done
}
802
if [ $mpm -le $lmin ] # Если меньше чем или равно предыдущему минимуму!
then # ^^
lmin=$mpm
iex=$mov
fi
fi
done # Это можно улучшить
}
if [ -z "$1" ]
then
return $FAIL
fi
local curr_pos=$1
local valid_movl=0
local icx=0
local movl
local sq
declare -a movesloc
strategy ()
{
echo
if [ -n "$STUPID" ]
then
for Moves in {1..63}
do
cposl=$1
moves=( $(generate_moves $currpos) )
do_move_stupid "$currpos"
if [ $? -eq $FAIL ]
then
failure
fi
done
fi
803
# Здесь не нужно "else",
#+ потому, что Глупая стратегия всегда приводит к поражению и выходу!
for Moves in {1..63}
do
cposl=$1
moves=( $(generate_moves $currpos) )
do_move "$currpos"
if [ $? -eq $FAIL ]
then
failure
fi
done
# Два цикла do, выше, можно объединить в один, но это
echo #+ замедлит все исполнение.
print_board
echo
echo "Рыцарский турнир окончен на $(to_algebraic $currpos) [square
#$currpos]."
return $SUCCESS
}
debug ()
{ # Включаем настройку DEBUG=1 в начале сценария.
local n
echo "================================="
echo " Ход номер $movenum:"
echo " *** возможных ходов = $mpm ***"
# echo "### клетка = $square ###"
echo "lmin = $lmin"
echo "${moves[@]}"
for n in {1..8}
do
echo -n "($n):${p_moves[n]} "
done
echo
echo "iex = $iex :: moves[iex] = ${moves[iex]}"
echo "square = $square"
echo "================================="
echo
} # Дает довольно полное представление о состоянии после каждого хода.
# =============================================================== #
# int main () {
from_algebraic "$1"
startpos=$?
if [ "$startpos" -eq "$FAIL" ] # Нормально, даже если нет $1.
then # ^^^^^^^^^^^ Нормально, даже если введено -lt 0.
echo "Нет указания на стартовую клетку (или не правильный ввод)."
let "startpos = $RANDOM % $SQUARES" # Допустимый диапазон 0 - 63.
fi
if [ "$2" = "stupid" ]
then
804
STUPID=1
echo -n " ### Глупая стратегия ###"
else
STUPID=''
echo -n " *** Алгоритм Warnsdorff ***"
fi
initialize_board
movenum=0
board[startpos]=$movenum # Отмечает каждую клетку доски с номером хода.
currpos=$startpos
algpos=$(to_algebraic $startpos)
strategy "$currpos"
echo
exit 0 # return 0;
# Упражнения:
# ---------
#
# 1) Расширьте этот пример для доски 10 x 10 или более.
# 2) Улучшите 'глупую' стратегию, изменив
# функцию do_move_stupid.
# Подсказка: Избегацте ходов в угловые клетки в начале игры
# (в противоположность алгоритму Warnsdorff!).
# 3) Этот сценарий можно значительное улучшить и оптимизировать,
# особенно в плохо написанной функции generate_moves ()
# и в патче DECIDE-MOVE функции do_move().
# Необходимо понять, почему стандартный алгоритм не выполняется для
#+ startpos=37 ... а _не_ для_любой_другой, в том числе симметричной
#+ startpos=26.
# Возможно, при расчете возможных ходов, считать обратные ходы в исходный
#+ квадрат. Если так, то это можно относительно легко исправить.
805
# целых чисел в котором все ряды, столбцы
# и *большие* диагонали равны одной и той же сумме чисел в них.
# Этот "квадрат," массив, имеет какое-то одинаковое число рядов и
# стобцов. Это число является "порядком."
# Пример магического квадрата 3 порядка:
# 8 1 6
# 3 5 7
# 4 9 2
# Все ряды, столбцы, и две диагонали имеют в сумме 15.
# Глобальные
EVEN=2
MAXSIZE=31 # 31 ряд x 31 колонку.
E_usage=90 # Ошибка вызова.
dimension=
declare -i square
usage_message ()
{
echo "Usage: Порядок $0"
echo " ... где \"порядок\" (размер квадрата) нечетное целое число"
echo " в диапазоне 3 - 31."
# На самом деле работает с квадратами до порядка 159,
#+ но большие квадраты не будут нормально выводиться в окне терминала.
# Попробуйте увеличить MAXSIZE, выше.
exit $E_usage
}
let "dimadj = $dimension * 3"; let "dimadj /= 2" # x 1.5, затем обрезается.
806
let "idx = $row * $dimension + $col"
printf "%3d " "${square[idx]}"; echo -n " "
done # Выводится порядок 13, аккуратные 80-колонок в окне терминала.
#################################################
if [[ -z "$1" ]] || [[ "$1" -gt $MAXSIZE ]]
then
usage_message
fi
calculate $1
print_square # echo "${square[@]}" # ОТЛАДКА
exit $?
#################################################
# Упражнения:
# ---------
# 1) Добавьте функцию для подсчета суммы каждого ряда, столбца,
# и *длинной* диагонали. Суммирование должно быть математическим.
# Это "волшебная постоянная" конкретного порядка данного квадрата.
# 2) Автоматическое вычисление функцией print_square, сколько места нужно
# выделить между элементами квадрата для оптимизации изображения.
# Может потребоваться параметризация строки «printf».
# 3) Добавьте соответствующие функции для создания магических квадратов
# *даже* с числом строк/столбцов. Это не обычно(!).
# См. URL Kwon Young Shin, выше, для справки.
# Классические "Пятнашки"
# Автор: Antonio Macchi
# Слегка отредактировано и прокомментировано автором ABS Guide.
# В ABS Guide используется с разрешения. (С!)
807
# | 9 10 11 12 |
# | 13 14 15 |
# ----------------
#######################
# Константы #
SQUARES=16 #
FAIL=70 #
E_PREMATURE_EXIT=80 #
#######################
##########
# Данные #
##########
###########
# Функции #
###########
function swap
{
local tmp
tmp=${Puzzle[$1]}
Puzzle[$1]=${Puzzle[$2]}
Puzzle[$2]=$tmp
}
function Jumble
{ # Перемешивание перед началом игры.
local i pos1 pos2
for i in {1..100}
do
pos1=$(( $RANDOM % $SQUARES))
pos2=$(( $RANDOM % $SQUARES ))
swap $pos1 $pos2
done
}
function PrintPuzzle
{
local i1 i2 puzpos
puzpos=0
clear
echo "Введите quit для выхода."; echo # Лучше чем Ctl-C.
808
(( puzpos++ ))
done
echo "|" # Правая граница.
test $i1 = 4 || echo "+----+----+----+----+"
done
echo "'----'----'----'----'" # Нижняя граница.
}
function GetNum
{ # Проверка правильности ввода.
local puznum garbage
while true
do
echo "Ходы: $moves" # Подсчет правильных ходов.
read -p "Число для хода: " puznum garbage
if [ "$puznum" = "quit" ]; then echo; exit $E_PREMATURE_EXIT; fi
test -z "$puznum" -o -n "${puznum//[0-9]/}" && continue
test $puznum -gt 0 -a $puznum -lt $SQUARES && break
done
return $puznum
}
function GetPosFromNum
{ # $1 = puzzle-number
local puzpos
function Move
{ # $1=Puzzle-pos
test $1 -gt 3 && test "${Puzzle[$(( $1 - 4 ))]}" = " "\
&& swap $1 $(( $1 - 4 )) && return 0
test $(( $1%4 )) -ne 3 && test "${Puzzle[$(( $1 + 1 ))]}" = " "\
&& swap $1 $(( $1 + 1 )) && return 0
test $1 -lt 12 && test "${Puzzle[$(( $1 + 4 ))]}" = " "\
&& swap $1 $(( $1 + 4 )) && return 0
test $(( $1%4 )) -ne 0 && test "${Puzzle[$(( $1 - 1 ))]}" = " " &&\
swap $1 $(( $1 - 1 )) && return 0
return 1
}
function Solved
{
local pos
809
}
echo;echo
PrintPuzzle
echo; echo "БРАВО!"; echo
exit 0
###################################################}
# Упражнение:
# --------
# Перепишите сценарий для вывода на экран, вместо чисел 1 — 15,
#+ букв от A до O.
# [Link]
# Version 2.00: modded for ASCII-graphic display.
# Version 2.01: fixed no command-line param bug.
# Используемый код предоставлен Antonio Macchi,
#+ и сильно отредактирован автором ABS Guide.
# Этот вариант подпадает под настоящие авторские права, см. выше.
# В ABS Guide используется с разрешения Amit Singh (спасибо!).
E_NOPARAM=86
E_BADPARAM=87 # Неправильное количество дисков переданных сценарию.
E_NOEXIT=88
810
DISKS=${1:-$E_NOPARAM} # Должно быть указано количество дисков.
Moves=0
MWIDTH=7
MARGIN=2
# Произвольные «волшебные» константы ; хорошо работают для относительно
#+ малого количества дисков.
# BASEWIDTH=51 # Оригинальный код.
let "basewidth = $MWIDTH * $DISKS + $MARGIN" # «База» под стержнями.
# «Алгоритм» вероятно был улучшен.
function FromRod {
local rod summit weight sequence
while true; do
rod=$1
test ${rod/[^123]/} || continue
while true; do
rod=$2
test ${rod/[^123]} || continue
811
done
test $weight -gt $1 -o $firstfree = 0 &&
{ echo "$rod $firstfree"; return; }
done
}
function PrintRods {
local disk rod empty fill sp sequence
display ()
{
echo
PrintRods
812
0)
;;
*)
dohanoi "$(($1-1))" $2 $4 $3
if [ "$Moves" -ne 0 ]
then
echo "+ Позиция после хода $Moves"
fi
((Moves++))
echo -n " Следующий ход будет: "
echo $2 "-->" $3
display $2 $3
dohanoi "$(($1-1))" $4 $3 $2
;;
esac
}
setup_arrays ()
{
local dim n elem
setup_arrays $DISKS
echo; echo "+ Start Position"
case $# in
1) case $(($1>0)) in # Должен иметься в наличии хотя бы один диск.
1)
disks=$1
dohanoi $1 1 3 2
# Total moves = 2^n - 1, где n = количество дисков.
echo
exit 0;
;;
*)
echo "$0: Неправильное значение количества дисков";
exit $E_BADPARAM;
;;
esac
;;
*)
clear
echo "Используйте: $0 N"
echo " Где \"N\" это число дисков."
exit $E_NOPARAM;
;;
813
esac
# Примечание:
# Перенаправьте вывод сценария в файл, в противном случае он выведется на
#+ экран.
# [Link]
# Version 2: modded for ASCII-graphic display.
# Uses code contributed by Antonio Macchi,
#+ with heavy editing by ABS Guide author.
# This variant also falls under the original copyright, see above.
# Используется в ABS Guide с разрешения Amit Singh (спасибо!).
# Переменные #
E_NOPARAM=86
E_BADPARAM=87 # Не правильное число дисков передаваемых сценарию.
E_NOEXIT=88
DELAY=2 # Интервал, в секундах, между ходами. Измените, если нужно.
DISKS=$1
Moves=0
MWIDTH=7
MARGIN=2
# Произвольные «волшебные» константы ; хорошо работают для относительно малого
#+ количества дисков.
# BASEWIDTH=51 # Оригинальный код.
let "basewidth = $MWIDTH * $DISKS + $MARGIN" # "База" для стержней.
# "Алгоритм" выше вероятно улучшен.
#################
814
done
}
function FromRod {
local rod summit weight sequence
while true; do
rod=$1
test ${rod/[^123]/} || continue
while true; do
rod=$2
test ${rod/[^123]} || continue
function PrintRods {
local disk rod empty fill sp sequence
tput cup 5 0
815
repeat "=" $basewidth # Отрисовка "базы" стержней.
echo
}
display ()
{
echo
PrintRods
if [ "${Rod3[lastmove_t]}" = 1 ]
then # Последний ход? Если да, то показывается финальная позиция.
tput cup 0 0
echo; echo "+ Финальная позиция: $Moves ходов"
PrintRods
fi
sleep $DELAY
}
setup_arrays ()
{
local dim n elem
816
Rod2[$n]=0
Rod3[$n]=0
((elem--))
done
}
setup_arrays $DISKS
tput cup 0 0
echo; echo "+ Начальная позиция"
case $# in
1) case $(($1>0)) in # Должен иметься хотя бы один диск.
1)
disks=$1
dohanoi $1 1 3 2
# Total moves = 2^n - 1, где n = # диска.
echo
exit 0;
;;
*)
echo "$0: Неправильное значение для номера диска";
exit $E_BADPARAM;
;;
esac
;;
*)
echo "usage: $0 N"
echo " Где \"N\" это количество дисков."
exit $E_NOPARAM;
;;
esac
# Упражнение:
# --------
# Есть небольшая ошибка в сценарии, которая вызывает сброс вывода
#+ предпоследнего хода.
#+ Исправьте это.
UseGetOpt () {
declare inputOptions
declare -r E_OPTERR=85
817
declare -r ScriptName=${0##*/}
declare -r ShortOpts="adf:hlt"
declare -r LongOpts="aoption,debug,file:,help,log,test"
DoSomething () {
echo "Имя функции '${FUNCNAME}'"
# Напомним, что $FUNCNAME является внутренней переменной,
#+ содержащей имя функции.
}
while true; do
case "${1}" in
--aoption | -a) # Найденные аргументы.
echo "Опция [$1]"
;;
*) # Got it
echo "Опция [$1] Используется ввод [$2]"
shift
;;
esac
DoSomething
;;
818
;;
--help | -h)
echo "Опция [$1] Вывод справки"
break
;;
Not there --) # Сделано! $# номер аргумента для "--", $@ это "--"
echo "Опция [$1] Двойное тире"
break
;;
*)
echo "Главная внутренняя ошибка!"
exit 8
;;
esac
echo "Количество аргументов: [$#]"
shift
done
shift
# Только в образовательных целях. Можно удалить
#----------------------------------------------------------------------
echo "++ Проверка: Количество аргументов после \"--\" это [$#] Они: [$@]"
echo '++ Проверка: Проходов цикла "$@"'
for a in "$@"; do
echo " ++ [$a]"
done
#----------------------------------------------------------------------
################################### О С Н О В Н О Е ########################
# Если удалить "функцию UseGetOpt () {" и передачу "}",
#+ можно не раскомментировать строку "exit 0" ниже, а вызывать этот сценарий
#+ с различными опциями из командной строки.
#-------------------------------------------------------------------
# exit 0
exit
819
#!/bin/bash
# [Link]
# Измененный сценарий для иллюстрации табличных расширений
#+ опций командной строки.
# См. приложение "Введение в табличные расширения".
# Доступные опции: -a -d -f -l -t -h
#+ --aoption, --debug --file --log --test -- help --
# UseGetOpt () {
declare inputOptions
declare -r E_OPTERR=85
declare -r ScriptName=${0##*/}
declare -r ShortOpts="adf:hlt"
declare -r LongOpts="aoption,debug,file:,help,log,test"
DoSomething () {
echo "Имя функции '${FUNCNAME}'"
}
while true; do
case "${1}" in
--aoption | -a) # Найденный аргумент.
echo "Option [$1]"
;;
*) # Передано
echo "Опция [$1] Используется ввод [$2]"
shift
;;
esac
DoSomething
;;
820
--log | -l) # Включение журналирования.
echo "Опция [$1] Журналирование включено"
;;
--help | -h)
echo "Опция [$1] Вывод справки"
break
;;
*)
echo "Главная внутренняя ошибка!"
exit 8
;;
esac
echo "Количество аргументов: [$#]"
shift
done
shift
# }
exit
# [Link]
# Вывод всех 256 цветов фона, с помощью управляющих последовательностей ANSI.
# Автор: Chetankumar Phulpagare
# В ABS Guide используется с разрешения.
T1=8
T2=6
T3=36
offset=0
821
done
offset=16
for num1 in {0..5}
do {
for num2 in {0..5}
do {
for num3 in {0..5}
do {
shownum=`echo "$offset + $T2 * ${num3} \
+ $num2 + $T3 * ${num1}" | bc`
echo -en "\E[0;48;5;${shownum}m color ${shownum} \E[0m"
}
done
echo
}
done
}
done
offset=232
for num1 in {0..23}
do {
shownum=`expr $offset + $num1`
echo -en "\E[0;48;5;${shownum}m ${shownum}\E[0m"
}
done
echo
LETTERSPACE=300000 # Микросекунды.
WORDSPACE=980000
# Ясно и медленно, для начинающих. Может быть 5 знаков в минуту?
822
declare -A morse # Ассоциативный массив!
# ======================================= #
morse[a]="точка; тире"
morse[b]="тире; точка; точка; точка"
morse[c]="тире; точка; тире; точка"
morse[d]="тире; точка; точка"
morse[e]="точка"
morse[f]="точка; точка; тире; точка"
morse[g]="тире; тире; точка"
morse[h]="точка; точка; точка; точка"
morse[i]="точка; точка;"
morse[j]="точка; тире; тире; тире"
morse[k]="тире; точка; тире"
morse[l]="точка; тире; точка; точка"
morse[m]="тире; тире"
morse[n]="тире; точка"
morse[o]="тире; тире; тире"
morse[p]="точка; тире; тире; точка"
morse[q]="тире; тире; точка; тире"
morse[r]="точка; тире; точка"
morse[s]="точка; точка; точка"
morse[t]="тире"
morse[u]="точка; точка; тире"
morse[v]="точка; точка; точка; тире"
morse[w]="точка; тире; тире"
morse[x]="тире; точка; точка; тире"
morse[y]="тире; точка; тире; тире"
morse[z]="тире; тире; точка; точка"
morse[0]="тире; тире; тире; тире; тире"
morse[1]="точка; тире; тире; тире; тире"
morse[2]="точка; точка; тире; тире; тире"
morse[3]="точка; точка; точка; тире; тире"
morse[4]="точка; точка; точка; точка; тире"
morse[5]="точка; точка; точка; точка; точка"
morse[6]="тире; точка; точка; точка; точка"
morse[7]="тире; тире; точка; точка; точка"
morse[8]="тире; тире; тире; точка; точка"
morse[9]="тире; тире; тире; тире; точка"
# Следующее должно быть заэкранировано или в кавычках.
morse[?]="точка; точка; тире; тире; точка; точка"
morse[.]="точка; тире; точка; тире; точка; тире"
morse[,]="тире; тире; точка; точка; тире; тире"
morse[/]="тире; точка; точка; тире; точка"
morse[\@]="точка; тире; тире; точка; тире; точка"
# ======================================= #
play_letter ()
{
eval ${morse[$1]} # Проигрываются точки и тире из звуковых файлов.
# Почему здесь необходим 'eval'?
usleep $LETTERSPACE # Пауза между буквами.
}
extract_letters ()
{ # Фрагмент строки поэлементно, по буквам.
local pos=0 # Начинается с левого конца строки.
local len=1 # По одной букве.
strlen=${#1}
823
while [ $pos -lt $strlen ]
do
letter=${1:pos:len}
# ^^^^^^^^^^^^ См. Главу 10.1.
play_letter $letter
echo -n "*" # Отмечается только что проигранная буква.
((pos++))
done
}
no_args ()
{
declare -a usage
usage=( $0 word1 word2 ... )
# int main()
# {
if [ -z "$1" ]
then
no_args
echo; echo; echo "$EXIT_MSG"; echo
exit $E_NOARGS
fi
until [ -z "$1" ]
do
extract_letters $1
shift # К следующему слову.
usleep $WORDSPACE
echo -n " " # Ввод пробелов между словами.
done
824
exit 0
# }
# Упражнения:
# ---------
# 1) Сценарий, в качестве аргументов, должен принимать слова в нижнем или
#+ верхнем регистре. Подсказка: Используйте 'tr'...
# 2) Сценарий должен принимать ввод из текстового файла.
function display_base64_char {
# Преобразование 6-битного числа (от 0 до 63) в его соответствующее значение
#+ Base64, затем вывод результата с указанным размером написанного.
printf "${base64_charset[$1]}"; (( width++ ))
(( width % text_width == 0 )) && printf "\n"
}
function encode_base64 {
# Кодирование трех 8-разрядных шестнадцатеричных кодов в четыре 6-битовых
#+ числа.
# Нам нужны два локальных массива переменных целых чисел:
825
# c8[]: для хранения кодов 8-битных символов для кодирования
# c6[]: для хранения закодированных 6-битных значений
declare -a -i c8 c6
function decode_base64 {
# Декодирование четырех символов base64 в три шестнадцатеричных символа ASCII.
# c8[]: для хранения кодов 8-битных символов
# c6[]: для хранения значений соответствующих 6-битным Base64
declare -a -i c8 c6
position=0
while [ "${current_char}" != "${base64_charset[${position}]}" ]; do
(( position++ ))
done
826
# main ()
else
# Делаем hexdump stdin и переформатируем в 3-байтовые группы.
content=$(cat - | xxd -ps -u | sed -r "s/(\w{6})/\1 /g" |
tr -d "\n")
echo
fi
827
exit 0
# Упражнение:
# Добавьте в этом сценарии проверку ошибок.
# Это ужасно необходимо.
# License: GPL3
# Reldate 06/23/11
# Глобальные переменные #
Enc_suffix="29379" # Вывод зашифрованного текста с указанным 5-значным
#+ суффиксом.
# Он работает, как флаг для расшифровки, f когда
#+ используется для создания паролей добавляет
#+ секретностb.
Default_key="gronsfeldk"
# Сценарий использует ее, если не указан ключ ниже
# ("Keychain").
# Измените два значения частоты, выше, для
#+ дополнительной безопасности.
########Keychain#########
key= ### Помещаем ключ сюда!!!
# 10 символов!
#########################
# Действие
: ()
{ # Шифрование или расшифровка зависит от установки $dflag.
# Почему ": ()" как имя функции? Просто чтобы доказать, что это возможно.
828
local idx keydx mlen off1 shft
local plaintext="$1"
local mlen=${#plaintext}
if [ -n "$dflag" ]
then # Дешифрование!
let "off1 = $(expr index "${alpha1[*]}" ${plaintext:idx:1}) - $shft"
# Сдвиг назад, для расшифровки.
else # Шифрование!
let "off1 = $(expr index "${alpha1[*]}" ${plaintext:idx:1}) + $shft"
# Сдвиг вперед для шифрования.
test $(( $idx % $GROUPLEN)) = 0 && echo -n " " # Группы из 5 букв.
# Закомментируйте строку выше для вывода в виде строки без пробелов,
#+ например, при использовании сценария в качестве генератора паролей.
fi
if [ $off1 -lt 0 ]
then # Отлов отрицательных индексов.
let "off1 += $wraplen"
fi
echo -n "${alpha2[off1]}"
done
if [ -z "$dflag" ]
then
echo " $Enc_suffix"
# echo "$Enc_suffix" # Для генератора паролей.
else
echo
fi
} # Окончание функции шифрования/дешифрования.
# int main () {
if [ ${!#} == "$Enc_suffix" ]
# ^^^^^ Последний аргумент командной строки.
then
dflag=ON
echo -n "+" # "+" это флаг для дешифровки текста, проще ID.
fi
if [ -z "$key" ]
829
then
key="$Default_key" # "gronsfeldk" см.выше.
fi
keylen=${#key}
# Отладка:
# echo "$args"; exit $DEBUG
# ************************************************************** #
# Этот сценарий, с несколькими незначительными изменениями,
#+ может работать как генератор паролей, см. выше.
# Он позволяет легко запомнить пароль, даже само слово «пароль»,
#+ которое шифруется в vrgfotvo29379
#+ достаточно надежный пароль не восприимчивый к словарю.
# Или, вы можете использовать ваше собственное название,
#+ (конечно, которое легко запомнить!).
# Например, Bozo Bozeman будет зашифровано в hfnbttdppkt29379.
# ************************************************************** #
#######################################################################
# Этот сценарий создает числа bingo.
# При нажатии клавиши создается новое число.
# Нажатие на 'q' прекращает сценарий.
# Запущенный сценарий производит не повторяющиеся числа.
# Когда сценарий завершается, он заполняет журнал созданными числами.
#######################################################################
830
declare -a Numbers
Prefix=(B I N G O)
initialize_Numbers ()
{ # Начинается с нуля.
# Если выбрано будет увеличиваться.
local index=0
until [ "$index" -gt $MAX ]
do
Numbers[index]=0
((index++))
done
generate_number ()
{
local number
while [ 1 ]
do
let "number = $(expr $RANDOM % $MAX)"
if [ ${Numbers[number]} -eq 0 ] # Число еще не вызвано.
then
let "Numbers[number]+=1" # Флаг массива.
break # И завершение цикла.
fi # Если вызвано, цикл и создание другого числа.
done
# Упражнение: Перепишите более элегантно, чем цикл until.
return $number
}
print_numbers_called ()
{ # Печать вызываемых цифр в аккуратные колонки в журнале.
# echo ${Numbers[@]}
831
echo -n "$pre$index#$count "
else # Префикс ноль.
echo -n "$pre$pre2$index#$count "
fi
done
}
# main () {
RANDOM=$$ # Источник генератора случайных чисел.
clear
echo "Вызванное число Bingo"; echo
generate_number; new_number=$?
echo $new_number
done
echo; echo
# Закончено ...
print_numbers_called
echo; echo "[#0 = не вызвано . . . #1 = вызвано]"
echo
exit 0
# }
832
# License: Use in any form, for any purpose.
# Revision: $ID$
#
# Для размещения отредактировано M.C.
# (автором "Advanced Bash Scripting Guide")
# Исправление и обновления (04/08) Cliff Bamford.
###-------------------------------------------###
### Для перемещения по страницам ###
###+ Направьте вывод этого сценария в 'more'. ###
### ###
### Можно так же перенаправить вывод ###
###+ в файл, для изучения. ###
###-------------------------------------------###
833
# Переменная может определяться как массив Bash, явно или косвенно,
#+ синтаксисом оператора присваивания.
# Явно:
declare -a ArrayVar
# Ссылки Glob-Pattern
echo $* # Все параметры сценария или функции
echo ${*} # То же самое
# Ссылки Все-элементы-из
echo $@ # То же, что выше
echo ${@} # То же, что выше
834
# Указание только имени массива Bash, это ссылка на индекс
#+ нулевого элемента, а НЕ на ПЕРВЫЙ ОПРЕДЕЛЕННЫЙ,
#+ и не на ПЕРВЫЙ С СОДЕРЖИМЫМ элемент.
echo "========================================================="
ArrayVar[0]='zero' # 0 обычный
835
ArrayVar[1]=one # 1 буквальный без кавычек
ArrayVar[2]='two' # 2 обычный
ArrayVar[3]='three' # 3 обычный
ArrayVar[4]='I am four' # 4 обычный с пробелами
ArrayVar[5]='five' # 5 обычный
unset ArrayVar[6] # 6 не определенный
ArrayValue[7]='seven' # 7 обычный
ArrayValue[8]='' # 8 определен, но пустой
ArrayValue[9]='nine' # 9 обычный
echo
echo '---Case0: Без двойных кавычек, по умолчанию IFS, пробел,табуляция,новая
строка ---'
IFS=$'\x20'$'\x09'$'\x0A' # Точно в этом порядке.
echo 'Здесь: printf %q {${ArrayVar[*]}'
printf %q ${ArrayVar[*]}
echo
echo 'Здесь: printf %q {${ArrayVar[@]}'
printf %q ${ArrayVar[@]}
echo
echo 'Здесь: echo ${ArrayVar[*]}'
echo ${ArrayVar[@]}
echo 'Здесь: echo {${ArrayVar[@]}'
echo ${ArrayVar[@]}
echo
echo '---Случай1: С двойными кавычками — по умолчанию IFS это пробел — табуляция
- новая строка ---'
IFS=$'\x20'$'\x09'$'\x0A' # Те же три байта,
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Случай2: С двойными кавычками - IFS является q'
IFS='q'
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
836
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Случай3: С двойными кавычками - IFS является ^'
IFS='^'
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Случай4: С двойными кавычками - IFS является ^ следующим за
пробелом,табуляцией,новой строкой'
IFS=$'^'$'\x20'$'\x09'$'\x0A' # ^ + пробел табуляция новая строка
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Случай5: С двойными кавычками - IFS установлен и пустой '
IFS=''
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Случай6: С двойными кавычками - IFS не установлен'
unset IFS
echo 'Здесь: printf %q "{${ArrayVar[*]}"'
printf %q "${ArrayVar[*]}"
echo
echo 'Здесь: printf %q "{${ArrayVar[@]}"'
printf %q "${ArrayVar[@]}"
echo
837
echo 'Здесь: echo "${ArrayVar[*]}"'
echo "${ArrayVar[@]}"
echo 'Здесь: echo "{${ArrayVar[@]}"'
echo "${ArrayVar[@]}"
echo
echo '---Окончание---'
echo "========================================================="; echo
# Напомним:
# Параметры похожи на массивы и имеют аналогичное поведение.
###
# Приведенные выше примеры свидетельствуют о возможности вариаций.
# Чтобы сохранить форму разреженного массива, требуется
#+ написать дополнительный сценарий.
###
# Исходный код Bash имеет обычный для вывода
#+ формат присваивания массива [Индекс]=значение
# Начиная с версии 2.05b эта процедура не используется,
#+ но это может измениться в будущих версиях.
838
# Размер массива, измеренный в определенных элементах,
#+ включая элементы содержащие null.
echo
echo 'Количество определенных элементов: '"${#ArrayVar[*]}"' элементов.'
###
$(_simple) # Выдается сообщение об ошибке:
# строка 436: SimpleFunc: command not found
# ---------------------------------------
echo
###
839
###
_print() {
echo -n 'printf %q '$@
}
# Действие переменных
# ------------------
echo
echo '- - Действие переменных - -'
# Переменная может представлять знаковое целое число, строку или массив.
# Строка может использоваться как имя функции с необязательными аргументами.
# Задержка замены
# -------------------
echo
echo '- - Задержка замены - -'
funcVar="$(_print '$VarSomething')" # Замены нет, единственное Слово Bash.
eval $funcVar # ЗДЕСЬ заменено $VarSomething.
echo
840
VarSomething='NewThing'
eval $funcVar # ЗДЕСЬ заменено $VarSomething.
echo
# ОБЗОР:
# ------
echo
echo '- - Проверка (но не изменение) - -'
echo '- пустая ссылка -'
echo -n ${VarNull-'НеОбъявлена'}' ' # НеОбъявлена
echo ${VarNull} # Только новая строка
echo -n ${VarNull:-'НеОбъявлена'}' ' # НеОбъявлена
echo ${VarNull} # Только новая строка
841
# Искусство времени в ASCII
# Статус Y==yes, N==no
# - :-
# Не установлено Y Y ${# ... } == 0
# Пусто N Y ${# ... } == 0
# содержимое N N ${# ... } > 0
echo
echo '- - Проверки и изменения - -'
echo '- Присвоить, если ссылка null -'
echo -n ${VarNull='NotSet'}' ' # NotSet NotSet
echo ${VarNull}
unset VarNull
842
echo '- Никаких изменений, если уже есть содержимое -'
echo ${VarSomething='Content'} # Буквально
echo ${VarSomething:='Content'} # Буквально
IFS=$'\x20'$'\x09'$'\x0A'
printf %q "${ArraySparse[*]}"
echo
843
#+ подходящий код пары чтение/запись.
###
# Упражнение: Очистим это.
unset ArraySparse
echo
echo '- - Условная альтернатива (Но не замена)- -'
echo '- Без альтернативы если ссылка null -'
echo -n ${VarNull+'NotSet'}' '
echo ${VarNull}
unset VarNull
# Буквальная альтернатива
echo -n ${VarSomething+'Content'}' ' # Буквальное содержимое
echo ${VarSomething}
# Вызванная функция
echo -n ${VarSomething:+ $(_simple) }' ' # Буквальная SimpleFunc
echo ${VarSomething}
echo
declare -i t
_incT() {
t=$t+1
}
# Примечание:
# Это тот же тест, используемый в фрагменте кода
#+ листинга разреженного массива.
844
${VarEmpty+ _incT } # Исполняемая.
echo $t' Null содержимое'
# ${name?err_msg} ${name:?err_msg}
# Следуют тем же правилам, но после - всегда выход,
#+ если действие указано после знака вопроса.
# Действие после вопросительного знака может быть
#+ буквальным или результатом действия.
###
# ${name?} ${name:?} является только проверкой, возвращать может только
#+ проверенное.
# Операции с элементами
# ------------------
echo
echo '- - Выбор содержимого элемента - -'
echo
echo '- Все после -'
echo ${VarSomething:1} # все не-null символы после [0]
echo ${ArrayVar[@]:1} # все элементы с содержимым после [0]
echo ${@:2} # все параметры с содержимым после [1]
echo
echo '- Диапазон после -'
echo ${VarSomething:4:3} # Диапазон после
# Три символа после
# символа[3]
845
# В версиях Bash 2.04, 2.05a и 2.05b,
#+ не обрабатываются разреженные массивы, как и ожидалось,
#+ использующие такую запись.
#
# Нынешний майнтейнер Bash, Chet Ramey, исправил это.
echo
echo ' - - Строка Жертва - -'$stringZ'- - '
echo ' - - Строка Жертва - -'${arrayZ[@]}'- - '
echo ' - - Разреженный массив - -'${sparseZ[@]}'- - '
echo ' - [0]==null ссылка, [2]==null ссылка, [4]==null содержимое - '
echo ' - [1]=abcabc [3]=ABCABC [5]=123123 - '
echo ' - подсчет элементов не-null-ссылок: '${#sparseZ[@]}''
echo
echo '- - Удаление префикса (слева) суб-элемента - -'
echo '- - Соответствие Glob-Pattern должно включать первый символ. - -'
echo '- - Глобальный шаблон может быть буквальным или результатом функции. - -'
echo
echo
echo '- Длинный префикс (слева) -'
echo ${stringZ##1*3} # Без изменений (не префикс)
echo ${stringZ##a*C} # abc
echo ${arrayZ[@]##a*c} # ABCABC 123123 ABCABC
echo
echo '- - Удаление суффикса содержимого элемента (справа) - -'
echo '- - Последний символ должен соответствовать Glob-Pattern. - -'
echo '- - Глобальный шаблон может быть буквальным или результатом функции. - -'
echo
846
echo '- Короткий суффикс (справа) -'
echo ${stringZ%1*3} # Без изменений (не суффикс).
echo ${stringZ%$(_abc)} # abcABC123ABC
echo ${arrayZ[@]%abc} # Применяется для каждого элемента.
echo
echo '- Длинный суффикс (справа) -'
echo ${stringZ%%1*3} # Без изменений (не суффикс)
echo ${stringZ%%b*c} # a
echo ${arrayZ[@]%%b*c} # a ABCABC 123123 ABCABC a
echo
echo '- - Перемещение содержимого элемента - -'
echo '- - Содержимое элемента в любом месте в строке. - -'
echo '- - Первой спецификацией является Glob-Pattern - -'
echo '- - Glob-Pattern может быть буквенным или Glob-Pattern результата функции.
- -'
echo '- - Второй спецификацией может быть буква или результат функции. - -'
echo '- - Вторая спецификация может быть не указана. Звучит как'
echo ' : Перемещение-в-никуда (Удаление) - -'
echo
echo
echo '- Удаление первого вхождения -'
echo ${stringZ/$(_123)/}
echo ${stringZ/ABC/}
echo ${arrayZ[@]/ABC/}
echo ${sparseZ[@]/ABC/}
echo
echo '- Замена всех вхождений -'
847
echo ${stringZ//[b2]/X} # X-замена b и 2
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
echo ${arrayZ[@]//abc/xyz} # Применяется к каждому элементу.
echo ${sparseZ[@]//abc/xyz} # Работает, как ожидалось.
echo
echo '- Удаление всех вхождений -'
echo ${stringZ//[b2]/}
echo ${stringZ//abc/}
echo ${arrayZ[@]//abc/}
echo ${sparseZ[@]//abc/}
echo
echo '- - Замена префикса (левого) содержимого элемента - -'
echo '- - Совпадение должно содержать первый символ. - -'
echo
echo
echo '- Удаление префикса (левого) содержимого -'
echo ${stringZ/#[b2]/}
echo ${stringZ/#$(_abc)/}
echo ${arrayZ[@]/#abc/}
echo ${sparseZ[@]/#abc/}
echo
echo '- - Замена суффикса (правого) содержимого элемента - -'
echo '- - Совпадение должно содержать последний символ. - -'
echo
echo
echo '- Удаление содержимого суффикса -'
echo ${stringZ/%[b2]/}
echo ${stringZ/%$(_abc)/}
echo ${arrayZ[@]/%abc/}
echo ${sparseZ[@]/%abc/}
echo
echo '- - Особые случаи пустого Glob-Pattern - -'
echo
echo
echo '- Все суффиксы -'
848
# пустой шаблон содержимого строки означающий 'суффикс'
echo ${stringZ/%/NEW} # abcABC123ABCabcNEW
echo ${arrayZ[@]/%/NEW} # Применяется к каждому элементу.
echo ${sparseZ[@]/%/NEW} # Применяется так же к null-содержимому.
# Это кажется разумно.
echo
echo '- - Особый случай Для-каждого Glob-Pattern - -'
echo '- - - - Это красивая мечта - - - -'
echo
_GenFunc() {
echo -n ${0} # Только иллюстрация.
# На самом деле, это было бы произвольное вычисление.
}
exit 0
count=50000
echo "Проверки вычислений"
echo "Вычисления с помощью \$(( ))"
time for (( i=0; i< $count; i++))
do
result=$(( $i%2 ))
done
849
time for (( i=0; i< $count; i++))
do
let result=$i%2
done
echo
echo "Проверяемые условия проверки"
exit $?
850
declare simple # При необходимости можно заменить на 20000.
declare -a array1
declare -A array2
declare -a array3
declare -A array4
echo "---"
echo "---"
echo "---"
echo "---"
echo "---"
echo "---"
echo "---"
851
done
echo "---"
echo "---"
echo "---"
echo "---"
exit $?
852
Приложение B. Типы ссылок
Следующие типы ссылок предоставляют полезную информацию о некоторых понятиях
сценариев.
Таблица B-1. Специальные переменные оболочки
Переменная Значение
$0 Имя сценария
$1 Позиционный параметр #1
$2 - $9 Позиционные параметры #2 - #9
${10} Позиционный параметр #10
$# Количество позиционных параметров
"$*" Все позиционные параметры (как одно слово) *
853
Переменная Значение
"$@" Все позиционные параметры (как разделенные строки)
${#*} Количество позиционных параметров
${#@} Количество позиционных параметров
$? Возвращаемое значение
$$ ID (PID) процесса сценария
$- Флаги передаваемые в сценарий (с помощью set)
$_ Последний аргумент предыдущей команды
$! ID (PID) процесса последнего в фоне запущенного задания
* Должны быть в кавычках, в противном случае по умолчанию будет $@.
Арифметическое Строковое
сравнение сравнение
-eq Равно = Равно
== Равно
-ne Не равно != Не равно
-lt Меньше чем \< Меньше чем (ASCII)
*
-le Меньше чем или равно
-gt Больше чем \> Больше чем (ASCII) *
-ge Больше чем или равно
-z Пустая строка
-n Не пустая строка
854
Оператор Проверяется --- Оператор Проверяется
-L Файл это символьная ссылка -x Файл имеет права на исполнение
-b Файл это блочное устройство
-c Файл это символьное -g Установленный флаг sgid
устройство
-p Файл это канал -u Установленный флаг suid
-S Файл это сокет -k Установленный "sticky bit"
-t Файл связан с терминалом
! НЕ (инвертирование смысла
проверок выше)
* Бинарный оператор (требует двух операндов ).
Таблица B-4. Параметры подстановки и расширения
Выражение Значение
${var} Значение переменной var (то же, что и $var)
855
Выражение Значение
${!varprefix@} Сравнение всех ранее объявленных переменных, начиная с
varprefix
* Если var присвоена, вычисляет выражение как $var без побочных эффектов.
Выражение Значение
${#string} Длина $string
856
Выражение Значение
начала $string
expr index "$string" $substring Числовая позиция в $string первого
символа $substring* который
соответствует [0 если не соответствует,
первый символ считается как позиция 1]
expr substr $string $position Извлекает $length символов из $string
$length начиная с $position [0 если не
соответствует, первый символ считается как
позиция 1]
expr match "$string" '\ Извлечение $substring*, поиск от начала
($substring\)' $string
expr "$string" : '\($substring\)' Извлечение $substring* , поиск от начала
$string
expr match "$string" '.*\ Извлечение $substring*, поиск от конца
($substring\)' $string
expr "$string" : '.*\ Извлечение $substring*, поиск от конца
($substring\)' $string
* Где $substring это Регулярное выражение.
Выражение Интерпретация
Скобки
if [ УСЛОВИЕ ] Конструкция проверки
if [[ УСЛОВИЕ ]] Расширяемая конструкция проверки
Array[1]=элемент1 Инициализация массива
[a-z] Диапазон символов в регулярном
выражении
Фигурные скобки
${переменная} Подстановка параметров
${!переменная} Косвенная ссылка на переменную
{ команда1; команда2; ... Блок кода
командаN; }
{строка1,строка2,строка3,...} Расширение в скобках
{a..z} Расширяемое расширение скобок
{} Замена текста, после find и xargs
Круглые скобки
( команда1; команда2 ) Группа команд выполняемая в
подоболочке
Array=(элемент1 элемент2 элемент3) Инициализация массива
result=$(КОМАНДА) Подстановка команды, новый стиль
857
Выражение Интерпретация
>(КОМАНДА) Процесс замены
<(КОМАНДА) Процесс замены
Кавычки
"$переменная" «Мягкие» кавычки
'строка' «Жесткие» кавычки
Обратные кавычки
result=`КОМАНДА` Подстановка команды, классический
стиль
858
Это очень краткое введение в утилиты текстовой обработки sed и awk. Мы рассмотрим
только несколько основных команд, но их будет достаточно для понимания простых
конструкций sed и awk внутри сценариев оболочки.
sed: не интерактивный редактор текстовых файлов
awk: поле-ориентированный язык обработки шаблонов с синтаксисом в стиле Си
При всех их различиях, эти две утилиты немного похожи синтаксисом вызова,
использованием регулярных выражений, чтением ввода из stdin по умолчанию и выводом
результата в stdout. Это прекрасные инструменты UNIX, хорошо работающие вместе.
Выходные данные из одной могут быть переданы другой, а их комбинирование придает
сценариям мощность Perl.
Одним из важных различий между утилитами является то, что сценарии могут
легко передавать аргументы в sed, что более сложно для awk (см. Пример 36-5
и Пример 28-2).
C.1. Sed
Sed это не интерактивный [1] потоковый редактор (stream editor). Он получает текстовый
ввод, из stdin или из файла, выполняет некоторые операции с заданной введенной строкой,
одной строкой за раз, а затем выводит результат в stdout или в файл. В рамках сценария
оболочки, sed, обычно, является одним из нескольких компонентов конвейера.
Sed определяет, какими строками ввода, переданными ему, он будет оперировать из
диапазона адресов. [2] Задается этот диапазон адресов номером строки или шаблоном
соответствия. Например, сигнал sed 3d удаляет строку 3 ввода, а /Windows/d говорит sed,
что нужно удалить каждую строку ввода, содержащую соответствие "Windows".
Из всех операций утилиты sed мы сосредоточимся, главным образом, на трех наиболее часто
используемых. Это printing - вывод (в stdout), deletion - удаление, и substitution —
подстановка или замена.
Таблица C-1. Основные операторы sed
859
Оператор Название Действие
указанном address-range (эквивалент tr)
[address] i шаблон insert Вставка шаблона в указанный адрес в файле
Filename Filename. Обычно используется с опцией -i
in-place.
g global Операция с любым шаблоном каждого
соответствия в строке ввода
Sed использует опцию -e, для указания, что следующая строка является
инструкцией или набором инструкций. Если существует только одна
инструкция, содержащаяся в строке, то эта опция может быть опущена.
860
Нотация Действие
8d Удаление 8-й строки ввода.
/^$/d Удаление всех пустых строк.
1,/^$/d Удаление от начала ввода до первой
пустой строки, включая первую пустую
строку.
/Jones/p Вывод на экран только строк содержащих
"Jones" (с опцией -n).
s/Windows/Linux/ Замена найденного первого случая
"Windows" в каждой строке ввода на
"Linux".
s/BSOD/stability/g Замена найденного первого случая
"BSOD" в каждой строке ввода на
"stability"
s/ *$// Удаление всех пробелов в конце каждой
строки.
s/00*/0/g Сжатие всех последовательностей нулей
подряд в один ноль.
echo "Working on it." | sed -e '1i Вывод на экран "How far are you along?"
How far are you along?' первой строкой, "Working on it" - второй.
5i 'Linux is great.' [Link] Вставка 'Linux is great.' в строку 5 файла
[Link].
/GUI/d Удаление всех строк содержащих "GUI".
s/GUI//g Удаление всех случаев "GUI", оставляя
остальную часть каждой строки не
измененной.
Замена строки нулевой длины на другую эквивалентно удалению строки в строке ввода. Она
оставляет оставшуюся часть строки нетронутой. Применение s/GUI// к строке
The most important parts of any application are its GUI and sound effects
Даст результат
The most important parts of any application are its and sound effects
Обратный слэш принуждает sed к замене команды, для продолжения на следующей строке.
Это эффект использования перевода строки в конце первой строки, как замены строки.
s/^ */\
/g
Эта подстановка заменяет пробел в начале новой строки переводом строки. Конечный
результат - замена отступа абзаца пустой строкой между абзацами.
861
Диапазон адресов, следующих одной или более операций, может потребовать открытой и
закрытой фигурных скобок, в соответствующих строках.
/[0-9A-Za-z]/,/^$/{
/^$/d
}
Удаляется только первая, из каждого последовательного ряда пустых строк. Это может быть
полезно для единственного пробела в текстовом файле, но сохраняются пустые строки между
абзацами.
862
19. Пример A-29
20. Пример A-31
21. Пример A-24
22. Пример A-43
23. Пример A-55
Для дополнительного изучения sed обратитесь к соответствующей ссылке в Библиографии.
Примечания
[1] Sed выполняется без вмешательства пользователя.
C.2. Awk
Awk[1] — полнофункциональный язык обработки текста с синтаксисом, напоминающим язык
Cи. Хотя он имеет обширный набор операторов и возможностей, мы здесь охватим лишь
немногие из них - наиболее полезные в сценариях оболочки.
Awk делит каждую введенную строку, переданную ей, на поля. По умолчанию, поле
представляет собой строку символов, разделенных пробелами, хотя это можно изменить.
Awk анализирует и обрабатывает каждое поле в отдельности. Что делает его идеальным для
работы со структурированными текстовыми файлами - особенно таблицами - данными,
организованными соответствующими частями, такими как строки и столбцы.
Строгие кавычки и фигурные скобки вкладывают блоки кода awk в сценарий.
# $1 это поле #1, $2 это поле #2, и т.д..
863
awk '{print $0}' $filename
# Выводит весь файл!
# Такой же эффект как: cat $filename . . . или . . . sed '' $filename
Мы только что рассмотрели команду awk print в действии. Другой особенностью awk, с
которой мы столкнемся, являются переменные. Awk обрабатывает переменные подобно
сценариям оболочки, хотя еще более гибко.
{ total += ${column_number} }
В соответствии END, существует и BEGIN, для блока кода, который необходимо выполнить
перед началом обработки входных данных awk.
В следующем примере демонстрируется, как awk может добавить в сценарий оболочки
средства синтаксического анализа текста.
Пример C-1. Подсчет вхождений буквы
#! /bin/sh
# [Link]: Подсчет вхождений буквы в текстовом файле.
#
# Сценарий nyal [nyal@[Link]].
# Используется в ABS Guide с разрешения.
# Комментировано и переформатировано автором ABS Guide.
# Version 1.1: Изменение для работы с gawk 3.1.3.
# (Будет по-прежнему работать с более ранними версиями.)
INIT_TAB_AWK=""
# Параметр инициализации сценария awk.
count_case=0
FILE_PARSE=$1
E_PARAMERR=85
usage()
{
echo "Usage: [Link] файл буквы" 2>&1
# Например: ./[Link] [Link] a b c
exit $E_PARAMERR # Слишком мало аргументов, передаваемых сценарию.
}
if [ ! -f "$1" ] ; then
echo "$1: Нет такого файла." 2>&1
usage # Вывод сообщения пользователю и выход.
fi
864
if [ -z "$2" ] ; then
echo "$2: Не указаны буквы." 2>&1
usage
fi
# DEBUG:
# echo $INIT_TAB_AWK;
cat $FILE_PARSE |
# Туннелирование целевого файла следующего сценария в awk.
# ---------------------------------------------------------------------
# Ранняя версия сценария:
# awk -v tab_search=0 -v final_tab=0 -v tab=0 -v \
# nb_letter=0 -v chara=0 -v chara2=0 \
awk \
"BEGIN { $INIT_TAB_AWK } \
{ split(\$0, tab, \"\"); \
for (chara in tab) \
{ for (chara2 in tab_search) \
{ if (tab_search[chara2] == tab[chara]) { final_tab[chara2]++ } } } } \
END { for (chara in final_tab) \
{ print tab_search[chara] \" => \" final_tab[chara] } }"
# ---------------------------------------------------------------------
# Все сложное - просто ...
#+ циклы for, проверки if и пара специальных функций.
exit $?
865
8. Пример 29-4
9. Пример 11-3
10. Пример 16-61
11. Пример 9-16
12. Пример 16-4
13. Пример 10-6
14. Пример 36-19
15. Пример 11-9
16. 16. Пример 36-4
17. 17. Пример 16-53
18. 18. Пример Т-3
Мы рассмотрели здесь awk, парни, но можно узнать много больше. Смотрите
соответствующие ссылки в Библиографии.
Примечания
[1] Название происходит от инициалов ее авторов, Aho, Weinberg и Kernighan.
866
Приложение D. Анализ и управление
путями имен (Pathnames)
Emmanual Rouat внес следующий пример синтаксического анализа и преобразования файлов
и, в частности, путей. В нем используется функциональность sed.
#!/usr/bin/env bash
#-----------------------------------------------------------
# Управление PATH, LD_LIBRARY_PATH, переменными MANPATH...
# Emmanuel Rouat <no-email>
# (Вдохновленный документацией bash «pathfuncs» и найденными обсуждениями
# переполнение стека:
# [Link]
# [Link] )
# Последнее изменение: Sat Sep 22 12:01:55 CEST 2012
#
# Следующие функции правильно обрабатывают пробелы.
# Я предполагаю, что эти функции находятся в .bash_profile, а не в
# .bashrc.
#
# Модульный аспект этих функций должен сделать их
867
# легко расширяемыми для обработки замены PATH, вместо удаления и т.д. ....
#
# См. [Link]
# (пункт 43) для объяснения удаления «дублированной записи»
# (это красивый трюк!)
#-----------------------------------------------------------
# Некоторые тесты:
echo
MYPATH="/bin:/usr/bin/:/bin://bin/"
p_append "/project//my project/bin" MYPATH
echo "Добавление '/project//my project/bin' в '/bin:/usr/bin/:/bin://bin/'"
echo "(результат должен быть: /bin:/usr/bin:/project/my project/bin)"
echo $MYPATH
echo
MYOTHERPATH="/bin:/usr/bin/:/bin:/project//my project/bin"
p_prepend "/project//my project/bin" MYOTHERPATH
echo "Вставка '/project//my project/bin' \
в '/bin:/usr/bin/:/bin:/project//my project/bin/'"
echo "(результат должен быть: /project/my project/bin:/bin:/usr/bin)"
echo $MYOTHERPATH
echo
p_prepend "/project//my project/bin" FOOPATH # FOOPATH не существует.
868
echo "Вставка '/project//my project/bin' в не объявленную переменную"
echo "(результат должен быть: /project/my project/bin)"
echo $FOOPATH
echo
BARPATH="/a:/b/://b c://a:/my local pub"
p_clean BARPATH
echo "Очистка BARPATH='/a:/b/://b c://a:/my local pub'"
echo "(результат должен быть: /a:/b:/b c:/my local pub)"
echo $BARPATH
***
David Wheeler любезно позволил мне использовать его поучительные примеры.
Делаем правильно: кратко
David Wheeler
[Link]
Итак, как правильно обрабатывать имена в оболочке? Вот краткий обзор, для
нетерпеливых, которые «просто хотят ответ» о том, как делать это правильно.
Короче говоря: используем двойные кавычки "$variable" вместо $variable,
устанавливаем IFS вместо новой строки и табуляции, начало (префиксы) всех
символов подстановок/файлов,которые работают правильно, не могут начинаться с
"-" при расширении и использовании одного из нескольких шаблонов. Вот некоторые
из этих шаблонов, работающих правильно :
IFS="$(printf '\n\t')"
# Удаление ПУСТОГО МЕСТА, т.к. файловые имена с пробелами хорошо работают.
869
done
mkfifo mypipe
870
Число кода Смысл Пример Комментарии
выхода
использование ction() команда, или проблемы с правами (и
встроенных команд {} код возврата diff ошибка сравнения
оболочки (согласно двоичного файла).
документации Bash)
126 Вызываемая команда не /dev/null Проблема с правами или команда не
может быть выполнена является исполняемой
127 "command not found" illegal_c Возможные проблемы с $PATH или
ommand типом
128 Недопустимый аргумент exit exit принимает только целые аргументы
для exit 3.14159 в диапазоне 0 - 255 (см. первую сноску)
128+n Неустранимая ошибка kill -9 $? возвращает 137 (128 + 9)
сигнала "n" $PPID of
script
130 Сценарий завершается Ctl-C Control-C это сигнал критической
нажатием Control-C ошибки 2, (130 = 128 + 2, см. выше)
255* Статус выхода вне exit -1 exit принимает только целые аргументы
диапазона в диапазоне 0 - 255
Согласно приведенной выше таблице, коды выхода 1-2, 126-165 и 255 [1] имеют особое
значение и поэтому пользователям следует избегать указывать эти параметры выхода.
Завершение сценария выходом exit 127, конечно, приводет к путанице (это код ошибки
'команда не найдена' или определенная пользователем?). Тем не менее, многие сценарии
используют exit 1 в качестве общего решения-на-ошибки. Так код выхода 1 означает любые
возможные ошибки, что не особенно полезно при отладке.
Предпринимались попытки систематизировать номера состояния выхода (см.
/usr/include/sysexits.h), но это предназначалось для программистов Cи и C++.
Аналогичный стандарт при написании сценариев может быть целесообразен. Автор этого
документа предлагает ограничить определенные пользователем коды выхода в диапазоне 64 -
113 (в дополнение к 0, при успешном выходе), для соответствия стандарту Си/C++. Это
выделит 50 действительных кодов и сделает устранение неполадок в сценариях более
простым. [2] Все пользовательские коды выхода, в прилагаемых примерах в этом документе,
соответствуют этому стандарту, за исключением случаев, когда существуют
переопределяющие обстоятельства, как в Примере 9-2.
Примечания
[1] Выход за диапазон значений выхода может привести к неожиданным кодам выхода.
Значение выхода больше, чем 255, возвращает код выхода по модулю 256. Например,
871
exit 3809 дает код выхода 225 (3809% 256 =225).
872
Существуют stdin, stdout и stderr, связанные с каждой командой. ls 2>&1 означает
временную связь stderr команды ls с тем же «ресурсом», что и stdout оболочки.
Например, при запуске xterm, она сначала инициализирует себя. Перед запуском оболочки
пользователя, xterm открывает терминальное устройство (/dev/pts/<n> или что-то
подобное) три раза.
В этот момент Bash наследует эти три файловых дескриптора, и любая команда (дочерний
процесс), запущенная Bash, в свою очередь наследует их, за исключением тех случаев, когда
вы перенаправляете команду. Перенаправление означает переназначение одного из
дескрипторов файла в другой файл (или канал, или что-то доступное). Файловые
дескрипторы могут быть переназначены локально (для команды, группы команд,
подоболочки, циклов while, if, case или for ...), или глобально, на оставшуюся часть
оболочки (с помощью команды exec).
873
lsof 426 root 2w FIFO 0,0 7520 pipe
exec 3>&1
(
(
(
while read a; do echo "FIFO2: $a"; done < /tmp/fifo2 | tee /dev/stderr \
| tee /dev/fd/4 | tee /dev/fd/5 | tee /dev/fd/6 >&7 & exec 3> /tmp/fifo2
) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&-
) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&-
) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&-
rm -f /tmp/fifo1 /tmp/fifo2
# Удачи!
exit 0
874
Многие исполняемые файлы, двоичные файлы или файлы сценариев, принимают параметры
для изменения их поведения во время выполнения. Например: ввод из командной строки
command -o вызовет command с опцией o.
Вывод файла
• -q
--quiet
• -r
-R
875
--recursive
• -z
--compress
876
• -r
--restricted
877
Приложение H. Важные файлы
файлы загрузки
Эти файлы содержат алиасы и переменные среды делающие доступным запущенный
Bash, как пользовательскую оболочку, и для всех сценариев Bash, вызванных после
инициализации системы.
/etc/profile
/etc/bashrc
$HOME/.bash_profile
$HOME/.bashrc
logout файла
$HOME/.bash_logout
Файлы данных
/etc/passwd
878
/etc/sysconfig/hwconf
Примечания
[1] Не применимо к csh, tcsh, и другим оболочкам не относящимся или не происходящим
из классической оболочки Bourne (sh).
[2] В более старых версиях UNIX пароли хранятся в /etc/passwd, это объясняет и
название файла.
879
Приложение I. Важнейшие системные
директории
Системные администраторы и те, кто пишет административные сценарии должны быть
хорошо знакомы со следующими системными директориями.
• /bin
• /etc/rc.d
880
Директория процессов. Содержит информацию и статистические данные о
запущенных процессах и параметрах ядра. См. Глава 29.
• /sys
• /media
Почта пользователя.
• /lib
881
Примечания
[1] Некоторые ранние системы UNIX имели быстрый диск малого объема (содержащий /,
корневой раздел), и второй диск, который был больше, но медленнее (содержащий /usr и
другие разделы). Наиболее часто используемые программы и утилиты находились на
небольшом, но быстром диске в /bin, а остальные на медленном диске в /usr/bin.
882
Приложение J. Введение в Программное
Завершение
Программное завершение в Bash позволяет вводить с клавиатуры части команды, а затем
нажав на клавишу [Tab] автоматически завершать последовательности команды. [1] Если
возможны несколько завершений, то [Tab] перечисляет их все. Давайте посмотрим, как она
работает.
bash$ xtra[Tab]
xtraceroute xtrapin xtrapproto
[Link] xtrapinfo xtrapreset
xtrapchar xtrapout xtrapstats
bash$ xtrac[Tab]
xtraceroute [Link]
bash$ xtraceroute.r[Tab]
[Link]
883
bash$ ./sample[Tab][Tab]
sample_command
[Link] [Link] [Link]
case "$cur" in
-*)
COMPREPLY=( $( compgen -W '-a -d -f -l -t -h --aoption --debug \
--file --log --test --help --' -- $cur ) );;
# Создает соответствующие завершения и загружает их в массив $COMPREPLY.
# xx) можно еще добавить варианты.
# yy)
# zz)
esac
return 0
}
884
bash$ ./[Link] --[Tab]
-- --aoption --debug --file --help --log --test
Примечания
[1] Работает только из командной строки, а не в сценарии.
885
Приложение K. Локализация
Локализация является не документированной особенностью Bash.
Локализованный сценарий оболочки выводит текст на языке, определенном в локали
системы. Пользователь Linux в Берлине, Германия, хотел бы получить вывод сценария на
немецком, в то время как его двоюродный брат в Берлине, штат Мэриленд, получить вывод
того же сценария на английском языке.
При создании локализованного сценария используйте следующий шаблон, выводящий все
сообщения на языке пользователя (сообщения об ошибках, подсказки и т.д.).
#!/bin/bash
# [Link]
# Сценарий Stéphane Chazelas,
#+ изменения Bruno Haible, исправление ошибок Alfredo Pironti.
.[Link]
E_CDERROR=65
error()
{
printf "$@" >&2
exit $E_CDERROR
886
}
# ------------------------------------------------------------------
# Alfredo Pironti комментирует:
# Символы экранирования:
#
# Для локализации предложения как
# echo -e "Hello\tworld!"
#+ вы должны использовать
# echo -e "`gettext \"Hello\\tworld\"`"
# "Два символа экранирования" нужны перед `t', потому что
#+ 'gettext' будет искать строку типа: 'Hello\tworld'
# Потому, что gettext будет читать буквально `\')
#+ и выведет строку типа "Bonjour\tmonde",
#+ т.к. команда 'echo' выведет сообщение на экран правильно.
#
# Можно не использовать
# echo "`gettext -e \"Hello\tworld\"`"
#+из-за ошибки xgettext, объясненной выше.
887
#
# Одним из решений может быть
# echo "`gettext -- \"-h display help and exit\"`"
# Здесь 'gettext' будет работать,
#+ но 'xgettext' будет извлекать "--", как указано выше.
#
# Обойти можно используя локализованную строку
# echo -e "`gettext \"\\0-h display help and exit\"`"
# Мы добавили \0 (NULL) в начало предложения.
# Здесь 'gettext' работает правильно, как и 'xgettext.'
# Кроме того, символ NULL не изменяет поведение команды 'echo'.
# ------------------------------------------------------------------
В этом листинге указан весь локализованный текст. (Опция -D перечисляет строки в двойных
кавычках с префиксом $, без выполнения сценария.)
bash$ bash --dump-po-strings [Link]
#: a:6
msgid "Can't cd to %s."
msgstr ""
#: a:7
msgid "Enter the value: "
msgstr ""
Этот код оболочки в этом случае не специфичен больше для Баш; он работает
так же, как в Bash 1.x , так и в других реализациях /bin/sh.
Теперь, создадим файл [Link] для каждого языка, на который сценарий будет
переводить, указав msgstr. Alfredo Pironti предлагает следующий пример:
888
[Link]:
#: a:6
msgid "Can't cd to $var."
msgstr "Impossible de se positionner dans le repertoire $var."
#: a:7
msgid "Enter the value: "
msgstr "Entrez la valeur : "
#!/bin/bash
# [Link]
E_CDERROR=65
error() {
local format=$1
shift
printf "$(gettext -s "$format")" "$@" >&2
exit $E_CDERROR
}
cd $var || error "Can't cd to %s." "$var"
read -p "$(gettext -s "Enter the value: ")" var
# ...
889
Приложение L. История команд
Оболочка Bash предоставляет средства командной строки для редактирования и управления
историей команд пользователя. Это прежде всего удобство, средство сокращения нажатий
клавиш.
История команд Bash:
1. history
2. fc
bash$ history
1 mount /mnt/cdrom
2 cd /mnt/cdrom
3 ls
...
890
Внутренние переменные, связанные с историей команд Bash:
1. $HISTCMD
2. $HISTCONTROL
3. $HISTIGNORE
4. $HISTFILE
5. $HISTFILESIZE
6. $HISTSIZE
set -o history
var=$(history); echo "$var" # 1 var=$(history)
bash$ ./[Link]
(нет вывода)
Сайт Advancing in the Bash Shell дает хорошую интродукцию использования истории команд
891
Bash.
892
# Этот файл нормально читается только интерактивной оболочкой.
#+ Здесь место для определения ваших алиасов, функций и других
#+ интерактивных действий, таких как запрос к вводу.
#
# Большая часть кода предполагает, что вы находитесь на
#+ системе GNU (подобной Linux box) и часто основываетесь
#+ на коде, найденном в Usenet или Интернет.
#
# См, например:
# [Link]
# [Link]
# [Link]
# [Link]
#
# Выбор цвета был сделан для оболочки с темным фоном
#+ (белым на черном фоне), а это, как правило, также подходит
#+ для чистой текстовой консоли (когда X сервер не доступен).
#+ Если вы используете белый фон, то вам придется сделать
#+ некоторые изменения для чтения
#
# Этот файл bashrc немного переполнен.
# Помните, что это всего лишь пример.
# Адаптируйте его к вашим потребностям.
#
# =============================================================== #
#-------------------------------------------------------------
# Источник глобальных определений (если таковые имеются)
#-------------------------------------------------------------
if [ -f /etc/bashrc ]; then
. /etc/bashrc # --> Чтение /etc/bashrc, если существует.
fi
#--------------------------------------------------------------
# Автоматическая установка $DISPLAY (если еще не установлена).
# Это работает у меня - у вас может быть иначе . . . .
# Проблема заключается в том, что различные типы терминалов
#+ дают разные ответы на «who am i» (rxvt в частности может быть
#+ проблемой) - однако, как кажется, в большинстве случаев
#+ этот код работает.
#--------------------------------------------------------------
function get_xserver ()
{
case $TERM in
xterm )
XSERVER=$(who am i | awk '{print $NF}' | tr -d ')''(' )
# Ane-Pieter Wieringa предлагает следующую альтернативу:
# I_AM=$(who am i)
# SERVER=${I_AM#*(}
# SERVER=${SERVER%*)}
893
XSERVER=${XSERVER%%:*}
;;
aterm | rxvt)
# Находит код, который здесь работает ...
;;
esac
}
if [ -z ${DISPLAY:=""} ]; then
get_xserver
if [[ -z ${XSERVER} || ${XSERVER} == $(hostname) ||
${XSERVER} == "unix" ]]; then
DISPLAY=":0.0" # Вывод на экран локального хоста.
else
DISPLAY=${XSERVER}:0.0 # Вывод на экран удаленного хоста.
fi
fi
export DISPLAY
#-------------------------------------------------------------
# Некоторые настройки
#-------------------------------------------------------------
# Включенные опции:
shopt -s cdspell
shopt -s cdable_vars
shopt -s checkhash
shopt -s checkwinsize
shopt -s sourcepath
shopt -s no_empty_cmd_completion
shopt -s cmdhist
shopt -s histappend histreedit histverify
shopt -s extglob # Необходимо для программируемого завершения.
# Отключенные опции:
shopt -u mailwarn
unset MAILCHECK # Не желаю, что бы моя оболочка предупреждала меня о
#+ входящей почте.
#-------------------------------------------------------------
# Приветствие, motd и т.д. ...
#-------------------------------------------------------------
894
# Нормальные цвета
Black='\e[0;30m' # Черный
Red='\e[0;31m' # Красный
Green='\e[0;32m' # Зеленый
Yellow='\e[0;33m' # Желтый
Blue='\e[0;34m' # Синий
Purple='\e[0;35m' # Фиолетовый
Cyan='\e[0;36m' # Голубой
White='\e[0;37m' # Белый
# Четкие
BBlack='\e[1;30m' # Черный
BRed='\e[1;31m' # Красный
BGreen='\e[1;32m' # Зеленый
BYellow='\e[1;33m' # Желтый
BBlue='\e[1;34m' # Синий
BPurple='\e[1;35m' # Фиолетовый
BCyan='\e[1;36m' # Голубой
BWhite='\e[1;37m' # Белый
# Фон
On_Black='\e[40m' # Черный
On_Red='\e[41m' # Красный
On_Green='\e[42m' # Зеленый
On_Yellow='\e[43m' # Желтый
On_Blue='\e[44m' # Синий
On_Purple='\e[45m' # Фиолетовый
On_Cyan='\e[46m' # Голубой
On_White='\e[47m' # Белый
#-------------------------------------------------------------
# Shell Prompt — примеры, см.:
# [Link]
# [Link]
# [Link]
# [Link]
#-------------------------------------------------------------
# Текущий формат: [TIME USER@HOST PWD] >
# TIME:
# Green == Машина загружается медленно
895
# Orange == Машина загружается нормально
# Red == Машина загружается быстро
# ALERT == Машина загружается очень быстро
# USER:
# Cyan == обычный пользователь
# Orange == пользователь из-под SU
# Red == администратор
# HOST:
# Cyan == локальная сессия
# Green == безопасное удаленное соединение (через ssh)
# Red == не безопасное удаленное соединение
# PWD:
# Green == больше чем 10% свободного дискового пространства
# Orange == меньше чем 10% свободного дискового пространства
# ALERT == меньше чем 5% свободного дискового пространства
# Red == текущий пользователь не имеет прав на запись
# Cyan == текущая ФС нулевого размера (типа /proc)
# >:
# White == без фоновых или приостановленных заданий в этой оболочке
# Cyan == по крайней мере одно фоновое задание в этой оболочке
# Orange == по крайней мере одно приостановленное задание в этой оболочке
#
# Команда добавляется в файл истории каждый раз, когда вы нажмете enter,
# поэтому оно доступно для всех оболочек (используя «history -a»).
896
программируемой доработки функций function load_color()
{
local SYSLOAD=$(load)
if [ ${SYSLOAD} -gt ${XLOAD} ]; then
echo -en ${ALERT}
elif [ ${SYSLOAD} -gt ${MLOAD} ]; then
echo -en ${Red}
elif [ ${SYSLOAD} -gt ${SLOAD} ]; then
echo -en ${BRed}
else
echo -en ${Green}
fi
}
897
PS1=${PS1}"\[\$(job_color)\]>\[${NC}\] "
# Задаем название текущего xterm:
PS1=${PS1}"\[\e]0;[\u@\h] \w\a\]"
;;
*)
PS1="(\A \u@\h \W) > " # --> PS1="(\A \u@\h \w) > "
# --> Выводим полный путь к файлу текущей
#+ --> директории.
;;
esac
#============================================================
#
# АЛИАСЫ И ФУНКЦИИ
#
# Возможно некоторые функции, определенные здесь, достаточно большие.
# Если вы хотите сделать этот файл меньше, эти функции могут быть
#+ преобразованы в сценарии и удалены отсюда.
#
#============================================================
#-------------------
Личные алиасы
#-------------------
alias h='history'
alias j='jobs -l'
alias which='type -a'
alias ..='cd ..'
#-------------------------------------------------------------
# Семейство 'ls' (предполагается, что вы используете последнее GNU ls).
#-------------------------------------------------------------
# Добавляет цвета в 'ls' для типов файлов и восприятия размеров по умолчанию:
alias ls='ls -h --color'
alias lx='ls -lXB' # Сортировка по расширению.
alias lk='ls -lSr' # Сортировка по размеру, большие после.
alias lt='ls -ltr' # Сортировка по дате, последние внизу.
898
alias lc='ls -ltcr' # Сортировка по времени изменения,поздние внизу.
alias lu='ls -ltur' # Сортировка по времени обращения,позднее внизу.
#-------------------------------------------------------------
# 'less'
#-------------------------------------------------------------
alias more='less'
export PAGER=less
export LESSCHARSET='latin1'
export LESSOPEN='|/usr/bin/[Link] %s 2>&-'
# Применяется, если существует [Link].
export LESS='-i -N -w -z-4 -g -e -M -X -F -R -P%t?f%f \
:stdin .?pb%pb\%:?lbLine %lb:?bbByte %bb:-...'
#-------------------------------------------------------------
# Ошибки правописания - очень личные и зависимые от клавиатуры :-)
#-------------------------------------------------------------
alias xs='cd'
alias vf='cd'
alias moer='more'
alias moew='more'
alias kk='ll'
#-------------------------------------------------------------
# Немного забавного
#-------------------------------------------------------------
function xtitle()
{
case "$TERM" in function xtitle(){}
*term* | rxvt)
echo -en "\e]0;$*\a" ;;
*) ;;
esac
}
899
alias top='xtitle Процессы $ HOST && top'
alias make='xtitle Making $(basename $PWD) ; make'
# .. и функции
function man()
{
for i ; do
xtitle The $(basename $1|tr -d .[:digit:]) manual
command man -a "$i"
done
}
#-------------------------------------------------------------
# Делает следующие команды автоматически работающими в фоновом режиме:
#-------------------------------------------------------------
#-------------------------------------------------------------
# Файлы и строки связанные функциями:
#-------------------------------------------------------------
900
return;
fi
find . -type f -name "${2:-*}" -print0 | \
xargs -0 egrep --color=always -sn ${case} "$1" 2>&- | more
function swap()
{ # 2 файла Swap, если они существуют (из Uzi в bashrc).
local TMPFILE=tmp.$$
mv "$1" $TMPFILE
mv "$2" "$1"
mv $TMPFILE "$2"
}
#-------------------------------------------------------------
# Функции связанные с процессом/системой :
#-------------------------------------------------------------
901
function killps() # убивает по имени процесса
{
local pid pname sig="-TERM" # сигнал по умолчанию
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
echo "Usage: killps [-SIGNAL] шаблон"
return;
fi
if [ $# = 2 ]; then sig=$1 ; fi
for pid in $(my_ps| awk '!/awk/ && $0~pat { print $1 }' pat=${!#} )
do
pname=$(my_ps | awk '$1~var { print $5 }' var=$pid )
if ask "Убить процесс $pid <$pname> сигналом $sig?"
then kill $sig $pid
fi
done
}
if [ ! -d $fs ]
then
echo -e $fs" :Нет такого файла или директории" ; continue
fi
902
echo -e "\n${BRed}Локальный IP Address :$NC" ; my_ip
echo -e "\n${BRed}Открытые соединения :$NC "; netstat -pan --inet;
echo
}
#-------------------------------------------------------------
# Различные утилиты:
#-------------------------------------------------------------
#=========================================================================
#
# ПРОГРАММИРУЕМАЯ ДОРАБОТКА РАЗДЕЛА
# Большинство из них взято из документации bash 2,05 и из пакета Ian McDonald
# 'Bash completion' (HTTP //[Link]/bash/#completion). На самом деле,
# для некоторых функций, Вам нужен bash не младше 3.0.
#
# Обратите внимание, что большинство дистрибутивов linux теперь предоставляют
# множество доработок 'из коробки' - однако, вам однажды, может потребоваться
# сделать их самому, поэтому я поместил их сюда в качестве примеров.
#=========================================================================
903
complete -A alias alias unalias
complete -A function function
complete -A user su mail finger
# Сжатие
complete -f -o default -X '*.+(zip|ZIP)' zip
complete -f -o default -X '!*.+(zip|ZIP)' unzip
complete -f -o default -X '*.+(z|Z)' compress
complete -f -o default -X '!*.+(z|Z)' uncompress
complete -f -o default -X '*.+(gz|GZ)' gzip
complete -f -o default -X '!*.+(gz|GZ)' gunzip
complete -f -o default -X '*.+(bz2|BZ2)' bzip2
complete -f -o default -X '!*.+(bz2|BZ2)' bunzip2
complete -f -o default -X '!*.+(zip|ZIP|z|Z|gz|GZ|bz2|BZ2)' extract
# Документы - Postscript,pdf,dvi.....
complete -f -o default -X '!*.+(ps|PS)' gs ghostview ps2pdf ps2ascii
complete -f -o default -X \
'!*.+(dvi|DVI)' dvips dvipdf xdvi dviselect dvitype
complete -f -o default -X '!*.+(pdf|PDF)' acroread pdf2ps
complete -f -o default -X '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?\
(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv
complete -f -o default -X '!*.texi*' makeinfo texi2dvi texi2html texi2pdf
complete -f -o default -X '!*.tex' tex latex slitex
complete -f -o default -X '!*.lyx' lyx
complete -f -o default -X '!*.+(htm*|HTM*)' lynx html2ps
complete -f -o default -X \
'!*.+(doc|DOC|xls|XLS|ppt|PPT|sx?|SX?|csv|CSV|od?|OD?|ott|OTT)' soffice
# Мультимедиа
complete -f -o default -X \
'!*.+(gif|GIF|jp*g|JP*G|bmp|BMP|xpm|XPM|png|PNG)' xv gimp ee gqview
complete -f -o default -X '!*.+(mp3|MP3)' mpg123 mpg321
complete -f -o default -X '!*.+(ogg|OGG)' ogg123
complete -f -o default -X \
'!*.@(mp[23]|MP[23]|ogg|OGG|wav|WAV|pls|\
m3u|xm|mod|s[3t]m|it|mtm|ult|flac)' xmms
complete -f -o default -X '!*.@(mp?(e)g|MP?(E)G|wma|avi|AVI|\
asf|vob|VOB|bin|dat|vcd|ps|pes|fli|viv|rm|ram|yuv|mov|MOV|qt|\
QT|wmv|mp3|MP3|ogg|OGG|ogm|OGM|mp4|MP4|wav|WAV|asx|ASX)' xine
904
#+ (Это будет выглядеть как 'ls --color=auto' для правильной работы).
COMP_WORDBREAKS=${COMP_WORDBREAKS/=/}
_get_longopts()
{
#$1 --help | sed -e '/--/!d' -e 's/.*--\([^[:space:].,]*\).*/--\1/'| \
#grep ^"$2" |sort -u ;
$1 --help | grep -o -e "--[^[:space:].,]*" | grep -e "$2" |sort -u
}
_longopts()
{
local cur
cur=${COMP_WORDS[COMP_CWORD]}
case "${cur:-*}" in
-*) ;;
*) return ;;
esac
case "$1" in
\~*) eval cmd="$1" ;;
*) cmd="$1" ;;
esac
COMPREPLY=( $(_get_longopts ${1} ${cur} ) )
}
complete -o default -F _longopts configure bash
complete -o default -F _longopts wget id info a2ps ls recode
_tar()
{
local cur ext regex tar untar
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
case "${COMP_WORDS[1]}" in
?(-)c*f)
COMPREPLY=( $( compgen -f $cur ) )
return 0
;;
+([^Izjy])f)
ext='tar'
regex=$ext
;;
*z*f)
ext='[Link]'
regex='t\(ar\.\)\(gz\|Z\)'
;;
905
*[Ijy]*f)
ext='t?(ar.)bz?(2)'
regex='t\(ar\.\)bz2\?'
;;
*)
COMPREPLY=( $( compgen -f $cur ) )
return 0
;;
esac
else
# Файл содержит соответствующие файлы.
COMPREPLY=( $( compgen -G $cur\*.$ext ) )
fi
return 0
_make()
{
local mdef makef makef_dir="." makef_inc gcmd cur prev i;
COMPREPLY=();
cur=${COMP_WORDS[COMP_CWORD]};
prev=${COMP_WORDS[COMP_CWORD-1]};
case "$prev" in
-*f)
COMPREPLY=($(compgen -f $cur ));
return 0
;;
esac;
case "$cur" in
-*)
COMPREPLY=($(_get_longopts $1 $cur ));
return 0
;;
esac;
906
elif [ -f ${makef_dir}/makefile ]; then
makef=${makef_dir}/makefile
elif [ -f ${makef_dir}/Makefile ]; then
makef=${makef_dir}/Makefile
else
makef=${makef_dir}/*.mk # Локальное соглашение.
fi
_killall()
{
local cur prev
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
return 0
}
907
# Локальные переменные:
# Режим:shell-script
# sh-shell:bash
# Конец:
DARKGRAY='\e[1;30m'
LIGHTRED='\e[1;31m'
GREEN='\e[32m'
YELLOW='\e[1;33m'
LIGHTBLUE='\e[1;34m'
NC='\e[m'
PS1="\n$GREEN[\w] \n$DARKGRAY($PCT\t$DARKGRAY)-($PCT\u$DARKGRAY)-($PCT\!
$DARKGRAY)$YELLOW-> $NC"
908
('\$PCT'\u$DARKGRAY)-('\$PCT'\!$DARKGRAY)$YELLOW-> $NC'\`"
909
Оператор пакетных файлов Эквивалент Shell Script Значение
ECHO OFF set +v Не выводит на экран следующую
(ие) команду(ы)
FOR %%VAR IN (LIST) for var in [list]; do Цикл "for"
DO
:LABEL отсутствует (без метка
необходимости)
GOTO отсутствует Переход к другому месту в
(используется функция) сценарии
PAUSE sleep Пауза или интервал ожидания
CHOICE case или select Меню выбора
IF if Проверка if
IF EXIST FILENAME if [ -e filename ] проверка if на наличие файла
IF !%N==! if [ -z "$N" ] Проверка на отсутствие
заменяемого параметра "N"
CALL source или . (оператор 'Включение' другого сценария
точка)
COMMAND /C source или . (оператор 'Включение' другого сценария (то
точка) же, что и CALL)
SET export Присваивание переменной
окружения
SHIFT shift сдвиг влево списка аргументов
командной строки
SGN -lt или -gt знак (целого числа)
ERRORLEVEL $? Статус выхода
CON stdin "консоль" (stdin)
PRN /dev/lp0 (Основное) устройство принтера
LPT1 /dev/lp0 Первое устройство принтера
COM1 /dev/ttyS0 Первый последовательный порт
Пакетные файлы обычно содержат команды DOS. Для преобразования пакетного файла в
сценарий оболочки они должны быть заменены на их эквиваленты в UNIX.
Таблица N-2. Команды DOS и их эквиваленты в UNIX
910
Команда DOS Эквивалент Действие
UNIX
COMP diff, comm, cmp Сравнение файла
COPY cp Копирование файла
Ctl-C Ctl-C Сброс (сигнала)
Ctl-Z Ctl-D EOF (окончание файла)
DEL rm Удаление файла(ов)
DELTREE rm -rf Рекурсивное удаление
директории
DIR ls -l Листинг директории
ERASE rm Удаление файла(ов)
EXIT exit Завершение текущего
процесса
FC comm, cmp Сравнение файла
FIND grep Поиск строк в файлах
MD mkdir Создание директории
MKDIR mkdir Создание директории
MORE more Постраничный фильтр
текстового файла
MOVE mv Перемещение
PATH $PATH Путь для выполнения
REN mv Переименование
(перемещение)
RENAME mv Переименование
(перемещение)
RD rmdir Удаление директории
RMDIR rmdir Удаление директории
SORT sort Сортировка файла
TIME date Вывод системного
времени
TYPE cat Вывод файла в stdout
XCOPY cp (расширенное)
копирование файла
911
результат часто читается лучше, чем оригинал.
Пример N-1. [Link]: Пакетный файл DOS
REM VIEWDATA
@ECHO OFF
:VIEWDATA
TYPE C:\BOZO\[Link] | MORE
REM SHOW ENTIRE FILE, 1 PAGE AT A TIME.
:EXIT0
DATAFILE=/home/bozo/datafiles/[Link]
ARGNO=1
exit 0 # :EXIT0
Примечания
[1] Читатели предложили изменения пакетного файла выше, приукрасив его и сделав его
более компактным и эффективным. По мнению автора ABS Guide это зря затраченные
усилия. Сценарий Bash может получить доступ к файловой системе DOS, или даже к
912
разделам NTFS (с помощью ntfs-3g), для пакетной обработки или операций сценариями.
Приложение O. Упражнения
Содержание
O.1. Анализ сценариев
O.2. Написание сценариев
(Увы, автора ABS Guide нет среди них. Это, наверное, благодаря злым слухам об отсутствии
учетных данных и недостаточном навыке написания сценариев.)
MAX=10000
913
let "t1 = nr % 5"
if [ "$t1" -ne 3 ]
then
continue
fi
done
exit 0
---
Объясните, что делает следующий сценарий. На самом деле это просто канал
параметризованной командной строки.
#!/bin/bash
DIRNAME=/usr/bin
FILETYPE="shell script"
LOGFILE=logfile
exit 0
---
Исследуйте и объясните следующий сценарий. Подсказка, вы можете ссылаться на списки с
find и stat.
#!/bin/bash
914
}
---
Прочитайте следующий фрагмент кода.
while read LINE
do
echo $LINE
done < `tail -f /var/log/messages`
915
именем [Link].
Преобразуйте циклы for в Примере 11-1 в циклы while. Подсказка: сохраните данные в
массив и пройдите по элементам массива.
Теперь, сделав эту трудную работу, конвертируйте циклы примера в циклы until.
Обратный список
В качестве входных данных задайте список имен файлов, этот сценарий должен
запрашивать каждый указанный файл (анализируя вывод команды file) для определения
используемого в них типа сжатия. Затем сценарий должен автоматически вызывать
команду соответствующей распаковки (gunzip, bunzip2, unzip, uncompress или любую
другую). Если файл не сжат, сценарий должен выдать предупреждающее сообщение, но
не принимать никаких иных действий для этого, конкретного, файла.
916
Создайте «уникальный» 6-значный шестнадцатеричный идентификатор для вашего
компьютера. Не используйте команду hostid. Подсказка: md5sum /etc/passwd, а
затем выберите первые 6 цифр вывода.
Резервное копирование
Заархивируйте в виде «tarball» (*.[Link] файлы) все файлы в дереве вашей домашней
директории (/home/ваше имя), которые были изменены за последние 24 часа.
Подсказка: используйте find.
Задав идентификатор процесса (PID), как аргумент, этот сценарий должен проверять, в
интервалы, определяемые пользователем, выполняется ли еще данный процесс. Вы
можете использовать команды ps и sleep.
Простые числа
Выведите (в stdout) все простые числа между 60000 и 63000. Вывод должен быть
красиво отформатирован в колонки (Подсказка: используйте printf).
Лотерейные номера
Один тур лотереи включает в себя набор пяти различных чисел, в диапазоне 1-50.
Напишите сценарий, который создает пять псевдослучайных чисел в этом диапазоне
без повторений. Сценарий должен дать возможность выводить числа в stdout или
сохранять их в файл, а также дату и время, когда определенный набор чисел был создан.
(Если ваш сценарий последовательно создает выигрышные номера лотереи, то можно
заработать, оставить оболочки сценариев для тех из нас, кто должен работать для того,
что бы жить.)
СРЕДНЕЙ СЛОЖНОСТИ
Целое число или строка
expr $1 + 0
917
Функция atoi в Cи преобразует символ строки в целое число. Напишите функцию
сценария оболочки, которая выполняет ту же операцию. Аналогичным образом,
напишите функцию сценария оболочки, которая производит обратное, зеркальное,
преобразование функции Cи itoa , которая преобразует целое число в символ ASCII.
Banner
Безопасное удаление
918
периодически вызываемого демоном cron).
Размен
Квадратное уравнение
Таблица логарифмов
Таблица Юникода
Подсказка: Используйте опцию -e для echo: echo -e '\uXXXX', где XXXX — это
числовое обозначение символа Юникода. Необходима версия 4.2 Bash или более
поздняя.
919
Некоторые примеры подходящих чисел 42057, 74638 и 89515.
Счастливые числа
Кости
Крестики-нолики
o | x |
----------
| x |
----------
| o |
Алфавитная строка
Анализ
Журнал входа
920
Какие-то базы данных и пакеты таблиц используют сохранение файлов с полями,
разделенными запятыми, обычно называемые разделителями-запятыми или CSV.
Другим приложениям часто необходимо анализировать эти файлы.
Обоснование
Задайте ввод текста ASCII из stdin или файла, настройте пробелы между словами к
правому краю каждой строки, заданной пользователем ширины строки, а затем
отправьте вывод в stdout.
Список рассылки
Создание паролей
Мониторинг пользователей
ТРУДНЫЕ
921
Проверка паролей
Пробный пароль должен будет вводиться в сценарий, как параметр командной строки.
Чтобы считаться приемлемым, пароль должен отвечать следующим минимальным
требованиям:
Дополнительно:
Перекрестная ссылка
Квадратный корень
guess = $argument
922
# $argument это число, из которого нужно найти квадратный корень.
# $guess это каждое последовательно вычисляемое «предположение»
#+ -- или промежуточное решение -- квадратного корня.
# Наш первый "guess" квадратного корня это сам аргумент.
oldguess = 0
# $oldguess это предыдущий $guess.
tolerance = .000001
# Точность желаемого вычисления.
loopcnt = 0
# Давайте сделаем несколько проходов цикла.
# Некоторые аргументы потребуют больше проходов цикла, чем другие.
do
oldguess = $guess # Обновляем $oldguess на предыдущий $guess.
# =======================================================
guess = ( $oldguess + ( $argument / $oldguess ) ) / 2.0
# = 1/2 ( ($oldguess **2 + $argument) / $oldguess )
# равнозначно:
# = 1/2 ( $oldguess + $argument / $oldguess )
# то есть, «усреднение» промежуточного решения и отклонение доли
#+ аргумента (разделение ошибки пополам).
# Это сводит к точному решению, удивительно, несколькими проходами
#+ цикла... для аргументов > $tolerance, конечно
# =======================================================
Журнал всех доступов к файлам в /etc в течении одного дня. Информация должна
включать: имя файла, имя пользователя и время доступа. Если какие-либо файлы
изменялись, то они должны быть отмечены. Эти данные должны быть записаны в файл
журнала в табличной (разделенной табуляцией) форме.
Мониторинг процессов
923
Напишите сценарий для постоянного мониторинга всех запущенных процессов и
отслеживания количества дочерних процессов порожденных каждым родителем. Если
процесс порождает более пяти потомков, то сценарий должен отправлять по
электронной почте всю соответствующую информацию системному администратору
(или root), включая время, родительский PID, PID потомков, и т.д. Сценарий должен
добавлять отчет в файл журнала каждые десять минут.
Удаление комментариев
Удалите все тэги из указанного файла HTML, затем, переформатируйте его в строки
длиной от 60 до 75 символов. При необходимости сбросьте абзацы, заблокируйте
интервалы и конвертируйте HTML-таблицы в их примерные текстовые эквиваленты.
Конвертация XML
Определение спаммеров
Создание Ман-страниц
924
Для начала будет полезен Пример A-39.
Hex Dump
Расчет детерминантов
Скрытые слова
Аннанграммы
Лестницы слов
925
mark --> park --> part --> past --> vast --> vase
^ ^ ^ ^ ^
Fog Index
"Fog index" оценивает сложность чтения отрывка текста в виде числа, примерно
соответствующему определенному школьному уровню. Например, отрывок с fog index
12 должен быть понятен любому, кто учится 12 лет.
По большому счету, Gunning fog index не должен считать сложные слова и имена
собственные «трудными» словами, это бы чрезвычайно усложнило сценарий.
926
В духе Примера 16-50, напишите сценарий, который выполняет способом Монте-Карло
моделирование иглы Buffon. Для упрощения, примите длину иглы, равной расстоянию
между трещинами, n = d.
Шифр Playfair
Шифр Playfair шифрует текст путем замены диграмм (2-х буквенных групп).
Традиционным является использование алфавитного квадрата состоящего из 5 х 5
ключевых букв для шифрования и дешифрования.
C O D E S
A B F G H
I K L M N
P Q R T U
V W X Y Z
Для шифрования, разделите текст сообщения на пары букв (2-х буквенные группы).
Если группа имеет две одинаковые буквы, удалите вторую и сформируйте новую
группу. Если в конце остается одна буква - вставляется символ «null», как
правило «X»
TH IS IS AT OP SE CR ET ME SA GE
или
или
927
Пара "TH" подпадающая под случай #3.
G H
M N
T U (Прямоугольник с "T" и "H" в углах)
T --> U
H --> G
=========================================================================
--
Пожалуйста, не присылайте автору ваши решения этих упражнений. Есть более подходящие
способы, что бы впечатлить его вашим умом, например исправления и предложения по
улучшению книги.
Примечания
[1] Для всех умных типов, которые не осилили промежуточную алгебру, детерминанта это
числовое значение связанное с многомерной матрицей (массивом чисел).
|a b|
|b a|
928
Решением является a*a - b*b, где "a" и "b" являются числами.
929
of the Linux Documentation Project.
Here is the e-mail to the LDP requesting permission to submit version 0.1.
From thegrendel@[Link] Sat Jun 10 09:05:33 2000 -0700
Date: Sat, 10 Jun 2000 09:05:28 -0700 (MST)
From: "M. Leo Cooper" <thegrendel@[Link]>
X-Sender: thegrendel@localhost
To: ldp-discuss@[Link]
Subject: Permission to submit HOWTO
I am working on and would like to submit to the LDP a HOWTO on the subject
of "Bash Scripting" (shell scripting, using 'bash'). As it happens,
I have been writing this document, off and on, for about the last eight
months or so, and I could produce a first draft in ASCII text format in
a matter of just a few more days.
At this stage, I'm up to 18,000+ words (124k), and that's over 50 pages of
text (whew!).
Mendel Cooper
thegrendel@[Link]
930
Релиз Дата Комментарий
0.1 14 Jun 2000 Initial release.
931
Релиз Дата Комментарий
0.2 30 Oct 2000 Bugs fixed, plus much additional material and more example scripts.
0.3 12 Feb 2001 Major update.
0.4 08 Jul 2001 Complete revision and expansion of the book.
0.5 03 Sep 2001 Major update: Bugfixes, material added, sections reorganized.
1.0 14 Oct 2001 Stable release: Bugfixes, reorganization, material added.
1.1 06 Jan 2002 Bugfixes, material and scripts added.
1.2 31 Mar 2002 Bugfixes, material and scripts added.
1.3 02 Jun 2002 TANGERINE release: A few bugfixes, much more material and scripts
added.
1.4 16 Jun 2002 MANGO release: A number of typos fixed, more material and scripts.
1.5 13 Jul 2002 PAPAYA release: A few bugfixes, much more material and scripts added.
1.6 29 Sep 2002 POMEGRANATE release: Bugfixes, more material, one more script.
1.7 05 Jan 2003 COCONUT release: A couple of bugfixes, more material, one more script.
1.8 10 May 2003 BREADFRUIT release: A number of bugfixes, more scripts and material.
1.9 21 Jun 2003 PERSIMMON release: Bugfixes, and more material.
2.0 24 Aug 2003 GOOSEBERRY release: Major update.
2.1 14 Sep 2003 HUCKLEBERRY release: Bugfixes, and more material.
2.2 31 Oct 2003 CRANBERRY release: Major update.
2.3 03 Jan 2004 STRAWBERRY release: Bugfixes and more material.
2.4 25 Jan 2004 MUSKMELON release: Bugfixes.
2.5 15 Feb 2004 STARFRUIT release: Bugfixes and more material.
2.6 15 Mar 2004 SALAL release: Minor update.
2.7 18 Apr 2004 MULBERRY release: Minor update.
2.8 11 Jul 2004 ELDERBERRY release: Minor update.
3.0 03 Oct 2004 LOGANBERRY release: Major update.
3.1 14 Nov 2004 BAYBERRY release: Bugfix update.
3.2 06 Feb 2005 BLUEBERRY release: Minor update.
3.3 20 Mar 2005 RASPBERRY release: Bugfixes, much material added.
3.4 08 May 2005 TEABERRY release: Bugfixes, stylistic revisions.
3.5 05 Jun 2005 BOXBERRY release: Bugfixes, some material added.
3.6 28 Aug 2005 POKEBERRY release: Bugfixes, some material added.
3.7 23 Oct 2005 WHORTLEBERRY release: Bugfixes, some material added.
3.8 26 Feb 2006 BLAEBERRY release: Bugfixes, some material added.
3.9 15 May 2006 SPICEBERRY release: Bugfixes, some material added.
4.0 18 Jun 2006 WINTERBERRY release: Major reorganization.
4.1 08 Oct 2006 WAXBERRY release: Minor update.
4.2 10 Dec 2006 SPARKLEBERRY release: Important update.
4.3 29 Apr 2007 INKBERRY release: Bugfixes, material added.
5.0 24 Jun 2007 SERVICEBERRY release: Major update.
5.1 10 Nov 2007 LINGONBERRY release: Minor update.
5.2 16 Mar 2008 SILVERBERRY release: Important update.
932
Релиз Дата Комментарий
5.3 11 May 2008 GOLDENBERRY release: Minor update.
5.4 21 Jul 2008 ANGLEBERRY release: Major update.
5.5 23 Nov 2008 FARKLEBERRY release: Minor update.
5.6 26 Jan 2009 WORCESTERBERRY release: Minor update.
6.0 23 Mar 2009 THIMBLEBERRY release: Major update.
6.1 30 Sep 2009 BUFFALOBERRY release: Minor update.
6.2 17 Mar 2010 ROWANBERRY release: Minor update.
6.3 30 Apr 2011 SWOZZLEBERRY release: Major update.
6.4 30 Aug 2011 VORTEXBERRY release: Minor update.
6.5 05 Apr 2012 TUNGSTENBERRY release: Minor update.
6.6 27 Nov 2012 YTTERBIUMBERRY release: Minor update.
10 10 Mar 2014 YTTERBIUMBERRY release: License change.
933
Приложение Q. Сайты зеркал и загрузки
Последнее обновление этого документа, в виде архива, тарболла bzip2, включая исходник
SGML и созданный HTML, могут быть загружены с домашнего сайта автора. Также доступна
версия pdf (с зеркала сайта). Есть также версия epub, любезно предоставленная Craig Barnes
и Michael Satke. В журнале изменений указана подробная история изменений. ABS Guide
даже имеет свою собственную страницу [Link]/freecode, для отслеживания
важных обновлений, комментариев пользователей и рейтинга популярности проекта.
Основным хостингом сайта этого документа является Linux Documentation Project, который
также сопровождает многие другие Руководства и HOWTO.
Большое спасибо Ronny Bangsund за предоставление дискового пространства для
размещения этого проекта.
934
Приложение S. Авторские права
Advanced Bash Scripting Guide является настоящим общественным достоянием (PUBLIC
DOMAIN). Это имеет следующие смысл и последствия.
A. Все предыдущие релизы Advanced Bash Scripting Guide также предоставляются
в общественное достояние.
A1. Все печатные издания, с согласия автора или нет, также предоставляются
в общественное достояние. Это юридически переопределяет любые намерения
или желания издателей. Любое заявление авторских прав является ничтожным
и недействительным.
В ЭТОМ ПУНКТЕ НИКАКИХ ИСКЛЮЧЕНИЙ БЫТЬ НЕ МОЖЕТ
A2. Любой выпуск Advanced Bash Scripting Guide, будь то в электронной или
печатной форме, предоставляются в общественное достояние по прямому
указанию автора и предыдущего владельца авторских прав, Mendel Cooper.
Без каких-либо других лиц или организаций, когда-либо имевших
действительное авторское право.
935
использование этой книги. Особенно автор призывает (royalty-free!) использовать ее для
учебных целей.
На сегодняшний день, ограниченные права на печать (издание Lulu) были предоставлены
одному лицу и никому другому. Ни кто, ни собственно Lulu, не имеет и никогда не имел
действительных авторских прав.
Все другие коммерческие торговые марки, упомянутые в тексте данной работы, являются
зарегистрированными их владельцами.
Hyun Jin Cha сделал корейский перевод версии 1.0.11 этой книги. Испанский, португальский,
французский, немецкий, итальянский, русский, чешский, китайский, индонезийский,
голландский, румынский, болгарский и турецкий переводы, также доступны или в стадии
936
разработки. Если вы хотите перевести этот документ на другой язык, пожалуйста, не
стесняйтесь делать это, в соответствии с условиями, изложенными выше. Автор хотел бы
получать уведомления о таких усилиях.
937
Приложение T. Таблица ASCII
Традиционно, книги такого рода имеют в приложении таблицу ASCII. В этой книге этого нет.
Вместо этого, в ней несколько коротких сценариев, каждый из которых создает полную
таблицу ASCII.
Пример T-1. Сценарий создающий таблицу ASCII
#!/bin/bash
# [Link]
938
# ver. 0.2, reldate 26 Aug 2008
# Исправлено автором ABS Guide.
MAXNUM=256
COLUMNS=5
OCT=8
OCTSQU=64
LITTLESPACE=-3
BIGSPACE=-5
exit $?
# Упражнение:
# Перепишите сценарий для использования десятичных чисел, вместо восьмеричных.
#-------------------------------------------------------------------------
#-- Файл: [Link] Вывод символов ASCII, создан 10/8/16 (JETS-2012)
939
#-------------------------------------------------------------------------
#-- Usage: ascii [oct|dec|hex|help|8|10|16]
#--
#-- Этот сценарий выводит краткое описание кодов символов ASCII от 0 до 127.
#-- Числовые значения могут быть выведены по основанию 10, 8 или 16.
#--
#-- Формат основания: /usr/share/lib/pub/ascii с основанием 10, как умолчание.
#-- Детали см. man ascii ...
#-------------------------------------------------------------------------
case "$1" in
oct|[Oo]?([Cc][Tt])|8) Obase=Octal; Numy=3o;;
hex|[Hh]?([Ee][Xx])|16|[Xx]) Obase=Hex; Numy=2X;;
help|?(-)[h?]) sed -n '2,/^[ ]*$/p' $0;exit;;
code|[Cc][Oo][Dd][Ee])sed -n '/case/,$p' $0;exit;;
*) Obase=Decimal
esac # На самом деле КОДА меньше, чем символов!
AB="nul soh stx etx eot enq ack bel bs tab nl vt np cr so si dle"
AD="dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us sp"
IDX=0
while [ $IDX -le 127 ] && CHR="${ABR[$IDX]}"
do ((${#CHR}))&& FM2='%-3s'|| FM2=`printf '\\\\%o ' $IDX`
printf "$FM1 $FM2" "$IDX" $CHR; (( (IDX+=1)%8))||echo '|'
done
exit $?
#-------------------------------------------------------------------------
#-- Файл: ascii Вывод символов ASCII, создан 10/8/16 (JETS-2010)
#-------------------------------------------------------------------------
#-- Usage: ascii [oct|dec|hex|help|8|10|16]
#--
#-- Этот сценарий выводит краткое описание кодов символов ASCII от 0 до 127.
#-- Числовые значения могут быть выведены по основанию 10, 8 или 16.
#--
#-- Формат основания: /usr/share/lib/pub/ascii по основанию 10, как умолчание.
#-- Детали см. man ascii
#-------------------------------------------------------------------------
940
case "$1" in
oct|[Oo]?([Cc][Tt])|8) Obase=Octal; Numy=3o;;
hex|[Hh]?([Ee][Xx])|16|[Xx]) Obase=Hex; Numy=2X;;
help|?(-)[h?]) sed -n '2,/^[ ]*$/p' $0;exit;;
code|[Cc][Oo][Dd][Ee])sed -n '/case/,$p' $0;exit;;
*) Obase=Decimal
esac
export Obase # На самом деле КОДА меньше, чем символов!
exit $?
Указатель
Это указатель/глоссарий/список быстрых ссылок на многие важные темы, затронутые в
тексте руководства. Термины расположены в приблизительной сортировке ASCII, измените, в
случае необходимости, для повышения ясности.
Обратите внимание, что команды указаны в Главе 4.
***
^ (знак вставки)
~ Тильда
= Знак равенства
941
Пример сценария
< Левая угловая скобка
• Меньше, чем
Сравнение строк
Сравнение целых чисел внутри двойных круглых скобок
• Перенаправление
< stdin
• Больше, чем
Сравнение строк
Сравнение целых чисел внутри двойных круглых скобок
• Перенаправление
| Канал (туннель), устройство для передачи вывода команды другой команде или оболочке
- (тире)
942
• Указывает перенаправление из stdin или stdout
• -- (двойное тире)
• Разделитель команд
Когда требуется...
если ключевое слово do находится на первой строке цикла
при завершении блока кода в фигурных скобках
• ;;& ;& Завершает опции в case (версия 4 Bash).
: Двоеточие
? (знак вопроса)
// Двойной слэш
. (точка / точка)
• . Загрузка файла (в сценарии), эквивалентна команде source
943
• . Текущая рабочая директория
• .. Родительская директория
, Оператор запятая
• ,
( ) Круглые скобки
• >( ... )
[ ] Квадратные скобки
• Элемент массива
944
${ ... } Управление/оценка переменной
945
\ Символ экранирует следующее :
* Звездочка
% Знак процента
946
• Оператор удаления содержимого строки (соответствующего шаблону)
+ Знак плюс
***
Переменные оболочки
$_ Последний аргумент предыдущей команды
$$ ID процесса сценария
Аннаграммы
And list
• Чтобы поставить по умолчанию аргумент командной строки
947
Заэкранированные угловые скобки, \< ... \> граница слова в регулярном выражении
Архивирование
• rpm
• tar
Арифметическое расширение
• Из статуса выхода
• Из другого
Арифметические операторы
• Комбинация операторов, Cи-стиль
+= -= *= /= %=
Массивы
• Ассоциативные массивы - более эффективны, чем обычные массивы
• Нотация скобок
• Копирование
• Объявление
declare -a array_name
• Встроенные массивы
• Косвенные ссылки
• Инициализация
array=( element1 element2 ... elementN)
Пример сценария
Использование подстановки команд
• Загрузка файла в массив
• Многомерность, имитация
948
• Размещение и вложение
• Запись и использование
• Действия
• Манипуляции со строками
***
Подсветка, настройка яркости
Обратные кавычки, использование в подстановке команды
Преобразование основания, пример сценария
Bash
• Плохая практика создания сценариев
Таблица
• Особенности, которых не хватает классическому Bourne shell
949
• Внутренние переменные
• Версия 2
• Версия 3
• Версия 4
Версия 4.1
Версия 4.2
.bashrc
$BASH_SUBSHELL
• В here document
Библиография (Литература)
Утилита Bison
Битовые операторы
• Пример сценария
Блочные устройства
• проверка for
Блоки кода
• Итерация/цикличность
• Перенаправление
• Параметризация
950
Квадратные скобки, [ ]
• Элемент массива
• find
• Позиционные параметры
• xargs
• Параметр (необязательный)
***
Конструкция case
• Параметры командной строки, обработка
• Злоупотребление
• Сценарии cat
• Использование
Символьные устройства
• Проверка for
Контрольная сумма
Дочерний процесс (процесс потомок)
951
Двоеточие, : ,эквивалент true для встроенных команд Bash
Расцвечивание сценариев
• Цикличность цветов фона, пример сценария
• Обратные кавычки
• Вызов подоболочки
• Вложение
• Разбиение слова
Соединения и хосты
Составные операторы сравнения
Утилиты сжатия
• bzip2
• compress
• gzip
• zip
952
Символы управления
• Control-C, прерывание
• Control-H, уничтожение
Сопроцессы
cron, служба (демон) планирования
• в команде find
• в xargs
***
Службы, в ОС типа UNIX
date
• Конвертация
• Swap-файлы, инициализация
• [Link]
953
Сценарии для отладки
• Инструменты
• Ловушки на выходе
• Ловушки сигналов
• опции
Параметры по умолчанию
Директория /dev
Файлы оборудования
dialog, утилита для создания диалоговых окон в сценарии
$DIRSTACK стек директории
954
echo
Переменные среды
-eq , равно сравнение целых чисел в проверке
• Внутренние ссылки
• Опасность использования
Упражнения
Выход и Статус выхода
955
• exit команда
• Извлечение Substring
• Совпадение Substring
• + Символ совпадения
***
factor, раскладывает целые числа на просты множители
956
n<&- Закрывает входной дескриптор файла n
Шифрование файла
find
• {} Фигурные скобки
Фильтр
• Использование — с утилитой обработки файлов в качестве фильтра
• Локальные переменные
и рекурсия
• Передача массива в функцию
• Позиционные параметры
• Рекурсия
957
• Возвращение
***
Игры и развлечения
• Анаграммы
• Анаграммы, снова
• Решатель кроссвордов
• Шифрование котировок
• Пятнашки
• Скачки
• Рыцарский турнир
• Игра "Life"
• Волшебные квадраты
• Nim
• Pachinko
• Perquackey
• Лепестки Розы
• Подкаст
• Стихи
• Создание речи
• Ханойские пагоды
958
Графическая версия
Другая графическая версия
getopt, внешняя команда для проверки аргументов сценария в командной строке
• Эмуляция в сценарии
getopts, встроенная команда Bash для проверки аргументов сценария в командной строке
• $OPTIND / $OPTARG
Глобальная переменная
Globbing, расширение имен файлов
• Корректная обработка имен файлов
Шифр Gronsfeld
$GROUPS, Группы в которых состоит пользователь
***
Хеширование , создание таблицы ключей поиска
• Пример сценария
Here document
• Анонимный here document, использование :
• Сценарий cat
959
• Подстановка команд
• Сценарий ex
• Here strings
Использование read
• Limit string
• Подстановка параметра
• Временные файлы
• Использование vi не интерактивно
История команд
$HOME, домашняя директория пользователя
***
Параметр $Id, в rcs (Revision Control System)
960
Операторы сравнения целых чисел
in, ключевое слово предшествующее [list] в цикле for
• Пример использования
• Пример сценария
Итерации (повторения)
***
ID заданий, таблица
jot, эмиссия последовательности целых чисел. Эквивалент seq.
***
Ключевые слова
• error, if missing
***
lastpipe опция оболочки
961
$LINENO, переменная, указывающее номер строки, где она появляется в сценарии
• Символьные ссылки, ln -s
Конструкция List
• And list
• Or list
Локальные переменные
• и рекурсия
Локализация
Логические операторы (&&, ||, etc.)
Цикл for
Цикл while
• do (ключевое слово), начинает выполнение команд в цикле
• Циклы for
Параметризация [list]
Перенаправление
962
• in, (ключевое слово) предшествующее [list] в цикле for
• Вложенные циклы
Цикл for
Цикл while
• Цикл until
until [ condition-is-true ]; do
• Цикл while
while [ condition ]; do
Оборудование Loopback
• Директория /dev
***
m4, макропроцессорный язык
Команды вычислений
963
Метасмысл
Сценарий для изучения кода Морзе
Modulo, оператор остатка вычисления
Сетевое программирование
nl, фильтр количества строк текста
***
-o Логическое ИЛИ объединяющее сравнение проверки
Помутнение
• Запятая как имя функции
• Домашнее задание
Оператор
964
• Значение
• Старшинство
Опции, переданные оболочке или сценарию из командной строки или указанной командой
Or list
Логический оператор ИЛИ, ||
***
Подстановка параметров
• ${parameter+alt_value}
${parameter:+alt_value}
Параметры по умолчанию
• ${!varprefix*}
${!varprefix@}
Значение параметра
• Изменения в Case (врсия 4+ Bash).
• Пример сценария
965
• Заключенная в скобки группа расширенных регулярных выражений
Путь к имени файла, filename включает в себя полный путь к заданному файлу.
• Встроенные комментарии
Ловушки
• - (тире) не является оператором перенаправления
• Злоупотребление cat
966
• Заключенный в двойные кавычки символ обратного слэша (\)
= и -eq не взаимозаменяемы
• Передача
echo в цикле
tail -f в grep
• Не инициализированные переменные
• Пробел, ошибки
967
Указатели
• Файловых дескрипторов
• Функций
• Внутренних ссылок
• Переменных
• Применение whatis
Позиционные параметры
• $@, как отдельные слова
• в функциях
• стандарт 1003.2
• Символы классов
Внеочередной, оператор
Предварение строк заголовка файла, пример сценария
Простые числа
• Создание простых чисел с помощью команды factor
Директория /proc
968
Процесс
• Дочерний процесс (потомок)
• Родительский процесс
• ID процесса (PID)
Процесс подстановки
• Сравнение содержимого директорий
• Временно
***
Quackey, анаграммная игра типа Perquackey (сценарий)
Знак вопроса, ?
Заключение в кавычки
• Символ строки
• Переменные
в скобках проверки
• Пробел, использование заключение в кавычки для сохранения
***
Случайные числа
• /dev/urandom
969
• rand(), случайная функция в awk
• Опции
• "Подготовка" текста
• Время ввода
readline библиотека
Рекурсия
• Представление
• Факториал
• Числа Фибоначчи
• Локальные переменные
• Ханойские пагоды
Перенаправление
• Блоки кола
970
• exec <filename,
<>filename
• stderr в stdout
2>&1
• stdin функции
• stdout в файл
Ссылки карт
• Различные конструкции
• Параметр подстановки/расширения
• Строковые операции
• Операторы проверки
Двоичное сравнение
Файлы
971
Регулярные выражения
• ^ (каретка) Начало строки
+ Символ соответствия
run-parts
***
Объем переменной, определение
Опции сценария, присвоение из командной строки
Процедуры сценариев , библиотека полезных определений и функций
Вторичное приглашение, $PS2
Проблемы безопасности
• nmap, карта сети / сканер портов
• sudo
972
• Таблица, основные операторы
• in list опущены
Семафор
Необходимость точки с запятой, когда ключевое слово do находится на первой строке цикла
• Доска Гальтона
• Скачки
• Life, Игра
• Стек Pushdown
• Сортировка вставляемого
973
source, выполнить сценарий или, в сценарии, импортировать файл
• Пример сценария
• Пример сценария
• Пример сценария
Специальные символы
Стек
• Определение
• Сравнение
• Размер (длина)
${#string}
• Управление
• strlen(), эквивалент
• Команда strings , поиск выводимых строк в двоичном файле или файле данных
• Извлечение Substring
974
${string:position}
${string:position:length}
Использование expr
• Удаление Substring
${var#Pattern}
${var##Pattern}
${var%Pattern}
${var%%Pattern}
• Замена Substring
${string/substring/replacement}
${string//substring/replacement}
${string/#substring/replacement}
${string/%substring/replacement}
Пример сценария
• Таблица управления string/substring и операторы извлечения
• Переменные в subshell
Символьные ссылки
975
Swap-файлы
***
Завершение клавишей TAB
Таблица подстановок, пример сценария
tail, Вывод в stdout строк от конца текстового файла
Терминалы
• setserial
• setterm
• stty
• tput
• wall
Команда test
Конструкции проверки
Операторы проверки
• -a Логическое И составного сравнения
• -e Наличие файла
976
• -o Логическое ИЛИ составного сравнения
• <= Меньше чем или равно, (сравнение целых чисел, в двойных круглых скобках)
• >= Больше чем или равно, (сравнение целых чисел, в двойных круглых скобках)
• || Логическое ИЛИ
• && Логическое И
Бинарное сравнение
Файл
Обработка текста и текстовых файлов
Время / Дата
Время для ввода
• С помощью read -t
• С помощью stty
• С помощью $TMOUT
977
• Захват возвращаемого значения функции, с помощью echo
• Блоки комментариев
• Псевдо-код
• rcs
• Переносимость сценария
Использование whatis
978
• Переменная Subshell, доступ из вне subshell
• Опции
• Варианты
• в конструкции let
typeset встроенная
• Опции
***
$UID, Число ID пользователя
Не инициализированные переменные
979
uniq, фильтр для удаления повторяющихся строк из отсортированного файла
Цикл until
until [ condition-is-true ]; do
***
Переменные
• Операции с массивами
• Присвоение
Пример сценария
Пример сценария
Пример сценария
• Внутренняя переменная Bash
• Окружение
• Косвенные ссылки
eval variable1=\$$variable2
Новая нотация
${!variable}
• Целое число
• Размер (длина)
${#var}
• Lvalue
• Управление и расширение
980
• Различия между именем и значением переменной
• Заключение в кавычки
в скобки test
• Не объявленность
• Не установленность
• Не вводимость
***
wait, приостановка выполнения сценария
• Синтаксис стиля Си
• Несколько условий
Избегание subshell
981
Символы пробелов, пропусков, табуляций, и перевода строк
• $IFS по умолчанию
• Ненадлежащее использование
• w
• whoame
• logname
Виджеты
Специальные символы групповых подстановок
• Звездочка *
• Конструкция In [list]
• Знак вопроса ?
Разделение слова
• Определение
Обертка, оболочка
***
xargs, Фильтр для группировки аргументов
• Фигурные скобки
• Опции
• Пробел , обработка
982
***
yes
• Эмуляция
***
-z Строка null
983