by William Shotts

У И Л Ь Я М Ш О Т Т С

КОМАНДНАЯ СТРОКА LINUX

П О Л Н ОЕ Р У К О В О Д С Т В О

2 - Е М Е Ж Д У Н А Р О Д Н О Е И З Д А Н И Е

ББК 32.973.2-018.2 УДК 004.451 Ш80

Ш80

Шоттс У. Командная строка Linux. Полное руководство. 2-е межд. изд. — СПб.: Питер, 2020. — 544 с.: ил. — (Серия «Для профессионалов»).

ISBN 978-5-4461-1430-6

Международный бестселлер «Командная строка Linux» поможет преодолеть путь от первых робких щелчков по клавишам до уверенного создания полноценных программ для последней версии bash — наиболее популярной командной оболочки Linux.

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

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

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)

ББК 32.973.2-018.2 УДК 004.451

Права на издание получены по соглашению с No Starch Press. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.

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

ISBN 978-1593279523 англ.

ISBN 978-5-4461-1430-6

© 2019 by William Shotts. The Linux Command Line, 2nd Edition: A Complete Introduction ISBN 978-1-59327-952-3, published by No Starch Press. © Перевод на русский язык ООО Издательство «Питер», 2020 © Издание на русском языке, оформление ООО Издательство

«Питер», 2020

© Серия «Для профессионалов», 2020

Краткое содержание

Об авторе ………………………………………………………………………………………………. 24

О научном редакторе……………………………………………………………………………… 25

Благодарности ………………………………………………………………………………………. 26

Введение ………………………………………………………………………………………………. 28

Часть I. КОМАНДНАЯ ОБОЛОЧКА

Глава 1. Что такое командная оболочка ………………………………………………………….. 36

Глава 2. Навигация ……………………………………………………………………………………… 41

Глава 3. Исследование системы …………………………………………………………………….. 47

Глава 4. Операции с файлами и каталогами …………………………………………………….. 59

Глава 5. Работа с командами …………………………………………………………………………. 76

Глава 6. Перенаправление ……………………………………………………………………………. 88

Глава 7. Взгляд на мир глазами командной оболочки ………………………………………. 102

Глава 8. Продвинутые приемы работы с клавиатурой ………………………………………. 114

Глава 9. Привилегии ………………………………………………………………………………….. 123

Глава 10. Процессы ……………………………………………………………………………………. 144

Часть II. ОКРУЖЕНИЕ И НАСТРОЙКА

Глава 11. Окружение …………………………………………………………………………………. 160

Глава 12. Плавное введение в vi ………………………………………………………………….. 173

Глава 13. Настройка приглашения к вводу …………………………………………………….. 192

6

Краткое содержание

Часть III. ТИПИЧНЫЕ ЗАДАЧИ И ОСНОВНЫЕ ИНСТРУМЕНТЫ

Глава 14. Управление пакетами …………………………………………………………………… 202

Глава 15. Устройства хранения ……………………………………………………………………. 212

Глава 16. Сети ………………………………………………………………………………………….. 231

Глава 17. Поиск файлов ……………………………………………………………………………… 247

Глава 18. Архивация и резервное копирование ………………………………………………. 264

Глава 19. Регулярные выражения ………………………………………………………………… 281

Глава 20. Обработка текста ………………………………………………………………………… 303

Глава 21. Форматирование вывода ………………………………………………………………. 344

Глава 22. Печать ……………………………………………………………………………………….. 365

Глава 23. Компиляция программ ………………………………………………………………….. 379

Часть IV. СЦЕНАРИИ КОМАНДНОЙ ОБОЛОЧКИ

Глава 24. Создание первого сценария командной оболочки ……………………………… 392

Глава 25. Начало проекта …………………………………………………………………………… 399

Глава 26. Проектирование сверху вниз …………………………………………………………. 410

Глава 27. Управление потоком выполнения: ветвление при помощи if ……………….. 420

Глава 28. Чтение ввода с клавиатуры …………………………………………………………… 437

Глава 29. Управление потоком выполнения: циклы while и until ………………………… 449

Глава 30. Поиск и устранение ошибок …………………………………………………………… 456

Глава 31. Управление потоком выполнения: ветвление с помощью case …………….. 470

Глава 32. Позиционные параметры ………………………………………………………………. 477

Глава 33. Управление потоком выполнения: цикл for ………………………………………. 491

Глава 34. Строки и числа ……………………………………………………………………………. 498

Глава 35. Массивы …………………………………………………………………………………….. 520

Глава 36. Экзотика …………………………………………………………………………………….. 530

Оглавление

Об авторе ………………………………………………………………………………………………. 24

О научном редакторе……………………………………………………………………………… 25

Благодарности ………………………………………………………………………………………. 26

К первому изданию ………………………………………………………………………………….. 26

Ко второму изданию …………………………………………………………………………………. 27

Введение ………………………………………………………………………………………………. 28

Зачем нужна командная строка? …………………………………………………………………. 29

О чем эта книга ……………………………………………………………………………………….. 29

Кому адресована эта книга ………………………………………………………………………… 30

Что дается в этой книге …………………………………………………………………………….. 31

Как читать эту книгу …………………………………………………………………………………. 31

Предварительные условия …………………………………………………………………… 32

Что нового во втором издании ……………………………………………………………………. 33

Ваши отзывы важны для нас! …………………………………………………………………….. 33

От издательства ………………………………………………………………………………………. 34

Часть I. КОМАНДНАЯ ОБОЛОЧКА ………………………………………………… 35

Глава 1. Что такое командная оболочка ………………………………………………….. 36

Эмуляторы терминалов……………………………………………………………………………… 36

Первые удары по клавишам ………………………………………………………………………. 36

История команд ………………………………………………………………………………….. 38

Управление курсором ………………………………………………………………………….. 38

Некоторые простые команды ……………………………………………………………………… 38

Завершение сеанса работы с терминалом …………………………………………………….. 39

Заключение …………………………………………………………………………………………….. 40

8

Оглавление

Глава 2. Навигация ………………………………………………………………………………… 41

Дерево каталогов файловой системы ………………………………………………………….. 41

Текущий рабочий каталог ………………………………………………………………………….. 42

Вывод содержимого каталога …………………………………………………………………….. 43

Смена текущего рабочего каталога …………………………………………………………….. 43

Абсолютные пути ……………………………………………………………………………….. 43

Относительные пути ……………………………………………………………………………. 44

Некоторые полезные сокращения …………………………………………………………. 46

Заключение …………………………………………………………………………………………….. 46

Глава 3. Исследование системы ……………………………………………………………… 47

Любопытные возможности ls ……………………………………………………………………… 47

Параметры и аргументы ………………………………………………………………………. 48

Пристальный взгляд на длинный формат ……………………………………………….. 49

Определение типов файлов командой file …………………………………………………….. 50

Просмотр содержимого файлов командой less ………………………………………………. 51

Обзорное путешествие ……………………………………………………………………………… 53

Символические ссылки ……………………………………………………………………………… 57

Жесткие ссылки ……………………………………………………………………………………….. 58

Заключение …………………………………………………………………………………………….. 58

Глава 4. Операции с файлами и каталогами ……………………………………………. 59

Групповые символы ………………………………………………………………………………….. 60

mkdir — создание каталогов ………………………………………………………………………. 62

cp — копирование файлов и каталогов ………………………………………………………… 63

Параметры команды cp и примеры ее использования ……………………………….. 63

mv — перемещение и переименование файлов …………………………………………….. 64

Параметры команды mv и примеры ее использования ………………………………. 64

rm — удаление файлов и каталогов ……………………………………………………………. 65

Параметры команды rm и примеры ее использования ………………………………. 65

ln — создание ссылок ……………………………………………………………………………….. 67

Жесткие ссылки …………………………………………………………………………………. 67

Символические ссылки ………………………………………………………………………… 68

Оглавление

9

Постройка песочницы ……………………………………………………………………………….. 68

Создание каталогов …………………………………………………………………………….. 68

Копирование файлов …………………………………………………………………………… 69

Перемещение и переименование файлов ……………………………………………….. 70

Создание жестких ссылок …………………………………………………………………….. 71

Создание символических ссылок …………………………………………………………… 72

Удаление файлов и каталогов ………………………………………………………………. 73

Заключение …………………………………………………………………………………………….. 75

Глава 5. Работа с командами ………………………………………………………………….. 76

Что такое команды? ………………………………………………………………………………….. 76

Идентификация команд …………………………………………………………………………….. 77

type — получение типа команды …………………………………………………………… 77

which — определение местоположения выполняемого файла ……………………. 77

Получение документации с описанием команд ……………………………………………… 78

help — получение справки для встроенных команд ………………………………….. 78

–help — вывод инструкции по использованию ………………………………………… 80

man — вывод страниц справочного руководства ……………………………………… 80

apropos — вывод списка подходящих команд ………………………………………….. 82

whatis — вывод очень краткого описания команды ………………………………….. 83

info — вывод записи из справочного руководства Info ………………………………. 83

README и другие файлы с описанием программ………………………………………. 85

Создание собственных команд с помощью alias …………………………………………….. 85

Заключение …………………………………………………………………………………………….. 87

Глава 6. Перенаправление ……………………………………………………………………… 88

Стандартный ввод, вывод и вывод ошибок …………………………………………………… 88

Перенаправление стандартного вывода ………………………………………………………. 89

Перенаправление стандартного вывода ошибок ……………………………………………. 91

Перенаправление стандартного вывода и стандартного вывода ошибок в один файл ………………………………………………………………………………………. 91

Удаление нежелательного вывода ………………………………………………………… 92

Перенаправление стандартного ввода …………………………………………………………. 93

cat — объединение файлов ………………………………………………………………….. 93

10

Оглавление

Конвейеры………………………………………………………………………………………………. 95

Фильтры ……………………………………………………………………………………………. 95

uniq — поиск или удаление повторяющихся строк ……………………………………. 96

wc — вывод числа строк, слов и байтов …………………………………………………. 97

grep — поиск строк, соответствующих шаблону ………………………………………. 97

head/tail — вывод первых/последних строк из файлов ……………………………… 98

tee — чтение со стандартного ввода и запись в стандартный вывод и в файлы ………………………………………………………………………………… 99

Заключение …………………………………………………………………………………………… 101

Глава 7. Взгляд на мир глазами командной оболочки ……………………………. 102

Подстановка ………………………………………………………………………………………….. 102

Подстановка путей ……………………………………………………………………………. 103

Подстановка тильды ………………………………………………………………………….. 104

Подстановка результатов арифметических выражений …………………………… 105

Подстановка фигурных скобок ……………………………………………………………. 106

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

Подстановка команд ………………………………………………………………………….. 108

Экранирование ………………………………………………………………………………………. 109

Двойные кавычки ……………………………………………………………………………… 109

Одиночные кавычки ………………………………………………………………………….. 111

Экранирование символов …………………………………………………………………… 111

Управляющие последовательности………………………………………………………. 112

Заключение …………………………………………………………………………………………… 113

Глава 8. Продвинутые приемы работы с клавиатурой ……………………………. 114

Редактирование командной строки ……………………………………………………………. 114

Перемещение курсора ……………………………………………………………………….. 115

Изменение текста ……………………………………………………………………………… 115

Вырезание и вставка (удаление и возврат) текста ………………………………….. 117

Дополнение …………………………………………………………………………………………… 117

Использование истории …………………………………………………………………………… 119

Поиск в истории ……………………………………………………………………………….. 119

Подстановка записей истории …………………………………………………………….. 121

Заключение …………………………………………………………………………………………… 122

Оглавление

11

Глава 9. Привилегии …………………………………………………………………………….. 123

Владельцы, члены группы и все остальные ………………………………………………… 124

Чтение, запись и выполнение …………………………………………………………………… 126

chmod — изменение режима доступа к файлу ……………………………………….. 128

Установка режима доступа к файлу с помощью графического интерфейса ………………………………………………………………….. 131

umask — определение разрешений доступа к файлам по умолчанию ………… 132

Некоторые специальные разрешения …………………………………………………… 134

Изменение идентичности …………………………………………………………………………. 135

su — запуск командной оболочки с подстановкой идентификаторов пользователя и группы ………………………………………………………………………. 136

sudo — выполнение команды от имени другого пользователя ………………….. 137

chown — изменение владельца и группы файла …………………………………….. 139

chgrp — изменение группы файла ……………………………………………………….. 140

Использование привилегий ……………………………………………………………………… 140

Изменение своего пароля ………………………………………………………………………… 142

Заключение …………………………………………………………………………………………… 143

Глава 10. Процессы ……………………………………………………………………………… 144

Как действует процесс …………………………………………………………………………….. 144

Просмотр списка процессов ……………………………………………………………………… 145

Просмотр состояния процессов в динамике с помощью top ……………………… 148

Управление процессами ………………………………………………………………………….. 150

Прерывание процесса ……………………………………………………………………….. 151

Перевод процессов в фоновый режим ………………………………………………….. 151

Возврат процесса на передний план…………………………………………………….. 152

Приостановка процесса ……………………………………………………………………… 152

Сигналы ……………………………………………………………………………………………….. 153

Посылка сигналов процессам командой kill ……………………………………………. 154

Посылка сигналов нескольким процессам с помощью killall ……………………… 156

Остановка системы …………………………………………………………………………………. 157

Другие команды управления процессами ……………………………………………………. 158

Заключение …………………………………………………………………………………………… 158

12

Оглавление

Часть II. ОКРУЖЕНИЕ И НАСТРОЙКА …………………………………………. 159

Глава 11. Окружение ……………………………………………………………………………. 160

Что хранится в окружении? ……………………………………………………………………… 160

Исследование окружения …………………………………………………………………… 161

Некоторые интересные переменные …………………………………………………….. 162

Как устанавливается окружение? ……………………………………………………………… 163

Что находится в файлах запуска? ………………………………………………………… 165

Изменение окружения …………………………………………………………………………….. 167

Какие файлы следует изменять? …………………………………………………………. 167

Текстовые редакторы ………………………………………………………………………… 167

Использование текстового редактора …………………………………………………… 168

Активация изменений ………………………………………………………………………… 172

Заключение …………………………………………………………………………………………… 172

Глава 12. Плавное введение в vi …………………………………………………………… 173

Зачем осваивать vi …………………………………………………………………………………. 173

Немного предыстории …………………………………………………………………………….. 174

Запуск и завершение vi …………………………………………………………………………… 174

Режимы редактирования …………………………………………………………………………. 176

Переход в режим вставки …………………………………………………………………… 177

Сохранение изменений ………………………………………………………………………. 177

Перемещение курсора …………………………………………………………………………….. 178

Основы редактирования ………………………………………………………………………….. 179

Добавление текста в конец ………………………………………………………………… 179

Вставка строки …………………………………………………………………………………. 180

Удаление текста ……………………………………………………………………………….. 181

Вырезание, копирование и вставка текста…………………………………………….. 182

Объединение строк …………………………………………………………………………… 183

Поиск и замена ………………………………………………………………………………………. 184

Поиск в пределах строки ……………………………………………………………………. 184

Поиск во всем файле …………………………………………………………………………. 184

Глобальный поиск и замена ……………………………………………………………….. 185

Оглавление

13

Редактирование нескольких файлов ………………………………………………………….. 186

Переключение между файлами …………………………………………………………… 187

Открытие дополнительных файлов для редактирования …………………………. 188

Копирование содержимого из одного файла в другой …………………………….. 188

Вставка целого файла в другой файл …………………………………………………… 189

Сохранение результатов работы ……………………………………………………………….. 190

Заключение …………………………………………………………………………………………… 191

Глава 13. Настройка приглашения к вводу ……………………………………………. 192

Устройство строки приглашения к вводу ……………………………………………………. 192

Альтернативные варианты оформления приглашения ………………………………….. 194

Добавление цвета ………………………………………………………………………………….. 196

Перемещение курсора …………………………………………………………………………….. 198

Сохранение определения приглашения ……………………………………………………… 200

Заключение …………………………………………………………………………………………… 200

Часть III. ТИПИЧНЫЕ ЗАДАЧИ И ОСНОВНЫЕ ИНСТРУМЕНТЫ ………. 201

Глава 14. Управление пакетами ……………………………………………………………. 202

Системы пакетов ……………………………………………………………………………………. 203

Как действует система пакетов …………………………………………………………………. 203

Файлы пакетов …………………………………………………………………………………. 203

Репозитории …………………………………………………………………………………….. 204

Зависимости …………………………………………………………………………………….. 205

Высоко- и низкоуровневые инструменты управления пакетами ………………… 205

Типичные задачи управления пакетами……………………………………………………… 206

Поиск пакета в репозитории ………………………………………………………………. 206

Установка пакета из репозитория ………………………………………………………… 206

Установка пакета из файла пакета ………………………………………………………. 207

Удаление пакета ………………………………………………………………………………. 207

Обновление пакетов из репозитория ……………………………………………………. 208

Обновление пакета из файла пакета ……………………………………………………. 208

Список установленных пакетов …………………………………………………………… 209

Определение, установлен ли пакет ……………………………………………………… 209

14

Оглавление

Вывод информации об установленном пакете ……………………………………….. 209

Поиск пакета по установленному файлу ……………………………………………….. 210

Заключение …………………………………………………………………………………………… 210

Глава 15. Устройства хранения …………………………………………………………….. 212

Монтирование и размонтирование устройств хранения ………………………………… 213

Просмотр списка смонтированных файловых систем ………………………………. 214

Определение названий устройств………………………………………………………… 218

Создание новых файловых систем …………………………………………………………….. 221

Управление разделами с помощью fdisk ……………………………………………….. 221

Создание новой файловой системы с помощью mkfs ………………………………. 224

Проверка и восстановление файловой системы …………………………………………… 225

Непосредственное перемещение данных между устройствами ………………………. 226

Создание образа компакт-диска ………………………………………………………….. 226

Создание образа-копии компакт-диска …………………………………………………. 227

Создание образа из коллекции файлов ………………………………………………… 227

Запись образа компакт-диска …………………………………………………………………… 228

Непосредственное монтирование файла ISO-образа ………………………………. 228

Очистка перезаписываемых компакт-дисков …………………………………………. 228

Запись образа ………………………………………………………………………………….. 228

Заключительное замечание ……………………………………………………………………… 229

Дополнительные сведения ………………………………………………………………………. 229

Глава 16. Сети ……………………………………………………………………………………… 231

Исследование и мониторинг сети ……………………………………………………………… 232

ping ………………………………………………………………………………………………… 232

traceroute ………………………………………………………………………………………… 233

ip……………………………………………………………………………………………………. 234

netstat …………………………………………………………………………………………….. 235

Передача файлов по сети ………………………………………………………………………… 237

ftp ………………………………………………………………………………………………….. 237

lftp — более удачная версия ftp…………………………………………………………… 239

wget ……………………………………………………………………………………………….. 239

Оглавление

15

Безопасные взаимодействия с удаленными узлами ………………………………………. 240

ssh …………………………………………………………………………………………………. 240

scp и sftp …………………………………………………………………………………………. 245

Заключение …………………………………………………………………………………………… 246

Глава 17. Поиск файлов ……………………………………………………………………….. 247

locate — простой способ поиска файлов …………………………………………………….. 247

find — сложный способ поиска файлов ………………………………………………………. 249

Проверки ………………………………………………………………………………………… 250

Операторы ………………………………………………………………………………………. 253

Предопределенные операции ……………………………………………………………… 255

Операции, определяемые пользователем ……………………………………………… 257

Увеличение эффективности ……………………………………………………………….. 258

xargs ………………………………………………………………………………………………. 259

Возвращаемся в песочницу ………………………………………………………………… 260

Параметры ………………………………………………………………………………………. 262

Заключение …………………………………………………………………………………………… 263

Глава 18. Архивация и резервное копирование …………………………………….. 264

Сжатие файлов ………………………………………………………………………………………. 264

gzip ………………………………………………………………………………………………… 265

bzip2 ………………………………………………………………………………………………. 268

Архивирование файлов …………………………………………………………………………… 268

tar ………………………………………………………………………………………………….. 269

zip ………………………………………………………………………………………………….. 274

Синхронизация файлов и каталогов ………………………………………………………….. 276

Использование rsync для копирования по сети ………………………………………. 279

Заключение …………………………………………………………………………………………… 280

Глава 19. Регулярные выражения ………………………………………………………… 281

Что такое регулярные выражения? ……………………………………………………………. 281

grep ……………………………………………………………………………………………………… 282

Метасимволы и литералы ………………………………………………………………………… 284

Любой символ ……………………………………………………………………………………….. 284

16

Оглавление

Якоря …………………………………………………………………………………………………… 285

Выражения в квадратных скобках и классы символов …………………………………… 286

Отрицание ………………………………………………………………………………………. 287

Традиционные диапазоны символов …………………………………………………….. 287

Классы символов POSIX …………………………………………………………………………… 288

Простые и расширенные регулярные выражения POSIX ……………………………….. 291

Чередование …………………………………………………………………………………………. 293

Квантификаторы ……………………………………………………………………………………. 295

? — совпадение с элементом ноль или один раз …………………………………….. 295

  • — совпадение с элементом ноль или более раз …………………………………… 295
  • — совпадение с элементом один или более раз ………………………………….. 296

{ } — совпадение с элементом определенное число раз ………………………….. 297

Практические примеры применения регулярных выражений …………………………. 298

Проверка списка телефонов с помощью grep ………………………………………… 298

Поиск необычных имен файлов с помощью find …………………………………….. 299

Поиск файлов с помощью locate ………………………………………………………….. 299

Поиск текста в less и vim ……………………………………………………………………. 300

Заключение …………………………………………………………………………………………… 301

Глава 20. Обработка текста ………………………………………………………………….. 303

Области применения текста …………………………………………………………………….. 304

Документы ………………………………………………………………………………………. 304

Веб-страницы …………………………………………………………………………………… 304

Электронная почта ……………………………………………………………………………. 304

Вывод на принтер …………………………………………………………………………….. 304

Исходный код программ …………………………………………………………………….. 305

А вот и наши старые знакомые! ………………………………………………………………… 305

cat ……………………………………………………………………………………………………….. 305

sort…………………………………………………………………………………………………. 307

uniq — выявление или удаление повторяющихся строк …………………………… 314

Нарезка и перетасовка текста ………………………………………………………………….. 316

cut — удаление фрагментов из всех строк в файлах……………………………….. 316

paste — слияние строк из файлов ……………………………………………………….. 319

join — объединение строк из двух файлов по общему полю …………………….. 321

Оглавление

17

Сравнение текста …………………………………………………………………………………… 323

comm — построчное сравнение двух сортированных файлов …………………… 323

diff — построчное сравнение файлов …………………………………………………… 324

patch — применение diff-файла к оригиналу ………………………………………….. 327

Редактирование на лету ………………………………………………………………………….. 328

tr — перекодирование или удаление символов ………………………………………. 328

sed — потоковый редактор для фильтрации и преобразования текста ………. 331

aspell — интерактивная проверка орфографии ………………………………………. 339

Заключение …………………………………………………………………………………………… 343

Дополнительное задание …………………………………………………………………………. 343

Глава 21. Форматирование вывода ……………………………………………………….. 344

Инструменты простого форматирования …………………………………………………….. 344

nl — нумерация строк ………………………………………………………………………… 345

fold — перенос строк после указанной длины ………………………………………… 348

fmt — простое форматирование текста ………………………………………………… 349

pr — форматирование текста для печати ……………………………………………… 352

printf — форматирование и вывод данных …………………………………………….. 353

Системы форматирования документов ……………………………………………………….. 357

groff ……………………………………………………………………………………………….. 358

Заключение …………………………………………………………………………………………… 364

Глава 22. Печать ………………………………………………………………………………….. 365

Краткая история поддержки печати…………………………………………………………… 365

Печать в ночное время ………………………………………………………………………. 366

Символьные принтеры ………………………………………………………………………. 366

Графические принтеры ……………………………………………………………………… 367

Печать в Linux ……………………………………………………………………………………….. 369

Подготовка файлов к печати ……………………………………………………………………. 369

pr — преобразование текстовых файлов для печати ………………………………. 369

Отправка задания печати на принтер ………………………………………………………… 371

lpr — печать файлов (в стиле Berkeley) ………………………………………………… 371

lp — печать файлов (в стиле System V) ………………………………………………… 372

Еще одна возможность: a2ps ………………………………………………………………. 373

18

Оглавление

Наблюдение за заданиями печати и управление ими …………………………………… 376

lpstat — вывод информации о состоянии принтера …………………………………. 376

lpq — вывод информации о состоянии очереди печати …………………………… 377

lprm и cancel — отмена заданий печати ………………………………………………… 378

Заключение …………………………………………………………………………………………… 378

Глава 23. Компиляция программ …………………………………………………………… 379

Что такое компиляция? …………………………………………………………………………… 380

Все ли программы компилируются? ……………………………………………………… 381

Компиляция программ на C ……………………………………………………………………… 382

Получение исходного кода …………………………………………………………………. 382

Исследование дерева исходных текстов ……………………………………………….. 384

Сборка программ ………………………………………………………………………………. 386

Установка программ ………………………………………………………………………….. 390

Заключение …………………………………………………………………………………………… 390

Часть IV. СЦЕНАРИИ КОМАНДНОЙ ОБОЛОЧКИ …………………………… 391

Глава 24. Создание первого сценария командной оболочки …………………… 392

Что такое сценарии командной оболочки …………………………………………………… 392

Как написать сценарий командной оболочки ………………………………………………. 393

Формат файла сценария …………………………………………………………………….. 393

Разрешения на выполнение ……………………………………………………………….. 394

Местоположение файла сценария ……………………………………………………….. 394

Выбор местоположения для сценариев ………………………………………………… 396

Дополнительные хитрости по оформлению ………………………………………………… 397

Длинные имена параметров ……………………………………………………………….. 397

Отступы и продолжения строк ……………………………………………………………. 397

Заключение …………………………………………………………………………………………… 398

Глава 25. Начало проекта …………………………………………………………………….. 399

Этап первый: минимальный документ ……………………………………………………….. 399

Этап второй: добавление некоторых данных ………………………………………………. 401

Переменные и константы ………………………………………………………………………… 402

Присваивание значений переменным и константам ………………………………… 405

Оглавление

19

Встроенные документы ……………………………………………………………………………. 407

Заключение …………………………………………………………………………………………… 409

Глава 26. Проектирование сверху вниз …………………………………………………. 410

Функции командной оболочки ………………………………………………………………….. 411

Локальные переменные …………………………………………………………………………… 414

Постоянное опробование сценария …………………………………………………………… 416

Заключение …………………………………………………………………………………………… 419

Глава 27. Управление потоком выполнения: ветвление при помощи if ……………………………………………………………………… 420

Инструкция if …………………………………………………………………………………………. 420

Код завершения …………………………………………………………………………………….. 421

Команда test ………………………………………………………………………………………….. 423

Выражения для проверки файлов ……………………………………………………….. 423

Выражения для проверки строк …………………………………………………………… 426

Выражения для проверки целых чисел …………………………………………………. 428

Более современная версия команды test ……………………………………………………. 429

(( )) — для проверки целых чисел ……………………………………………………….. 430

Объединение выражений ………………………………………………………………………… 431

Операторы управления: еще один способ ветвления ……………………………………. 434

Заключение …………………………………………………………………………………………… 435

Глава 28. Чтение ввода с клавиатуры……………………………………………………. 437

read — чтение значений со стандартного ввода ………………………………………….. 438

Параметры ………………………………………………………………………………………. 440

Выделение полей в строке ввода с помощью IFS ……………………………………. 442

Проверка ввода ……………………………………………………………………………………… 444

Меню ……………………………………………………………………………………………………. 446

Заключение …………………………………………………………………………………………… 448

Дополнительные сведения ………………………………………………………………………. 448

Глава 29. Управление потоком выполнения: циклы while и until ……………. 449

Циклы ………………………………………………………………………………………………….. 449

while ……………………………………………………………………………………………….. 450

20

Оглавление

Прерывание цикла………………………………………………………………………………….. 452

until ………………………………………………………………………………………………… 454

Чтение файлов в циклах ………………………………………………………………………….. 454

Заключение …………………………………………………………………………………………… 455

Глава 30. Поиск и устранение ошибок …………………………………………………… 456

Синтаксические ошибки ………………………………………………………………………….. 456

Отсутствующие кавычки …………………………………………………………………….. 457

Отсутствующие или неожиданные лексемы …………………………………………… 458

Непредвиденная подстановка …………………………………………………………….. 458

Логические ошибки ………………………………………………………………………………… 460

Защитное программирование ……………………………………………………………… 460

Будьте внимательны к именам файлов …………………………………………………. 462

Проверка ввода ………………………………………………………………………………… 463

Тестирование ………………………………………………………………………………………… 464

Комплекты тестов …………………………………………………………………………….. 465

Отладка ………………………………………………………………………………………………… 465

Поиск проблемной области ………………………………………………………………… 465

Трассировка …………………………………………………………………………………….. 466

Исследование значений в процессе выполнения ……………………………………. 468

Заключение …………………………………………………………………………………………… 469

Глава 31. Управление потоком выполнения: ветвление с помощью case …………………………………………………………………… 470

Команда case …………………………………………………………………………………………. 470

Шаблоны …………………………………………………………………………………………. 472

Выполнение нескольких вариантов ……………………………………………………… 474

Заключение …………………………………………………………………………………………… 476

Глава 32. Позиционные параметры ………………………………………………………. 477

Доступ к командной строке ……………………………………………………………………… 477

Определение числа аргументов …………………………………………………………… 478

shift — доступ к множеству аргументов ………………………………………………… 479

Простые приложения ………………………………………………………………………… 480

Оглавление

21

Использование позиционных параметров в функциях …………………………….. 481

Обработка позиционных параметров скопом ………………………………………………. 482

Более сложное приложение …………………………………………………………………….. 484

Заключение …………………………………………………………………………………………… 487

Глава 33. Управление потоком выполнения: цикл for ……………………………. 491

for: традиционная форма …………………………………………………………………………. 491

for: форма в стиле языка C ………………………………………………………………………. 494

Заключение …………………………………………………………………………………………… 496

Глава 34. Строки и числа ……………………………………………………………………… 498

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

Простые параметры ………………………………………………………………………….. 499

Подстановка пустых переменных ………………………………………………………… 499

Получение имен переменных ……………………………………………………………… 501

Операции со строками ……………………………………………………………………….. 502

Преобразование регистра символов …………………………………………………….. 505

Вычисление и подстановка арифметических выражений …………………………. 507

Основание системы счисления ……………………………………………………………. 508

Унарные операторы ………………………………………………………………………….. 508

Простая арифметика …………………………………………………………………………. 508

Присваивание ………………………………………………………………………………….. 510

Битовые операции …………………………………………………………………………….. 512

Логические операторы ………………………………………………………………………. 513

bc — язык калькулятора для вычислений с произвольной точностью ……………… 516

Применение bc …………………………………………………………………………………. 517

Пример сценария ……………………………………………………………………………… 518

Заключение …………………………………………………………………………………………… 519

Дополнительные сведения ………………………………………………………………………. 519

  1. Массивы …………………………………………………………………………………………. 520

Что такое массивы? ………………………………………………………………………………… 520

Создание массива …………………………………………………………………………………… 521

Присваивание значений массиву ………………………………………………………………. 521

22

Оглавление

Доступ к элементам массива…………………………………………………………………….. 522

Операции с массивами ……………………………………………………………………………. 524

Вывод содержимого всего массива ………………………………………………………. 524

Определение числа элементов в массиве ……………………………………………… 525

Поиск используемых индексов…………………………………………………………….. 526

Добавление элементов в конец массива ……………………………………………….. 526

Сортировка массива ………………………………………………………………………….. 526

Удаление массива …………………………………………………………………………….. 527

Ассоциативные массивы ………………………………………………………………………….. 528

Заключение …………………………………………………………………………………………… 529

Глава 36. Экзотика ……………………………………………………………………………….. 530

Группы команд и подоболочки …………………………………………………………………. 530

Подстановка процессов …………………………………………………………………………… 534

Ловушки ……………………………………………………………………………………………….. 536

Асинхронное выполнение с командой wait ………………………………………………….. 540

Именованные каналы ……………………………………………………………………………… 541

Создание именованного канала …………………………………………………………… 542

Использование именованных каналов ………………………………………………….. 542

Заключение …………………………………………………………………………………………… 543

Посвящается Карен

Об авторе

Уильям Шоттс (William Shotts) — профессиональный разработчик программного обеспечения с более чем 30-летним стажем, который уже больше 20 лет активно пользуется операционной системой Linux. Имеет богатый опыт разработки про- граммного обеспечения, технической поддержки, контроля качества и написания документации. Также является создателем LinuxCommand.org, образовательного и информационно-просветительского сайта, посвященного Linux, где публикуют- ся новости, обзоры и оказывается поддержка пользующимся командной строкой Linux.

О научном редакторе

Жорди Гутьеррес Эрмосо (Jordi Gutiérrez Hermoso) — программист, математик и сторонник этичного хакерства. С 2002 года пользуется исключительно Debian GNU/Linux не только дома, но и на работе. Жорди участвует в разработке GNU Octave, бесплатной вычислительной среды, во многом совместимой с Matlab, а также Mercurial, распределенной системы управления версиями. Увлекается чистой и прикладной математикой, катанием на коньках, плаванием и вязанием. В последнее время много думает о проблемах выброса парниковых газов и уча- ствует в акциях по спасению носорогов.

Благодарности

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

К первому изданию

В первую очередь хочу поблагодарить тех, кто вдохновил меня:

Дженни Уотсон (Jenny Watson), рецензента издательства Wiley Publishing, — она первая предложила написать книгу о языке сценариев командной оболочки.

Джона Дворака (John C. Dvorak), известного колумниста и большого эрудита, — в эпизоде своего видеоподкаста «Cranky Geeks» он дал великолепный посыл твор- ческому процессу: «Черт! Пишите по 200 слов в день в течение года, и получите роман». Следуя его совету, я писал по одной странице в день до тех пор, пока не получил книгу.

Дмитрия Попова (Dmitri Popov), написавшего статью в журнале «Free Software Magazine» под названием «Creating a book template with Writer», — именно она вдохновила меня использовать OpenOffice.org Writer (позднее переименованный в LibreOffice Writer) для набора текста. Результат получился изумительным.

Далее, спасибо добровольцам, которые помогли выпустить оригинал, свободно распространяемую версию этой книги (доступна на LinuxCommand.org):

Марка Полески (Mark Polesky), который выполнил большую редакторскую рабо- ту и проверил текст книги.

Джесси Беккер (Jesse Becker), Томаш Хщонович (Tomasz Chrzczonowicz), Майкл Левин (Michael Levin) и Спенс Майнер (Spence Miner) также проверили отдель- ные фрагменты книги и представили свои рецензии.

Ко второму изданию

27

Карен М. Шоттс (Karen M. Shotts) много часов посвятила правке моей оригиналь- ной рукописи.

Наконец, спасибо добрым людям из No Starch Press, которые серьезно потруди- лись над созданием коммерческой версии книги:

Серене Янг (Serena Yang), управляющей производством; Киту Фанчеру (Keith Fancher), моему редактору; и остальным сотрудникам No Starch Press.

Ко второму изданию

Особое спасибо добровольцам, приславшими свои ценные отзывы о свободно распространяемой версии книги и ко второму изданию, выпущенному No Starch Press: Адриану Арпидесу (Adrian Arpidez), Ху Бо (Hu Bo), Стиву Брэггу (Steve Bragg), Джону Барнсу (John Burns), Паоло Казати (Paolo Casati), Хериберто Кан- ту (Heriberto Cantú), Энцо Кардиналу (Enzo Cardinal), Ликсину Дуану (Lixin Duan), Джошуа Эскамилле (Joshua Escamilla), Брюсу Фаулеру (Bruce Fowler), Двину Харперу (Devin Harper), Йоргену Хейтманну (Jørgen Heitmann), Джона- тану Джонсу (Jonathan Jones), Сунил Джоши (Sunil Joshi), Ма Джун (Ma Jun), Эрику Каммереру (Eric Kammerer), Сету Кингу (Seth King), Крису Найту (Chris Knight), Ярославу Колосовскому (Jaroslaw Kolosowski), Джиму Ковачу (Jim Kovacs), Бартломею Майку (Bartłomiej Majka), Башару Мари (Bashar Maree), Френку Мактипсу (Frank McTipps), Майку О’Доннеллу (Mike O’Donnell), Джа- стину Пейджу (Justin Page), Парвизу Расулипуру (Parviz Rasoulipour), Уолдо Рибейро (Waldo Ribeiro), Нику Роузу (Nick Rose), Сатею Кумару Саху (Satej Kumar Sahu), Михаилу Сизову (Mikhail Sizov), Бену Слатеру (Ben Slater), Пик- лсу Спиллу (Pickles Spill), Габриэлю Штутцману (Gabriel Stutzman), Франче- ско Турко (Francesco Turco), Вольфраму Вольпи (Wolfram Volpi), Боянгу Вану (Boyang Wang), Вальтеру Вежбе (Valter Wierzba) и Кристиану Ветриху (Christian Wuethrich).

В работе над вторым изданием, выпущенным издательством No Starch Press, уча- ствовали: Серена Янг (Serena Yang), заведующая производством, и Мег Снирин- гер (Meg Sneeringer), выпускающий редактор; Крис Кливленд (Chris Cleveland), редактор; Жорди Гутьеррес Эрмосо (Jordi Gutiérrez Hermoso), научный редактор; и Ким Вимпсетт (Kim Wimpsett), редактор-корректор.

И наконец, спасибо читателям LinuxCommand.org, приславшим мне так много до- брых писем. Их поддержка помогла поверить, что я действительно чего-то стою!

Введение

Я хочу поведать вам историю. Нет, не о том, как в 1991-м Линус Торвальдс создал первую версию ядра Linux. Эту историю вы прочитаете в других книгах о Linux. Я не стану рассказывать вам, как несколькими годами ранее Ричард Столлман запустил проект GNU по созданию свободной Unix-подобной операционной си- стемы. И эту апокрифическую историю можно узнать из других книг о Linux.

Но я хочу рассказать, как можно вернуть управление своим компьютером.

В конце 1970-х, когда я, будучи студентом колледжа, только начинал заниматься компьютерами, еще продолжалась IT-революция. Появление микропроцессора сделало возможным приобретение компьютеров простыми людьми, такими как вы или я. Сегодня многим трудно представить себе мир, в котором компьютеры (огромные вычислительные машины) принадлежали только крупным компаниям и правительствам, а обычным людям они были недоступны.

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

Сейчас принято употреблять термин «свобода» в отношении Linux, но я не ду- маю, что большинство знает, что в действительности подразумевается под свобо- дой. Свобода — это возможность самому решать, что будет делать ваш компьютер, и единственный путь к достижению этой свободы — знание того, что он делает.

О чем эта книга

29

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

Зачем нужна командная строка?

Обращали ли вы внимание, что в фильмах, когда «суперхакер» — парень, спо- собный за 30 секунд взломать суперзащищенную военную систему, — садится за компьютер, он никогда не берется за мышь? Создатели фильмов инстинктивно понимают, что мы, будучи людьми, можем сделать за компьютером что-то дей- ствительно стоящее, только вводя команды с клавиатуры.

Большинство современных пользователей компьютеров знакомы только с гра- фическим интерфейсом (Graphical User Interface, GUI) и верят производителям и экспертам, что интерфейс командной строки (Command Line Interface, CLI) — это раннее средневековье. Открою тайну: интерфейс командной строки — удоб- ный и выразительный способ общения с компьютером, во многом напоминающий способ письменного общения между людьми. Как однажды было подмечено, «гра- фический пользовательский интерфейс делает простые задачи еще проще, а ин- терфейс командной строки делает сложные задачи выполнимыми», — это выска- зывание остается истинным и по сей день.

Поскольку операционная система Linux создавалась на основе семейства опера- ционных систем Unix, она унаследовала богатое разнообразие инструментов ко- мандной строки Unix. ОС Unix заняла ведущее положение в начале 1980-х (хотя появилась на десяток лет раньше), еще до повсеместного распространения графи- ческого интерфейса, и, соответственно, широко использовала интерфейс команд- ной строки. Фактически одной из основных причин, по которой первопроходцы Linux выбрали эту ОС, а не, скажем, Windows NT, была мощная поддержка интер- фейса командной строки, который «делает сложные задачи выполнимыми».

О чем эта книга

Эта книга представляет обширный обзор «жизни» в командной строке Linux. В отличие от других книг, посвященных одной программе, такой как командный интерпретатор bash, в этой книге я попытаюсь рассказать, как поладить с интер- фейсом командной строки в более широком аспекте. Как он работает? Что можно сделать с его помощью? Как лучше его использовать?

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

30

Введение

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

Эта книга исключительно о Linux. Многие книги пытаются расширить свою целевую аудиторию, включая в содержание другие платформы, такие как Unix и macOS. По этой причине в них рассматриваются лишь общие темы. Эта книга, напротив, посвящена исключительно современным дистрибутивам Linux. И хотя девяносто пять процентов сведений будут полезны пользователями других Unix- подобных систем, основной целевой аудиторией этой книги являются пользовате- ли командной строки современных версий Linux.

Кому адресована эта книга

Эта книга адресована новым пользователям Linux, мигрирующим с других плат- форм. Весьма вероятно, что вы — «опытный пользователь» определенной версии Microsoft Windows. Возможно, руководитель дал вам задание освоить админи- стрирование Linux-сервера или вы вступаете в новый захватывающий мир одно- платных компьютеров (Single Board Computers, SBC), таких как Raspberry Pi. А может быть, вы обычный пользователь, уставший от нескончаемых проблем безопасности и решивший попробовать Linux. Кем бы вы ни были, здесь вас ждет радушный прием.

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

С другой стороны, изучение командной строки Linux чрезвычайно полезно. Если вы считаете себя «опытным пользователем», подождите немного, и вы узнаете, что такое действительно опытный пользователь. Кроме того, в отличие от других навыков работы с компьютером, умение работать в командной строке еще долго будет оставаться полезным. Навыки, приобретенные сегодня, останутся полезны- ми и через 10 лет. Командная строка выдержала испытание временем.

Если у вас нет опыта программирования — не волнуйтесь, мы поможем в его при- обретении.

Как читать эту книгу

31

Что дается в этой книге

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

Цель данной книги — познакомить вас с идеологией Unix, которая отличается от идеологии Windows. По пути мы иногда будем отклоняться в сторону, чтобы по- пытаться понять, почему то или иное работает именно так, а не иначе. Linux — это не просто программное обеспечение, это также часть обширной культуры Unix, имеющей свой язык и историю. Здесь я мог бы добавить еще пару напыщенных фраз. Но воздержусь от этого.

Книга делится на четыре части, каждая из которых охватывает определенный аспект владения командной строкой:

 Часть I «Командная оболочка» вводит в курс основ языка командной строки: структура команд, приемы навигации в файловой системе, редактирование ко- мандной строки и поиск справочной информации с описанием команд.

 Часть II «Окружение и настройка» посвящена редактированию конфигура-

ционных файлов, управляющих работой командной строки.

 Часть III «Типичные задачи и основные инструменты» исследует множество типовых задач, часто выполняемых в командной строке. Unix-подобные опе- рационные системы, такие как Linux, имеют множество «классических» про- грамм командной строки, помогающих выполнять различные операции с дан- ными.

 Часть IV «Сценарии командной оболочки» знакомит с программированием на языке командной оболочки, который, по общему мнению, обладает не слиш- ком широкими возможностями, но прост в изучении и позволяет автоматизи- ровать многие вычислительные задачи. Изучая программирование на языке командной оболочки, вы познакомитесь с идеями, которые сможете применять в других языках программирования.

Как читать эту книгу

Начните с начала и последовательно двигайтесь в направлении последней страницы. Это не справочник; книга действительно имеет начало, середину и конец.

32

Введение

ПОЧЕМУ Я НЕ ИСПОЛЬЗУЮ НАЗВАНИЕ «GNU/LINUX»

В некоторых кругах операционную систему Linux принято называть «операционной системой GNU/Linux». Проблема в том, что не существует абсолютно правильного названия, так как эта система создавалась множеством разных людей по всему миру. С технической точки зрения Linux — это название ядра операционной систе- мы, и ничего более. Ядро играет важную роль, конечно, потому что обеспечивает работу операционной системы, но одного его, разумеется, недостаточно.

Ричард Столлман (Richard Stallman), гениальный философ, основатель движения свободного программного обеспечения (Free Software), фонда свободных программ (Free Software Foundation) и проекта GNU, автор первой версии компилятора GNU C Compiler (GCC) и общественной лицензии GNU General Public License (GPL) и прочая и прочая, настаивает на названии «GNU/Linux» как отражающем вклад проекта GNU. Но даже при том, что проект GNU предшествовал появлению ядра Linux и вклад проекта заслуживает самой высокой оценки, использование его назва- ния в названии операционной системы можно расценивать как несправедливость в отношении всех остальных. Кроме того, я считаю, что технически более точным было бы название «Linux/GNU», потому что сначала загружается ядро, а потом все остальное выполняется на его основе.

Под общепринятым названием «Linux» подразумевается ядро и все остальное бесплатное и открытое программное обеспечение, которое можно найти в типич- ном дистрибутиве Linux, — то есть вся экосистема Linux, а не только компоненты GNU. Кроме того, на рынке операционных систем чаще отдается предпочтение названиям из одного слова, например: DOS, Windows, macOS, Solaris, Irix, AIX. Я решил использовать популярную форму. Но если вы предпочитаете название «GNU/Linux», подставляйте мысленно недостающую часть, когда будете читать эту книгу. Я не буду возражать.

Предварительные условия

Для работы с книгой вам понадобится действующая система Linux. Вы можете получить ее двумя способами:

 Установить Linux на (пусть и не самый новый) компьютер. Выбор дистрибу- тива не играет большой роли, однако многие в наши дни начинают с Ubuntu, Fedora или OpenSUSE. Если не знаете, на чем остановить свой выбор, попро- буйте сначала Ubuntu. Установка современного дистрибутива Linux может быть смехотворно простой или чрезвычайно сложной, все зависит от комплек- тации вашего компьютера. Я бы рекомендовал выбрать не слишком пожилой настольный компьютер, имеющий хотя бы 2 Гбайт ОЗУ и 6 Гбайт свободного дискового пространства. Не советую использовать ноутбуки с беспроводным

Ваши отзывы важны для нас!

33

подключением к сети, если это возможно, потому что часто они сложнее в на- стройке.

 Использовать Live CD. Одна из самых удобных возможностей, которой обла- дают многие дистрибутивы Linux, — загрузка и запуск системы непосредствен- но с компакт-диска или флешки, без необходимости устанавливать ее. Просто включите возможность загрузки с компакт-диска или флешки в настройках BIOS, вставьте Live CD в CD-ROM или флешку в порт USB и перезагрузитесь. Этот способ дает отличную возможность проверить совместимость компьюте- ра с Linux перед установкой. Недостаток Live CD — очень медленная работа в сравнении c установкой Linux на жесткий диск. Оба дистрибутива, Ubuntu и Fedora (среди прочих), имеют версии Live CD.

Вне зависимости от того, как вы установите Linux, чтобы выполнять примеры из книги, вам будут нужны привилегии суперпользователя (то есть администра- тора).

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

Что нового во втором издании

Базовая структура и содержание книги остались практически неизменными, но в этом издании «Командной строки Linux» появилось множество новых уточне- ний, пояснений и улучшений, большинство из которых основано на отзывах чита- телей. Кроме того, в книгу внесено два особенно заметных улучшения. Во-первых, теперь в книге используется версия bash 4.x, которая еще не получила широкого распространения к моменту, когда я работал над первым изданием. В этой чет- вертой версии bash появилось несколько интересных особенностей, описываемых в этом издании. Во-вторых, внесены улучшения в четвертую часть «Сценарии ко- мандной оболочки» — теперь в ней представлены более удачные примеры и улуч- шенные приемы программирования. Сценарии, включенные в четвертую часть, были пересмотрены с целью повышения их надежности и исправления некоторых ошибок ;-).

Ваши отзывы важны для нас!

Эта книга постоянно дорабатывается и обновляется, подобно многим проектам с открытым исходным кодом. Если вы обнаружите техническую ошибку, черкните мне пару строк по адресу bshotts@users.sourceforge.net.

34

Введение

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

От издательства

Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция).

Мы будем рады узнать ваше мнение!

На веб-сайте издательства www.piter.com вы найдете подробную информацию о на- ших книгах.

Часть I КОМАНДНАЯ ОБОЛОЧКА

1 Что такое командная

оболочка

Говоря о командной строке, на самом деле мы имеем в виду командную оболочку (shell). Командная оболочка — это программа, которая принимает команды, введен- ные с клавиатуры, и передает их операционной системе для выполнения. Практиче- ски все дистрибутивы Linux поставляются с командной оболочкой из проекта GNU, которая называется bash. Имя bash — это аббревиатура от названия Bourne Again Shell, отражающего тот факт, что bash является улучшенной заменой sh, первона- чальной командной оболочки для Unix, написанной Стивом Борном (Steve Bourne).

Эмуляторы терминалов

При использовании графического интерфейса для взаимодействия с командной оболочкой нам понадобится еще одна программа — эмулятор терминала. Загля- нув в меню рабочего стола, вы наверняка обнаружите такую программу. В KDE используется konsole, в GNOME — gnome-terminal, однако соответствующий пункт в меню часто называется просто «Terminal» (или «Терминал»). Для Linux существует также множество других эмуляторов терминала, но все они решают одну и ту же задачу: предоставляют доступ к командной оболочке. Со временем у вас наверняка появятся свои предпочтения, в зависимости от «рюшечек и бан- тиков», которые они имеют.

Первые удары по клавишам

Итак, приступим. Запустите эмулятор терминала. После появления окна на экра- не вы увидите в нем нечто подобное:

[me@linuxbox ~]$

Первые удары по клавишам

37

НЕСКОЛЬКО СЛОВ О МЫШИ И ФОКУСЕ ВВОДА

Для работы с командной оболочкой достаточно одной клавиатуры, однако эмуля- тор терминала позволяет также использовать мышь. X Window System (механизм, который воспроизводит графический интерфейс на экране) поддерживает при- ем быстрого копирования с помощью мыши. Если выделить текст, нажав левую кнопку и переместив указатель мыши над ним (или выполнив двойной щелчок на слове), он будет скопирован в специальный буфер, которым управляет X. Нажатие средней кнопки мыши вызовет вставку текста в позицию курсора. Попробуйте проделать этот фокус.

Не пытайтесь использовать комбинации CTRL+C и CTRL+V для копирования и встав- ки в окне терминала. Эти команды там не работают. В командной оболочке эти комбинации клавиш имеют другие значения, присвоенные им задолго до появления Microsoft Windows.

Графическое окружение вашего рабочего стола (скорее всего, KDE или GNOME) работает очень похоже на графическое окружение Windows и, вероятнее всего, реализует политику «щелкни, чтобы передать фокус ввода». Это означает, что для передачи фокуса ввода в окно (его активизации) на нем нужно щелкнуть мышью. Это противоречит традиционному поведению X «фокус следует за мы- шью», когда для передачи фокуса ввода в окно достаточно просто навести на него указатель мыши. Окно не поднимется на передний план, пока вы не щелкнете на нем мышью, но способно принять фокус ввода. Настройка политики «фокус следует за мышью» упростит работу с окном терминала. Попробуйте, я думаю, вам понравится. Соответствующие параметры находятся в программе настройки вашего диспетчера окон.

Это называется приглашением к вводу (shell prompt) и появляется всякий раз, когда командная оболочка готова принять ввод. В разных дистрибутивах приглашение выглядит по-разному, но обычно включает строку имя_пользователя@имя_ком- пьютера, за которой следует имя текущего каталога (подробнее об этом чуть ниже) и знак доллара.

Если последний символ в приглашении — знак решетки (#), а не знак доллара, это означает, что сеанс в терминале обладает привилегиями суперпользователя. То есть либо вы зарегистрировались как пользователь root, либо запустили эму- лятор терминала, который автоматически устанавливает привилегии суперполь- зователя (администратора).

Будем считать, что пока все идет хорошо, и попробуем что-нибудь ввести. Набе- рите на клавиатуре какую-нибудь бессмыслицу, например:

[me@linuxbox ~]$ kaekfjaeifj

38

Глава 1. Что такое командная оболочка

Поскольку это бессмыслица, командная оболочка немедленно сообщит об этом и даст вам второй шанс:

bash: kaekfjaeifj: команда не найдена [me@linuxbox ~]$

История команд

Если теперь нажать клавишу со стрелкой вверх, после приглашения к вводу по- явится предыдущая команда kaekfjaeifj. Это называется историей команд. Боль- шинство дистрибутивов Linux по умолчанию запоминают последние 1000 команд. Нажмите клавишу со стрелкой вниз, и предыдущая команда исчезнет.

Управление курсором

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

Некоторые простые команды1

Теперь, когда вы понажимали клавиши, попробуем ввести несколько простых ко- манд. Первая команда date. Она выводит текущие время и дату:

[me@linuxbox ~]$ date Fri Feb 2 15:09:41 EST 2018

Родственная ей команда cal по умолчанию выводит календарь текущего месяца:

[me@linuxbox ~]$ cal February 2018 Su Mo Tu We Th Fr Sa 1 2 3

1 Часть вывода команд в Linux переведена на русский язык, часть — нет. В дальнейшем те системные сообщения и результаты выполнения команд, выводимые на консоль, которые в локализованной версии Linux переведены, мы будем приводить на рус- ском, остальные — на английском. В тех случаях, когда русский перевод консольного вывода важен для изложения, он будет приводиться в сносках внизу страницы. — При- меч. ред.

Завершение сеанса работы с терминалом

39

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Чтобы увидеть объем свободного пространства на дисках, введите df:

[me@linuxbox ~]$ df Файл.система 1K-блоков Использовано Доступно Использовано% Cмонтировано в /dev/sda2 15115452 5012392 9949716 34% / /dev/sda5 59631908 26545424 30008432 47% /home /dev/sda1 147764 17370 122765 13% /boot tmpfs 256856 0 256856 0% /dev/shm

Аналогично, чтобы увидеть объем свободного пространства в памяти, введите коман ду free:

[me@linuxbox ~]$ free total used free shared buffers available Mem: 513712 503976 9736 0 5312 122916 -/+ buffers/cache: 375748 137964 Swap: 1052248 104712 947536

Завершение сеанса работы с терминалом

Завершить сеанс работы с терминалом можно, либо закрыв окно эмулятора тер- минала, либо введя команду exit:

[me@linuxbox ~]$ exit

КОНСОЛЬ ЗА КУЛИСАМИ

Даже если не запущен ни один эмулятор терминала, за ширмой графического рабочего стола продолжают выполняться несколько сеансов терминалов. Полу- чить доступ к этим сеансам, или виртуальным консолям, в большинстве дистри- бутивов Linux можно с помощью комбинаций клавиш, начиная с CTRL+ALT+F1 до CTRL+ALT+F6. После перехода к сеансу вы увидите приглашение к регистрации в системе, где нужно ввести имя пользователя и пароль. Для переключения из одной виртуальной консоли в другую используйте клавиши ALT и F1–F6. Чтобы вернуться в графическое окружение рабочего стола, в большинстве систем нужно нажать ALT+F7.

40

Глава 1. Что такое командная оболочка

Заключение

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

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

2 Навигация

Первое, что мы попробуем изучить (после пробных нажатий на клавиши), — на- вигацию в файловой системе Linux. В этой главе введем в обиход следующие команды:

pwd — выводит название текущего рабочего каталога;

cd — выполняет переход в другой каталог;

ls — выводит список содержимого каталога.

Дерево каталогов файловой системы

Так же как Windows, Unix-подобная операционная система, такая как Linux, ор- ганизует свои файлы в иерархическую структуру каталогов. То есть каталоги (в других системах их иногда называют папками) имеют древовидную органи- зацию и могут содержать файлы и другие каталоги. Первый каталог в файловой системе называется корневым каталогом. Корневой каталог содержит файлы и подкаталоги, которые в свою очередь также содержат файлы и каталоги, и т. д.

Обратите внимание, что в отличие от Windows, где для каждого устройства хране- ния создается отдельная файловая система, в Unix-подобных системах, таких как Linux, всегда имеется только одна файловая система, независимо от числа при- водов или устройств хранения, подключенных к компьютеру. Устройства хране- ния подключаются (или, как принято говорить, монтируются) к разным точкам дерева в соответствии с желанием системного администратора, человека (или не- скольких человек), ответственного за обслуживание системы.

42

Глава 2. Навигация

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

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

Рис. 2.1. Дерево каталогов файловой системы в диспетчере файлов с графическим интерфейсом

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

Однако командная строка не имеет графического интерфейса, поэтому для пере- мещения по дереву файловой системы его следует представлять иначе.

Представьте файловую систему в виде лабиринта в форме перевернутого дерева и себя в середине. В любой конкретный момент времени мы можем находиться только в одном каталоге, видеть файлы в этом каталоге и перемещаться в выше- лежащий каталог (называется родительским каталогом) и в любые нижележа- щие каталоги. Каталог, в котором мы находимся, называется текущим рабочим каталогом. Название текущего рабочего каталога выводится командой pwd (print working directory — вывести рабочий каталог):

[me@linuxbox ~]$ pwd /home/me

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

Смена текущего рабочего каталога

43

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

Вывод содержимого каталога

Чтобы вывести список файлов и каталогов в текущем рабочем каталоге, восполь- зуйтесь командой ls:

[me@linuxbox ~]$ ls Desktop Documents Music Pictures Public Templates Videos

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

Смена текущего рабочего каталога

Чтобы сменить рабочий каталог (в котором мы находимся в середине древовид- ного лабиринта), можно воспользоваться командой cd: введите cd и добавьте путь к желаемому рабочему каталогу. Путь (pathname) — это маршрут, перечисляющий ветви дерева, по которым нужно пройти, чтобы достигнуть желаемого каталога. Пути могут определяться двумя способами: как абсолютные или как относитель- ные. Рассмотрим сначала абсолютные пути.

Абсолютные пути

Абсолютный путь начинается с корневого каталога и перечисляет ветви дерева, отделяющие корень от желаемого каталога или файла. Например, в системе име- ется каталог, в который устанавливается большинство программ. Путь к этому каталогу имеет вид /usr/bin. То есть в корневом каталоге (представлен первым символом «слеш» в пути) имеется каталог с названием usr, содержащий каталог с названием bin.

[me@linuxbox ~]$ cd /usr/bin [me@linuxbox bin]$ pwd /usr/bin [me@linuxbox bin]$ ls …Длинный, очень длинный список файлов…

44

Глава 2. Навигация

Как видите, мы сменили текущий рабочий каталог на /usr/bin, и он полон файлов. Обратите внимание, как изменилось приглашение командной оболочки к вводу. Для удобства оно обычно настраивается так, чтобы автоматически показывать на- звание рабочего каталога.

Относительные пути

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

Символ . (точка) обозначает рабочий каталог, а символ .. (точка-точка) обозна- чает каталог, родительский по отношению к рабочему. Ниже показано, как ими пользоваться. Давайте снова сменим рабочий каталог на /usr/bin:

[me@linuxbox ~]$ cd /usr/bin [me@linuxbox bin]$ pwd /usr/bin

Отлично, а теперь допустим, что мы хотим сменить рабочий каталог на родитель- ский для каталога /usr/bin, которым является /usr. Сделать это можно двумя спо- собами: пойти по абсолютному пути:

[me@linuxbox bin]$ cd /usr [me@linuxbox usr]$ pwd /usr

или по относительному:

[me@linuxbox bin]$ cd .. [me@linuxbox usr]$ pwd /usr

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

Аналогично, существуют два способа сменить рабочий каталог с /usr на /usr/bin. Указать абсолютный путь:

[me@linuxbox usr]$ cd /usr/bin [me@linuxbox bin]$ pwd /usr/bin

Смена текущего рабочего каталога

45

или относительный:

[me@linuxbox usr]$ cd ./bin [me@linuxbox bin]$ pwd /usr/bin

А теперь я хочу сделать важное замечание. Практически во всех случаях мож- но опустить пару символов ./, потому что они подразумеваются по умолчанию. Команда

[me@linuxbox usr]$ cd bin

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

ЧТО СЛЕДУЕТ ЗНАТЬ ОБ ИМЕНАХ ФАЙЛОВ

В системе Linux файлы именуются так же, как в других системах, таких как Windows, но есть некоторые важные отличия.

• Файлы, имена которых начинаются с точки, считаются скрытыми. Это означа- ет, что команда ls не будет выводить их, если не вызвать ее с параметром: ls -a. В момент создания учетной записи пользователя в его домашний каталог помещается несколько скрытых файлов, где хранятся различные настройки учетной записи. В главе 11 мы еще вернемся к подобным файлам, когда будем рассматривать возможные настройки своего окружения. Кроме того, некоторые приложения помещают в домашний каталог свои скрытые файлы с настройками.

• Linux, как это принято в Unix, различает регистр символов в именах файлов

и командах. Файлы с именами File1 и file1 — это разные файлы.

• Хотя Linux поддерживает длинные имена файлов с пробелами и знаками пунк- туации, старайтесь не использовать в именах файлов другие знаки пунктуации, кроме точки, дефиса и подчеркивания. И самое главное, не используйте про- белы в именах файлов. Если необходимо отделить друг от друга слова в имени файла, используйте символы подчеркивания. Потом вы не раз скажете себе спасибо за это.

• В Linux не поддерживается понятие «расширения файла», как в некоторых других операционных системах. Вы можете давать своим файлам любые имена. Тип и/или назначение файла определяется другими средствами. Но даже при том, что Unix-подобные операционные системы не используют расширения файлов для определения типа/назначения файлов, некоторые прикладные программы все же используют их для этой цели.

46

Глава 2. Навигация

Некоторые полезные сокращения

В табл. 2.1 перечислены некоторые способы быстрой смены рабочего каталога.

Таблица 2.1. Сокращенные варианты команды cd

Сокращение

Результат

cd

cd -

cd ~username

Сменить рабочий каталог на домашний

Сменить рабочий каталог на предыдущий рабочий каталог

Сменить рабочий каталог на домашний каталог пользователя username. Например, cd ~bob выполнит переход в домашний каталог пользователя bob

Заключение

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

3 Исследование системы

Теперь, когда мы знаем, как перемещаться по файловой системе, совершим об- зорное путешествие по системе Linux. Но прежде чем отправиться, познакомимся еще с несколькими командами, которые пригодятся в пути:

ls — выводит список содержимого каталога;

file — определяет тип файла;

less — выводит содержимое файла.

Любопытные возможности ls

Команда ls является, пожалуй, одной из самых часто используемых команд, и не без оснований. С ее помощью можно увидеть, что находится в каталоге, и узнать некоторые важные атрибуты файлов и каталогов. Как мы уже видели, чтобы по- лучить список файлов и подкаталогов в текущем рабочем каталоге, достаточно ввести команду ls:

[me@linuxbox ~]$ ls Desktop Documents Music Pictures Public Templates Videos

Команде можно явно указать каталог, содержимое которого требуется вывести:

me@linuxbox ~]$ ls /usr bin games include lib local sbin share src

и даже несколько каталогов. Следующая команда выведет содержимое домашнего каталога пользователя (обозначен символом ~) и каталога /usr:

48

Глава 3. Исследование системы

[me@linuxbox ~]$ ls ~ /usr /home/me: Desktop Documents Music Pictures Public Templates Videos /usr: bin games include lib local sbin share src

Можно также изменить формат вывода, чтобы получить больше информации:

[me@linuxbox ~]$ ls -l total 56 drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Desktop drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Documents drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Music drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Pictures drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Public drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Templates drwxrwxr-x 2 me me 4096 2017-10-26 17:20 Videos

Параметр -l, добавленный в команду, требует использования «длинного» (long) формата вывода.

Параметры и аргументы

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

команда -параметры аргументы

Большинство команд используют параметры, состоящие из одного символа, кото- рому предшествует дефис, например: -l. Но многие команды, в том числе коман- ды из проекта GNU, поддерживают параметры с длинными именами, состоящие из слова, которому предшествуют два дефиса. Кроме того, многие команды по- зволяют объединять вместе параметры с короткими именами. В следующем при- мере команде ls передаются два параметра: параметр l, требующий использовать длинный (long) формат вывода, и параметр t, требующий сортировать результаты по времени (time) изменения:

[me@linuxbox ~]$ ls -lt

Добавим параметр с длинным именем –reverse, чтобы изменить порядок сорти- ровки на обратный:

[me@linuxbox ~]$ ls -lt –reverse

Любопытные возможности ls

49

ПРИМЕЧАНИЕ

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

Команда ls имеет огромное число допустимых параметров. Наиболее популяр- ные из них перечислены в табл. 3.1.

Таблица 3.1. Наиболее популярные параметры команды ls

Параметр

Длинный параметр Описание

-a

-A

-d

-F

-h

-l

-r

-S

-t

–all

–almost-all

–directory

Список всех (all) файлов, даже с именами, начина- ющимися с точки, которые обычно не выводятся (то есть скрытых)

Действует подобно параметру -a, но не выводит каталоги . (текущий рабочий каталог) и .. (роди- тельский каталог)

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

–classify

Добавляет в конец каждого имени символ-индикатор (например, прямой слеш, если это имя каталога)

–human-readable

При использовании длинного формата вывода ото- бражает размеры файлов не в байтах, а в величинах с единицами измерения

Выводит результаты с использованием длинного формата

–reverse

Выводит результаты в обратном порядке. Обычно ко- манда ls выводит результаты в алфавитном порядке

Сортировать результаты по размеру (size)

Сортировать результаты по времени (time) последне- го изменения

Пристальный взгляд на длинный формат

Как было показано выше, параметр -l заставляет команду ls выводить резуль- таты с использованием длинного формата. Этот формат предусматривает вывод

50

Глава 3. Исследование системы

большого количества полезной информации. Ниже приводится пример вывода содержимого каталога Examples в системе Ubuntu:

-rw-r–r– 1 root root 3576296 2017-04-03 11:05 Experience ubuntu.ogg -rw-r–r– 1 root root 1186219 2017-04-03 11:05 kubuntu-leaflet.png -rw-r–r– 1 root root 47584 2017-04-03 11:05 logo-Edubuntu.png -rw-r–r– 1 root root 44355 2017-04-03 11:05 logo-Kubuntu.png -rw-r–r– 1 root root 34391 2017-04-03 11:05 logo-Ubuntu.png -rw-r–r– 1 root root 32059 2017-04-03 11:05 oo-cd-cover.odf -rw-r–r– 1 root root 159744 2017-04-03 11:05 oo-derivatives.doc -rw-r–r– 1 root root 27837 2017-04-03 11:05 oo-maxwell.odt -rw-r–r– 1 root root 98816 2017-04-03 11:05 oo-trig.xls -rw-r–r– 1 root root 453764 2017-04-03 11:05 oo-welcome.odt -rw-r–r– 1 root root 358374 2017-04-03 11:05 ubuntu Sax.ogg

Рассмотрим различные поля для одного из файлов и их назначение (табл. 3.2).

Таблица 3.2. Поля длинного формата вывода команды ls

Поле

Назначение

-rw-r-r–

1

root

root

32059

Права доступа к файлу. Первый символ указывает тип файла. Например, символом дефиса обозначаются обычные файлы, а символом d — каталоги. Следующие три символа сообщают о правах доступа для владельца файла, следующие три — для членов группы, которой принадлежит файл, и последние три — для всех остальных. Более полный обзор прав доступа приводится в главе 9

Число жестких ссылок на файл. Подробнее о ссылках рассказывает- ся в разделах «Символические ссылки» и «Жесткие ссылки» далее в этой главе

Имя пользователя, владеющего файлом

Имя группы, владеющей файлом

Размер файла в байтах

2017-04-03 11:05

Дата и время последнего изменения файла

oo-cd-cover.odf

Имя файла

Определение типов файлов командой file

Занимаясь исследованием системы, полезно иметь возможность определять тип содержимого файлов. В этом нам поможет команда file. Как отмечалось выше,

Просмотр содержимого файлов командой less

51

имена файлов в Linux не обязаны отражать тип содержимого файлов. Например, увидев имя файла picture.jpg, можно предположить, что он содержит изображение в формате JPEG, но в Linux такие предположения могут не оправдываться. Вы- звать команду file можно так:

file имя_файла

Команда file выводит краткое описание содержимого файла. Например:

[me@linuxbox ~]$ file picture.jpg picture.jpg: JPEG image data, JFIF standard 1.01

Существует множество разных типов файлов. Одна из известных идей в Unix- подобных системах, таких как Linux, гласит: «Все сущее есть файл». По мере чте- ния книги вы убедитесь в истинности этого утверждения.

Типы многих файлов в вашей системе будут вам знакомы, например файлы MP3 и JPEG, но иногда будут попадаться файлы с малоизвестными и даже странными типами.

Просмотр содержимого файлов командой less

Команда less — это программа для просмотра текстовых файлов. В системе Linux присутствует множество файлов, содержащих обычный удобочитаемый текст. Программа less предоставляет удобный способ исследовать их содержимое.

Зачем может понадобиться исследовать текстовые файлы? Дело в том, что многие файлы с системными настройками (их называют конфигурационными файлами) хранят информацию в этом формате, что дает возможность прочитать их и вник- нуть в особенности работы системы. Кроме того, в этом формате хранятся многие программы в системе (их называют сценариями). В последующих главах мы узна- ем, как редактировать файлы с настройками системы и как писать свои сценарии, а пока просто просматривайте их содержимое.

Команда less используется так:

less имя_файла

После запуска программа less позволяет прокручивать текстовый файл взад и вперед. Например, просмотреть содержимое файла со всеми известными систе- ме учетными записями пользователей можно с помощью следующей команды:

[me@linuxbox ~]$ less /etc/passwd

52

Глава 3. Исследование системы

ЧТО ЕСТЬ «ТЕКСТ»

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

Некоторые из этих систем представления очень сложны (например, сжатые видео- файлы), другие, напротив, очень просты. Одна из самых ранних и простых систем называется ASCII-текст. ASCII (произносится «ас-ки») — это аббревиатура названия «American Standard Code for Information Interchange» (американский стандартный код для обмена информацией). Эта простая система кодирования впервые была использована в телетайпах.

Текст — это простое отображение «один в один» символов в числа. Это очень компактный формат. Пятьдесят символов текста преобразуются в пятьдесят байт данных. Но это не то же самое, что текст в документе, созданном текстовым процессором, таким как Microsoft Word или LibreOffice Writer. Файлы документов, в отличие от простых файлов с ASCII-текстом, содержат множество нетекстовых элементов, используемых для описания их структуры и форматирования. Файлы с простым ASCII-текстом содержат только сами символы и очень небольшое ко- личество простейших кодов управления, таких как символы табуляции, возврата каретки и перевода строки.

В системе Linux многие файлы хранятся в текстовом формате, и многие инстру- менты работают с текстовыми файлами. Даже Windows признает важность этого формата. Хорошо известная программа Notepad (Блокнот) — это редактор для простых файлов с ASCII-текстом.

После запуска программа less выведет содержимое файла. Если файл занимает больше одной страницы, его можно прокручивать вверх и вниз. Чтобы выйти из программы less, нажмите клавишу Q.

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

Таблица 3.3. Команды программы less

Команда

Действие

Page Up или b

Прокрутка к началу на одну страницу

Page Down или ПРОБЕЛ Прокрутка к концу на одну страницу

Обзорное путешествие

53

Команда

Действие

СТРЕЛКА ВВЕРХ

Прокрутка к началу на одну строку

СТРЕЛКА ВНИЗ

Прокрутка к концу на одну строку

G

1G или g

/символы

n

h

q

Переход в конец текстового файла

Переход в начало текстового файла

Поиск вниз по тексту до ближайшего вхождения указанной по- следовательности символов

Поиск следующего вхождения искомой последовательности символов

Вывод экрана со справкой

Завершить less

МЕНЬШЕ ЗНАЧИТ БОЛЬШЕ

Программа less создавалась как улучшенная замена более ранней Unix-программы с именем more. Ее имя — это игра слов «less is more» (меньше значит больше) — девиз архитекторов-модернистов и проектировщиков.

less относится к категории программ постраничного просмотра текстовых до- кументов, которые называют пейджерами (pagers). В отличие от программы more, которая может листать страницы только вперед, программа less способна листать текст в обоих направлениях, вперед и назад, и имеет множество других особенностей.

Обзорное путешествие

Файловая система в Linux имеет практически ту же компоновку, что и в других Unix-подобных системах. Фактически ее структура определяется опубликован- ным стандартом с названием «Linux Filesystem Hierarchy Standard». Не все дис- трибутивы Linux следуют этому стандарту, но большинство придерживаются его достаточно близко.

А теперь немного попутешествуем по файловой системе и познакомимся с основ- ными достопримечательностями системы Linux. Это даст нам шанс попрактико- ваться в навигации. Первое, что мы обнаружим: многие интересные файлы имеют простой текстовый формат. В ходе путешествия пробуйте выполнить следующие действия:

54

Глава 3. Исследование системы

 с помощью команды cd перейдите в указанный каталог;

 выведите содержимое каталога командой ls -l;

 если увидите заинтересовавший вас файл, определите его тип командой file;

 если файл выглядит как текстовый, попробуйте просмотреть его коман-

дой less.

ВСПОМНИТЕ ТРЮК С КОПИРОВАНИЕМ И ВСТАВКОЙ!

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

Если по ошибке вы попробовали просмотреть содержимое нетекстового файла и полностью нарушили нормальную работу окна терминала, вы можете вернуть его в исходное состояние, выполнив команду reset.

В ходе путешествия не бойтесь заглядывать внутрь системы. Обычные пользо- ватели практически ничего не смогут испортить. Это работа системного админи- стратора! Если команда пожалуется на что-то, просто перейдите к чему-нибудь другому. Потратьте некоторое время на знакомство с окрестностями. Это наша система, и мы вправе заниматься ее исследованием. Помните, что в Linux нет се- кретов!

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

Таблица 3.4. Каталоги в системе Linux

Каталог

Описание

/

/bin

/boot

Корневой каталог, откуда все начинается

Содержит двоичные (binaries) файлы (программы), необходимые для за- грузки и функционирования системы

Содержит ядро Linux, образ начального RAM-диска (с драйверами, необхо- димыми на этапе загрузки) и сам загрузчик. Интересные файлы:

/boot/grub/grub.conf или menu.lst, используются для настройки загруз- чика /boot/vmlinuz (или с похожим именем), ядро Linux

Обзорное путешествие

55

Каталог

Описание

/dev

/etc

/home

Специальный каталог, содержащий узлы устройств. «Все сущее есть файл» применяется также к устройствам. Здесь ядро хранит список всех известных ему устройств

Каталог /etc содержит все системные конфигурационные файлы. Здесь же хранится коллекция сценариев командной оболочки, запускающих систем- ные службы во время загрузки. Практически все файлы в этом каталоге содержат обычный читаемый текст. В /etc все файлы интересны, но, на мой взгляд, особенный интерес пред- ставляют:

/etc/crontab, файл, определяющий время запуска автоматизированных заданий; /etc/fstab, таблица устройств хранения и соответствующих им точек монтирования; /etc/passwd, список всех учетных записей пользователей

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

/lib

Содержит файлы разделяемых библиотек, используемых основными си- стемными программами. Они напоминают библиотеки DLL в Windows

/lost+found

Каждый раздел или устройство, отформатированные с использованием файловой системы Linux, такой как ext3, будут иметь этот каталог. Он используется на случай частичного восстановления повреждений в фай- ловой системе. Если с системой ничего страшного не происходило, этот каталог будет оставаться пустым

/media

/mnt

/opt

/proc

В современных системах Linux каталог /media будет содержать точки монтирования съемных носителей, таких как USB-диски, CD-ROM и т. д., которые монтируются в момент подключения

В старых системах Linux каталог /mnt содержал точки монтирования съем- ных носителей, монтируемых вручную

Каталог /opt используется для установки «необязательного» (optional) программного обеспечения, в основном для установки коммерческого про- граммного обеспечения

Специальный каталог. Не является фактической файловой системой, в том смысле, что файлы в этом каталоге не хранятся на жестком диске. Это виртуальная файловая система, поддерживаемая ядром Linux. Файлы в ней являются «глазками», через которые можно заглянуть в ядро. Эти файлы доступны для чтения и помогают «увидеть» компьютер глазами ядра

56

Глава 3. Исследование системы

Таблица 3.4 (окончание)

Каталог

Описание

/root

/sbin

/tmp

/usr

Домашний каталог пользователя root

Каталог содержит системные двоичные файлы (system binaries). Эти программы выполняют жизненно важные задачи и обычно запускаются только суперпользователем

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

Дерево каталогов /usr является, пожалуй, самым объемным в системе Linux. В нем хранятся все программы и файлы поддержки, используемые обычными пользователями

/usr/bin

Каталог /usr/bin содержит выполняемые программы, установленные дис- трибутивом Linux. Очень часто в этом каталоге хранятся тысячи программ

/usr/lib

Содержит разделяемые библиотеки для программ в /usr/bin

/usr/local

/usr/sbin

/usr/share

/usr/share/ doc

/var

/var/log

Дерево каталогов /usr/local используется для установки тех программ, которые не входят в состав дистрибутива, но должны быть доступны всем пользователям в системе. Программы, собираемые из исходных текстов, обычно устанавливаются в /usr/local/bin. В новейших версиях системы Linux это дерево каталогов присутствует, но остается пустым, пока систем- ный администратор не добавит туда что-нибудь

Содержит дополнительные программы для администрирования

Каталог /usr/share содержит все разделяемые данные, используемые программами в /usr/bin, в том числе конфигурационные файлы с настрой- ками по умолчанию, ярлыки, фоновые изображения для рабочего стола, звуковые файлы и т. д

Большинство пакетов, установленных в системе, содержат документа- цию. Вся эта документация, организованная по пакетам, хранится в ката- логе /usr/share/doc

За исключением /tmp и /home, все упоминавшиеся выше каталоги остают- ся относительно статичными; то есть их содержимое почти не меняется. Дерево каталогов /var — как раз то место, где хранятся часто изменяемые данные: различные базы данных, буферные файлы, почта пользователей и пр.

Каталог /var/log содержит файлы журналов с записями о различных действиях, выполнявшихся в системе. Они очень важны и должны про- веряться время от времени. Наиболее полезными являются файлы /var/ log/messages и /var/log/syslog. Обратите внимание, что из соображений безопасности некоторые системы требуют привилегий суперпользователя для просмотра файлов журналов

Символические ссылки

57

Символические ссылки

Просматривая содержимое каталогов (например, /lib), нередко можно увидеть такие записи:

lrwxrwxrwx 1 root root 11 2018-08-11 07:34 libc.so.6 -> libc-2.6.so

Обратили внимание на первую букву l и на присутствие двух имен файлов в кон- це? Это специальный файл, который называется символической ссылкой (иногда их называют мягкими ссылками или, на жаргоне, симлинками). В большинстве Unix-подобных систем можно дать одному и тому же файлу несколько имен. Даже притом, что на данный момент ценность такого приема может быть не очевидна, в действительности это очень удобная возможность.

Вообразите следующий сценарий: программе требуется некий разделяемый ре- сурс (например, библиотека), хранящийся в файле с именем «foo», но номер вер- сии «foo» меняется очень часто. Было бы хорошо включить номер версии в имя файла, чтобы администратор или другое заинтересованное лицо могли видеть, какая версия «foo» установлена. И здесь возникает проблема. Если изменить имя разделяемого ресурса, нам придется проверять каждую программу, использую- щую этот ресурс, и изменять в ней имя ресурса на новое после установки новой версии ресурса. Если честно, такая перспектива не выглядит привлекательной.

Символические ссылки помогут спасти положение. Допустим, мы установили «foo» версии 2.6 с именем файла «foo-2.6» и затем создали символическую ссыл- ку с простым именем «foo», указывающую на ресурс «foo-2.6». То есть когда про- грамма откроет файл «foo», в действительности она откроет файл «foo-2.6». И все будут счастливы. Программы, полагающиеся на имя «foo», найдут нужный файл, а мы сможем увидеть фактическую версию ресурса. Когда придет время обновить ресурс до версии «foo-2.7», мы просто добавим файл в систему, удалим символи- ческую ссылку «foo» и создадим новую символическую ссылку, указывающую на новую версию. Такой подход не только решает проблему обновления версий, но также позволяет сохранить на компьютере обе версии ресурса. Представьте, что в версии «foo-2.7» обнаружилась ошибка (ох уж эти разработчики!) и нужно вер- нуть старую версию. В этом случае достаточно просто вновь удалить символиче- скую ссылку, указывающую на новую версию, и создать новую символическую ссылку, указывающую на старую версию.

Запись выше (получена в каталоге /lib в системе Fedora) соответствует символи- ческой ссылке с именем libc.so.6, указывающей на файл разделяемой библиоте- ки с именем libc-2.6.so. Это означает, что программа, ищущая libc.so.6, фактически получит файл libc-2.6.so. Как создавать символические ссылки, мы узнаем в сле- дующей главе.

58

Глава 3. Исследование системы

Жесткие ссылки

Пока мы не ушли далеко от темы ссылок, нужно упомянуть, что существует вто- рой тип ссылок, которые называют жесткими ссылками (hard link). Жесткие ссыл- ки также позволяют присвоить одному файлу несколько имен, но они действуют иначе. Подробнее о различиях между жесткими и символическими ссылками рас- сказывается в следующей главе.

Заключение

Совершив обзорное путешествие, мы узнали много нового о нашей системе. Мы увидели разные файлы и каталоги и их содержимое. Один из важнейших уроков, которые вы должны вынести, — это насколько открыта система. В Linux содер- жится много важных файлов, содержащих обычный легкочитаемый текст. В отли- чие от многих коммерческих систем, Linux полностью открыта для исследования и изучения.

4 Операции с файлами

и каталогами

Теперь мы готовы приступить к настоящей работе! В этой главе будут представ- лены пять наиболее часто используемых команд Linux. Следующие команды ис- пользуются для работы и с файлами, и с каталогами:

cp — копирует файлы и каталоги;

mv — перемещает/переименовывает файлы и каталоги;

mkdir — создает каталоги;

rm — удаляет файлы и каталоги;

ln — создает жесткие и символические ссылки.

Справедливости ради следует заметить, что некоторые задачи, выполняемые эти- ми командами, гораздо проще решаются с помощью графического диспетчера файлов. В диспетчере файлов можно мышью перетаскивать файлы из одного ка- талога в другой, вырезать и вставлять файлы, удалять файлы и т. д. Но зачем тогда использовать эти старые программы командной строки?

Ответ прост: потому что они обладают мощностью и гибкостью. Несмотря на то что простые операции с файлами легко выполняются в диспетчере файлов с гра- фическим интерфейсом, сложные задачи проще решать с помощью программ ко- мандной строки. Например, как скопировать файлы HTML из одного каталога в другой, причем только те, что отсутствуют в каталоге назначения или имеют бо- лее позднюю дату последнего изменения? Сделать это в диспетчере файлов очень сложно, но легко в командной строке:

cp -u *.html destination

60

Глава 4. Операции с файлами и каталогами

Групповые символы

Прежде чем приступать к использованию обсуждаемых команд, необходимо сна- чала поговорить об одной особенности командной оболочки, которая делает эти команды такими мощными. Так как имена файлов используются в командной обо- лочке повсеместно, она поддерживает специальные символы, помогающие быстро определять группы имен файлов. Эти специальные символы называют групповы- ми символами (wildcards). Групповые символы (также известны как символы под- становки (globbing)) позволяют выбирать имена файлов по шаблону. В табл. 4.1 перечислены групповые символы и их соответствия.

Таблица 4.1. Групповые символы

Групповой символ

Соответствует

?

[символы]

[!символы]

Любая последовательность любых символов

Любой один символ

Любой один символ из указанного множества символов

Любой один символ, не принадлежащий указанному множеству символов

[[:класс:]]

Любой один символ, принадлежащий указанному классу

В табл. 4.2 перечислены наиболее часто используемые классы символов.

Таблица 4.2. Наиболее часто используемые классы символов

Класс символов

Соответствует

[:alnum:]

[:alpha:]

[:digit:]

[:lower:]

[:upper:]

Любой алфавитно-цифровой символ

Любой алфавитный символ

Любой цифровой символ

Любая буква в нижнем регистре

Любая буква в верхнем регистре

Групповые символы позволяют конструировать сложные критерии выбора имен файлов. В табл. 4.3 перечислены некоторые примеры шаблонов и их соответ- ствия.

Групповые символы

61

ГРУППОВЫЕ СИМВОЛЫ ТАКЖЕ ДЕЙСТВУЮТ В ГРАФИЧЕСКОМ ИНТЕРФЕЙСЕ

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

• В Nautilus (диспетчер файлов для GNOME) можно выбирать файлы с помощью диалога Edit (Правка) ► Select Pattern (Выделить по шаблону). Просто введите шаблон для выбора файлов с групповыми символами, и в текущем каталоге будут выделены файлы, соответствующие шаблону.

• В некоторых версиях Dolphin и Konqueror (диспетчеры файлов для KDE) груп- повые символы можно вводить непосредственно в адресную строку. Например, если понадобится увидеть все файлы с именами, начинающимися с буквы «u» в нижнем регистре, в каталоге /usr/bin, просто введите в адресной строке текст: /usr/bin/u*, и вы получите желаемый результат.

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

Таблица 4.3. Примеры использования групповых символов

Шаблон

Соответствует

g*

b*.txt

Data???

[abc]*

Все имена файлов

Все имена файлов, начинающиеся с символа «g»

Все имена файлов, начинающиеся с символа «b», за кото- рым следует любое число других символов, и заканчиваю- щиеся на «.txt»

Все имена файлов, начинающиеся с символов «Data», за которыми следуют ровно три любых символа

Все имена файлов, начинающиеся с символа «a», «b» или «c»

BACKUP.[0-9][0-9][0-9]

Все имена файлов, начинающиеся с символов «BACKUP.», за которыми следуют ровно три цифровых символа

[[:upper:]]*

Все имена файлов, начинающиеся с буквы в верхнем регистре

62

Глава 4. Операции с файлами и каталогами

Таблица 4.3 (Окончание)

Шаблон

Соответствует

[![:digit:]]*

Все имена файлов, не начинающиеся с цифры

*[[:lower:]123]

Все имена файлов, заканчивающиеся буквой в нижнем регистре или цифрой «1», «2» или «3»

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

ДИАПАЗОНЫ СИМВОЛОВ

Если у вас уже есть опыт работы с другим Unix-подобным окружением или вам приходилось читать другие книги по этой теме, вы встречали форму записи диапа- зонов символов [A-Z] или [a-z]. Это традиционные для Unix формы записи, и они прекрасно работают в старых версиях Linux. Более того, они все еще работают в новых версиях, но будьте очень осторожны при их использовании, потому что они не всегда дают ожидаемый результат без правильной настройки. А вообще, старайтесь избегать их и использовать классы символов.

mkdir — создание каталогов

Команда mkdir используется для создания каталогов. Вызывается она следующим образом:

mkdir каталог…

Примечание к форме записи: в этой книге три точки в описании команды, следу- ющие за аргументом (как в примере выше), говорят о том, что аргументов может быть несколько; то есть в данном случае команда

mkdir dir1

создаст один каталог с именем dir1, а команда

mkdir dir1 dir2 dir3

создаст три каталога с именами dir1, dir2 и dir3.

cp — копирование файлов и каталогов

63

cp — копирование файлов и каталогов

Команда cp копирует файлы и каталоги. Ее можно использовать двумя разными способами:

cp item1 item2

чтобы скопировать один файл или каталог item1 в файл или каталог item2, и

cp элемент… каталог

чтобы скопировать несколько элементов (файлов или каталогов) в указанный ка- талог.

Параметры команды cp и примеры ее использования

В табл. 4.4 перечислены некоторые параметры (короткие и эквивалентные им длинные), наиболее часто используемые с командой cp.

Таблица 4.4. Параметры команды cp

Параметр

Значение

-a, –archive

-i, –interactive

-r, –recursive

-u, –update

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

Запрашивать у пользователя подтверждение перед переза- писью существующего файла. Если этот параметр отсут- ствует, команда cp просто перезапишет существую- щие файлы (без предупреждения)

Рекурсивно копировать каталоги и их содержимое. Это обязательный параметр (или параметр -a) при копировании каталогов

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

-v, –verbose

Выводить информационные сообщения в процессе копиро- вания

64

Глава 4. Операции с файлами и каталогами

В табл. 4.5 приводится несколько примеров использования этих параметров.

Таблица 4.5. Примеры использования команды cp

Команда

Результат

cp file1 file2

cp -i file1 file2

Скопирует file1 в file2. Если file2 существует, он будет затерт новым файлом file1. Если file2 отсутствует, он будет создан

То же, что и выше, но если файл file2 существует, у пользо- вателя будет запрошено подтверждение перед перезаписью файла

cp file1 file2 dir1

Скопирует file1 и file2 в каталог dir1. Каталог dir1 должен существовать

cp dir1/* dir2

cp -r dir1 dir2

С использованием группового символа. Скопирует все файлы из каталога dir1 в каталог dir2. Каталог dir2 должен существо- вать

Скопирует содержимое каталога dir1 в каталог dir2. Если каталог dir2 не существует, он будет создан и заполнен содержимым каталога dir1. Если каталог dir2 существует, тогда в него будет скопирован сам каталог dir1 (вместе с его содержимым)

mv — перемещение и переименование файлов

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

mv item1 item2

перемещает или переименовывает файл или каталог item1 в item2.

mv элемент… каталог

перемещает один или более элементов из одного каталога в другой.

Параметры команды mv и примеры ее использования

Команда mv поддерживает множество тех же параметров, что и команда cp, как показано в табл. 4.6.

rm — удаление файлов и каталогов

65

Таблица 4.6. Параметры команды mv

Параметр

Значение

-i, –interactive

Запрашивать у пользователя подтверждение перед перезапи- сью существующего файла. Если этот параметр отсутствует, коман да mv просто перезапишет существующие файлы

-u, –update

При перемещении файлов из одного каталога в другой переме- щать только файлы, отсутствующие в каталоге назначения или более новые

-v, –verbose

Выводить информационные сообщения в процессе перемещения

В табл. 4.7 приводится несколько примеров использования команды mv.

Таблица 4.7. Примеры использования команды mv

Команда

Результат

mv file1 file2

Переместит file1 в file2. Если file2 существует, он будет заме- нен на новый файл file1. Если file2 отсутствует, он будет создан. В любом случае файл file1 исчезнет

mv -i file1 file2

То же, что и выше, но если файл file2 существует, у пользователя будет запрошено подтверждение перед перезаписью файла

mv file1 file2 dir1

Переместит file1 и file2 в каталог dir1. Каталог dir1 должен суще- ствовать

mv dir1 dir2

Если каталог dir2 отсутствует, команда mv создаст его и переместит содержимое каталога dir1 в каталог dir2, после чего удалит dir1. Если каталог dir2 существует, команда mv переместит каталог dir1 (вместе с его содержимым) в каталог dir2

rm — удаление файлов и каталогов

Команда rm используется для удаления (remove) файлов и каталогов, например:

rm элемент…

где элемент — это один или несколько файлов или каталогов.

Параметры команды rm и примеры ее использования

В табл. 4.8 перечислены некоторые параметры, наиболее часто используемые с коман дой rm.

66

Глава 4. Операции с файлами и каталогами

Таблица 4.8. Параметры команды rm

Параметр

Значение

-i, –interactive

Запрашивать у пользователя подтверждение перед удалением существующего файла. Если этот параметр отсутствует, команда rm просто удалит существующие файлы

-r, –recursive

-f, –force

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

Игнорировать отсутствующие файлы и не запрашивать под- тверждения. Этот параметр отменяет действие параметра –interactive

-v, –verbose

Выводить информационные сообщения в процессе удаления

БУДЬТЕ ОСТОРОЖНЫ С КОМАНДОЙ RM!

Unix-подобные операционные системы, такие как Linux, не имеют команды, от- меняющей удаление. Если вы что-то удалили командой rm, это исчезнет навсегда. Linux считает вас достаточно ответственным человеком, отдающим себе отчет в своих действиях.

Будьте особенно осторожны с групповыми символами. Рассмотрим следующий классический пример. Допустим, вы захотели удалить все файлы HTML в каталоге. Для этого вы вводите команду:

rm *.html

которая сделает именно то, что вам нужно, но если вы случайно вставите пробел между * и .html, как в следующей команде:

rm * .html

rm удалит все файлы в каталоге и затем сообщит, что не нашла файла .html.

Полезный совет: всякий раз, используя групповые символы с командой rm (по- мимо внимательной проверки ввода!), проверьте сначала аргумент с групповым символом с командой ls. Это позволит увидеть, какие файлы будут удалены. За- тем нажмите клавишу со стрелкой вверх, чтобы восстановить команду из истории, и замените ls на rm.

В табл. 4.9 приводится несколько примеров использования команды rm.

ln — создание ссылок

67

Таблица 4.9. Примеры использования команды rm

Команда

rm file1

Результат

Просто удалит файл file1

rm -i file1

Перед удалением file1 запросит подтверждение у пользователя

rm -r file1 dir1

Удалит файл file1 и каталог dir1 со всем его содержимым

rm -rf file1 dir1

То же, что и выше, но в отсутствие file1 и/или dir1 просто продол- жит работу, не выводя никаких сообщений

ln — создание ссылок

Команда ln применяется для создания жесткой или символической ссылки. Ее можно использовать одним из двух способов:

ln файл ссылка

создает жесткую ссылку.

ln -s элемент ссылка

создает символическую ссылку, где элементом может быть файл или каталог.

Жесткие ссылки

Жесткие ссылки — это первоначальный способ создания ссылок в Unix; символи- ческие ссылки — более позднее изобретение. По умолчанию каждый файл имеет одну жесткую ссылку, определяющую его имя. Создавая жесткую ссылку, мы соз- даем дополнительную запись в каталоге для файла. Жесткие ссылки имеют два важных ограничения.

 Жесткая ссылка не может указывать на файл за пределами собственной фай- ловой системы. Это означает, что ссылка не может указывать на файл, находя- щийся в другом разделе диска.

 Жесткая ссылка не может указывать на каталог.

Жесткая ссылка неотличима от самого файла. В отличие от символических ссы- лок, при выводе списка с содержимым каталогов жесткие ссылки никак не вы- деляются. При удалении жесткой ссылки удаляется только сама ссылка, а файл остается на месте (то есть пространство, занимаемое файлом, не освобождается), пока не будут удалены все жесткие ссылки на файл.

68

Глава 4. Операции с файлами и каталогами

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

Символические ссылки

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

Файл, на который указывает символическая ссылка, и сама символическая ссыл- ка почти неотличимы друг от друга. Например, если попытаться что-то записать в символическую ссылку, запись будет выполнена в файл, на который она указы- вает. Однако при удалении символической ссылки удаляется только символиче- ская ссылка, но не файл. Если удалить файл до того, как будет удалена символи- ческая ссылка, ссылка останется на месте, но будет указывать в никуда. О таких ссылках говорят, что они «битые». Во многих реализациях команда ls выделяет битые ссылки цветом, например красным, чтобы обратить на них внимание.

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

Постройка песочницы

Поскольку мы собираемся на практике опробовать некоторые операции с файла- ми, давайте выделим безопасный уголок для «игр» с командами управления фай- лами. Прежде всего нам понадобится каталог, в котором мы будем практиковать- ся. Создайте такой каталог в своем домашнем каталоге и назовите его playground.

Создание каталогов

Для создания каталогов используется команда mkdir. Чтобы создать каталог playground, проверьте сначала, находитесь ли вы в домашнем каталоге, и только потом создайте новый каталог:

[me@linuxbox ~]$ cd [me@linuxbox ~]$ mkdir playground

Постройка песочницы

69

Чтобы немножко украсить вашу песочницу, создайте внутри playground пару каталогов с именами dir1 и dir2. Для этого смените текущий рабочий каталог на playground и выполните еще одну команду mkdir:

[me@linuxbox ~]$ cd playground [me@linuxbox playground]$ mkdir dir1 dir2

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

Копирование файлов

Далее, добавим немного данных в нашу песочницу. Для этого скопируем какие- нибудь файлы. Командой cp скопируйте файл passwd из каталога /etc в текущий рабочий каталог.

[me@linuxbox playground]$ cp /etc/passwd .

Обратите внимание на сокращение, обозначающее текущий рабочий каталог, — точку в конце команды. Если после этого выполнить команду ls, мы увидим наш файл:

[me@linuxbox playground]$ ls -l итого 12 drwxrwxr-x 2 me me 4096 2018-01-10 16:40 dir1 drwxrwxr-x 2 me me 4096 2018-01-10 16:40 dir2 -rw-r–r– 1 me me 1650 2018-01-10 16:07 passwd

Теперь ради развлечения повторите операцию копирования, но на этот раз с пара- метром -v, чтобы посмотреть, как она работает:

[me@linuxbox playground]$ cp -v /etc/passwd . /etc/passwd' -> ./passwd'

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

[me@linuxbox playground]$ cp -i /etc/passwd . cp: переписать `./passwd'?

70

Глава 4. Операции с файлами и каталогами

Если в ответ на запрос ввести y, команда перезапишет существующий файл; если ввести любой другой символ (например, n), cp оставит прежнюю копию файла нетронутой.

Перемещение и переименование файлов

Имя passwd не выглядит органичным в нашей песочнице, поэтому дадим этому файлу какое-нибудь другое имя:

[me@linuxbox playground]$ mv passwd fun

Теперь немножко позабавимся и переместим переименованный файл в каждый из каталогов и обратно:

[me@linuxbox playground]$ mv fun dir1

переместит файл в каталог dir1. Следующая команда

[me@linuxbox playground]$ mv dir1/fun dir2

переместит файл из каталога dir1 в каталог dir2. Следующая команда

[me@linuxbox playground]$ mv dir2/fun .

вернет его в текущий рабочий каталог. Теперь посмотрим, как mv влияет на катало- ги. Сначала переместите файл в каталог dir1:

[me@linuxbox playground]$ mv fun dir1

Затем переместите dir1 в dir2 и проверьте их содержимое командой ls:

[me@linuxbox playground]$ mv dir1 dir2 [me@linuxbox playground]$ ls -l dir2 итого 4 drwxrwxr-x 2 me me 4096 2018-01-11 06:06 dir1 [me@linuxbox playground]$ ls -l dir2/dir1 итого 4 -rw-r–r– 1 me me 1650 2018-01-10 16:33 fun

Обратите внимание: так как dir2 уже существует, mv переместит dir1 в dir2. Если бы каталога dir2 не было, mv просто переименовала бы dir1 в dir2. В заключение верните все на свои места:

[me@linuxbox playground]$ mv dir2/dir1 . [me@linuxbox playground]$ mv dir1/fun .

Постройка песочницы

71

Создание жестких ссылок

Теперь попробуем поиграть со ссылками. Сначала займемся жесткими ссылками: создайте несколько жестких ссылок для нашего файла:

[me@linuxbox playground]$ ln fun fun-hard [me@linuxbox playground]$ ln fun dir1/fun-hard [me@linuxbox playground]$ ln fun dir2/fun-hard

Теперь у нас есть четыре экземпляра файла fun. Посмотрим, что содержит наш каталог playground:

[me@linuxbox playground]$ ls -l итого 16 drwxrwxr-x 2 me me 4096 2018-01-14 16:17 dir1 drwxrwxr-x 2 me me 4096 2018-01-14 16:17 dir2 -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun-hard

Прежде всего следует обратить внимание на второе поле в записях, соответствую- щих файлам fun и fun-hard. Оба содержат 4 — число жестких ссылок на файл, су- ществующих в данный момент. Как вы помните, файл всегда имеет хотя бы одну жесткую ссылку, потому что имя файла определяется ссылкой. Но как убедиться, что fun и fun-hard — это один и тот же файл? В этом случае команда ls нам не помощник. Вы, конечно, скажете, что fun и fun-hard имеют одинаковые размеры (поле 5), но по списку файлов нельзя уверенно утверждать, что это один и тот же файл. Чтобы решить эту задачу, заглянем поглубже.

Рассуждая о жестких ссылках, полезно представлять файлы состоящими из двух частей:

 раздела с данными, где хранится содержимое файла;

 раздела с именем, где хранится имя файла.

Создавая жесткую ссылку, мы фактически создаем дополнительный раздел с име- нем, ссылающийся на тот же раздел с данными. Цепочку дисковых блоков система присваивает тому, что называется индексным узлом (inode), который затем присва- ивается разделу с именем. То есть каждая жесткая ссылка ссылается на определен- ный индексный узел с содержимым файла.

Команда ls может извлекать эту информацию. Для этого ее нужно вызвать с па- раметром -i:

[me@linuxbox playground]$ ls -li итого 16 12353539 drwxrwxr-x 2 me me 4096 2018-01-14 16:17 dir1

72

Глава 4. Операции с файлами и каталогами

12353540 drwxrwxr-x 2 me me 4096 2018-01-14 16:17 dir2 12353538 -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun 12353538 -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun-hard

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

Создание символических ссылок

Символические ссылки были придуманы с целью преодолеть ограничения жест- ких ссылок:

 жесткие ссылки не могут указывать на файлы, находящиеся на других физиче-

ских устройствах;

 жесткие ссылки не могут указывать на каталоги — только на файлы.

Символическая ссылка — это файл особого типа, хранящий текстовый указатель на файл или каталог.

Создаются символические ссылки почти так же, как жесткие ссылки:

[me@linuxbox playground]$ ln -s fun fun-sym [me@linuxbox playground]$ ln -s ../fun dir1/fun-sym [me@linuxbox playground]$ ln -s ../fun dir2/fun-sym

Первый пример достаточно очевиден: мы просто добавили параметр -s, чтобы вместо жесткой ссылки создать символическую ссылку. Но два других выглядят несколько необычно. Не забывайте, что, создавая символическую ссылку, мы фак- тически определяем текст, описывающий местоположение целевого файла отно- сительно символической ссылки. В этом легко убедиться, если взглянуть на вы- вод команды ls:

[me@linuxbox playground]$ ls -l dir1 итого 4 -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun-hard lrwxrwxrwx 1 me me 6 2018-01-15 15:17 fun-sym -> ../fun

Запись с информацией о fun-sym в dir1 сообщает, что это символическая ссыл- ка (первый символ l в первом поле), указывающая на ../fun, что правильно. От- носительно символической ссылки fun-sym файл fun находится в каталоге уров- нем выше. Обратите также внимание на размер файла символической ссылки,

Постройка песочницы

73

равный 6, — это число символов в строке ../fun, а не размер файла, на который она указывает.

При создании символических ссылок можно также указывать абсолютные пути, например:

[me@linuxbox playground]$ ln -s /home/me/playground/fun dir1/fun-sym

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

Помимо обычных файлов, символические ссылки могут указывать также на ка- талоги:

[me@linuxbox playground]$ ln -s dir1 dir1-sym [me@linuxbox playground]$ ls -l итого 16 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir1 lrwxrwxrwx 1 me me 4 2018-01-16 14:45 dir1-sym -> dir1 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir2 -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun -rw-r–r– 4 me me 1650 2018-01-10 16:33 fun-hard lrwxrwxrwx 1 me me 3 2018-01-15 15:15 fun-sym -> fun

Удаление файлов и каталогов

Как уже говорилось ранее, удаление файлов и каталогов выполняется при помо- щи команды rm. Далее мы немного почистим нашу песочницу. Сначала удалите одну из жестких ссылок:

[me@linuxbox playground]$ rm fun-hard [me@linuxbox playground]$ ls -l итого 12 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir1 lrwxrwxrwx 1 me me 4 2018-01-16 14:45 dir1-sym -> dir1 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir2 -rw-r–r– 3 me me 1650 2018-01-10 16:33 fun lrwxrwxrwx 1 me me 3 2018-01-15 15:15 fun-sym -> fun

Результат получился вполне ожидаемым. Файл fun-hard исчез, и счетчик ссылок во втором поле в записи для файла fun уменьшился с четырех до трех. Далее, уда- лите файл fun и ради развлечения добавьте в команду параметр -i, чтобы посмо- треть, что происходит:

74

Глава 4. Операции с файлами и каталогами

[me@linuxbox playground]$ rm -i fun rm: удалить обычный файл `fun'?

Введите y в ответ на запрос, и файл будет удален. Но давайте посмотрим на вы- вод ls. Заметили, что произошло с fun-sym? Поскольку теперь символическая ссылка указывает на несуществующий файл, она стала битой:

[me@linuxbox playground]$ ls -l итого 8 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir1 lrwxrwxrwx 1 me me 4 2018-01-16 14:45 dir1-sym -> dir1 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir2 lrwxrwxrwx 1 me me 3 2018-01-15 15:15 fun-sym -> fun

В большинстве дистрибутивов Linux команда ls особым образом настраивается на отображение битых ссылок. Битые ссылки не представляют никакой опасно- сти, но вносят определенную путаницу. При попытке использовать битую ссылку вы увидите:

[me@linuxbox playground]$ less fun-sym fun-sym: Нет такого файла или каталога

Давайте немного приберем за собой. Удалите символическую ссылку:

[me@linuxbox playground]$ rm fun-sym dir1-sym [me@linuxbox playground]$ ls -l итого 8 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir1 drwxrwxr-x 2 me me 4096 2018-01-15 15:17 dir2

Главное, что следует помнить о символических ссылках: большинство операций с файлами воздействуют на целевой элемент, а не на саму ссылку. Однако команда rm является исключением из этого правила. Когда вы удаляете ссылку, удаляется сама ссылка, а не элемент, на который она указывает.

В заключение удалим каталог playground. Для этого вернитесь в домашний ката- лог и вызовите команду rm с параметром рекурсивного удаления каталогов (-r), чтобы удалить каталог playground и все его содержимое, включая подкаталоги:

[me@linuxbox playground]$ cd [me@linuxbox ~]$ rm -r playground

Заключение

75

СОЗДАНИЕ СИМВОЛИЧЕСКИХ ССЫЛОК С ПОМОЩЬЮ ГРАФИЧЕСКОГО ИНТЕРФЕЙСА

Диспетчеры файлов в GNOME и KDE предоставляют простой автоматизированный способ создания символических ссылок. Если в GNOME во время перетаскивания файла мышью удерживать нажатыми клавиши CTRL и SHIFT, вместо копирования (или перемещения) файлов будет выполнена операция создания ссылки. В KDE, когда перетаскиваемый файл сбрасывается в целевой каталог, появляется не- большое меню, предлагающее выбор из трех операций: скопировать, переместить или создать ссылку.

Заключение

Мы узнали много нового, но чтобы информация усвоилась, требуется время. Вы- полняйте упражнения в песочнице раз за разом, пока не почувствуете, что пони- маете их смысл. На данном этапе очень важно надежно усвоить, как работают ос- новные команды управления файлами и групповые символы. Не бойтесь выйти за рамки предложенных упражнений — добавьте дополнительные файлы и каталоги, поэкспериментируйте с групповыми символами для определения групп файлов в разных операциях. Идея ссылок на первый взгляд может показаться малопо- нятной, поэтому уделите время их исследованию. Зачастую они оказываются на- стоящим спасательным кругом.

5 Работа с командами

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

type — сообщает, как интерпретируется имя указанной команды;

which — сообщает, какая программа будет выполнена;

help — выводит справку по встроенным командам оболочки;

man — выводит страницу справочного руководства с описанием команды;

apropos — выводит список подходящих команд;

info — выводит запись из справочного руководства Info с описанием команды;

whatis — выводит краткое описание команды;

alias — создает псевдоним для команды.

Что такое команды?

Команда может быть:

 Выполняемой программой, как те файлы, что мы видели в каталоге /usr/bin. К этой категории относятся: скомпилированные двоичные программы, напри- мер, написанные на C и C++; программы, написанные на языках сценариев, та- ких как shell, Perl, Python, Ruby и др.

 Встроенной командой, реализованной внутри самой командной оболочки. Командная оболочка bash поддерживает множество внутренних команд, кото-

Идентификация команд

77

рые так и называют — встроенными (shell builtins). Команда cd, например, — это встроенная команда.

 Функцией командной оболочки. Функции командной оболочки (shell functions) — это миниатюрные сценарии на языке командной оболочки, встро- енные в окружение. Мы еще вернемся к вопросам настройки окружения и соз- дания функций командной оболочки в последующих главах, а пока просто помните об их существовании.

 Псевдонимом. Псевдоним (alias) — это команда, которую мы можем опреде-

лить сами, сконструировав из других команд.

Идентификация команд

Часто бывает полезно точно знать, какому из четырех типов принадлежит коман- да, и Linux предлагает пару способов узнать это.

type — получение типа команды

Команда type — это встроенная команда, которая сообщает тип указанной ей команды. Вызывается она следующим образом:

type команда

где команда — это имя исследуемой команды. Например:

[me@linuxbox ~]$ type type type встроена в оболочку [me@linuxbox ~]$ type ls ls является алиасом для `ls –color=tty' [me@linuxbox ~]$ type cp cp хэширована (/bin/cp)

Здесь мы видим результаты определения типов трех разных команд. Обратите внимание, что команда ls (в дистрибутиве Fedora) фактически является псевдо- нимом (alias) команды ls с параметром –color=tty. Теперь-то мы знаем, почему результаты команды ls отображаются в цвете!

which — определение местоположения выполняемого файла

Иногда в системе имеется более одной версии исполняемой программы. Это до- вольно редкое явление для настольных систем, но вполне обычное для больших

78

Глава 5. Работа с командами

серверов. Точно определить местоположение данного исполняемого файла позво- ляет команда which:

[me@linuxbox ~]$ which ls /bin/ls

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

[me@linuxbox ~]$ which cd /usr/bin/which: no cd in (/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games)

Это своеобразное сообщение «command not found» (команда не найдена).

Получение документации с описанием команд

Теперь, зная тип команды, можно поискать документацию с описанием, доступ- ную для каждого вида команд1.

help — получение справки для встроенных команд

bash имеет встроенную справку для каждой встроенной команды. Чтобы полу- чить ее, введите help с именем встроенной команды. Например:

[me@linuxbox ~]$ help cd cd: cd [-L|-P] [dir] Change the current directory to DIR. The default DIR is the value of the HOME shell variable.

The variable CDPATH defines the search path for the directory containing DIR. Alternative directory names in CDPATH are separated by a colon (:). A null directory name is the same as the current directory. If DIR begins with a slash (/), then CDPATH is not used.

If the directory is not found, and the shell option `cdable_vars’ is set, then try the word as a variable name. If that variable has a value, its value is used for DIR.

1 Некоторые разделы справки Linux переведены на русский язык, а некоторые — нет. Для переведенных разделов мы будем приводить русский текст, выводимый системой на кон- соль, для непереведенных — указывать перевод в сносках. — Примеч. ред.

Получение документации с описанием команд

79

Options: -L force symbolic links to be followed: resolve symbolic links in DIR after processing instances of ..' -P use the physical directory structure without following symbolic links: resolve symbolic links in DIR before processing instances of ..' -e if the -P option is supplied, and the current working directory cannot be determined successfully, exit with a non-zero status -@ on systems that support it, present a file with extended attributes as a directory containing the file attributes

The default is to follow symbolic links, as if -L' were specified. ..’ is processed by removing the immediately previous pathname component back to a slash or the beginning of DIR.

Exit Status: Returns 0 if the directory is changed, and if $PWD is set successfully when -P is used; non-zero otherwise..1

1 Перевод:

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

$HOME.

Переменная CDPATH определяет пути поиска каталога, содержащего DIR. Альтернативные имена каталогов в CDPATH отделяются друг от друга двоеточием (:). Пустое имя каталога соответствует текущему каталогу. Если DIR начинается с символа слеш (/), переменная CDPATH не используется.

Если каталог не найден и установлен параметр `cdable_vars’ командной оболочки, вы- полняется попытка интерпретировать слово как имя переменной. Если эта переменная имеет значение, тогда это значение используется как DIR.

Параметры: -L требует следовать по символическим ссылкам: символические ссылки разрешаются

в DIR после обработки элементов `..'

–P требует использовать физическую структуру каталогов без следования по символиче- ским ссылкам: символические ссылки разрешаются в DIR до обработки элементов `..' -e если указан параметр -P и текущий рабочий каталог невозможно определить, завер-

шить команду с ненулевым кодом возврата

-@ в системах, поддерживающих это, представляет файл с расширенными атрибутами как

каталог, содержащий атрибуты файлов

По умолчанию следует по символическим ссылкам, как если бы был указан параметр -L'. Встретив ..’, удаляет предыдущий компонент пути до ближайшего символа слеш или до начала DIR.

Возвращаемый код состояния: Возвращает 0, если смена каталога произошла и переменной $PWD было благополучно присвоено значение (при использовании параметра -P); в остальных случаях возвращает ненулевое значение.

80

Глава 5. Работа с командами

Примечание к форме записи: квадратные скобки в описании синтаксиса команды указывают на необязательность элемента. Вертикальная черта используется для перечисления взаимоисключающих вариантов. В примере с описанием команды cd, приведенном выше, ее синтаксис описывается как

cd [-L|[-P[-e]]] [dir]

Эта форма записи говорит, что команда cd может принимать необязательный па- раметр -L или -P и необязательный аргумент dir. Если указан параметр -P, вместе с ним можно также передать параметр -e.

Несмотря на то что help дает краткое и точное описание команды cd, это описание не может служить инструкцией по использованию, и, как вы можете видеть, в нем упоминается многое из того, чего мы еще не знаем! Но не волнуйтесь, со всем этим мы познакомимся в свое время.

–help — вывод инструкции по использованию

Многие выполняемые программы поддерживают параметр –help для вывода описания синтаксиса и параметров, поддерживаемых командой. Например:

[me@linuxbox ~]$ mkdir –help Использование: mkdir [КЛЮЧ]… КАТАЛОГ… Создает КАТАЛОГ(и), если он еще не существует.

-Z, –context=CONTEXT (SELinux) установить контекст безопасности в CONTEXT Аргументы, обязательные для длинных ключей, обязательны и для коротких. -m, –mode=РЕЖИМ установить режим доступа (как в chmod), не a=rwx — umask -p, –parents не выдавать ошибок, если существует, создавать родительские каталоги, если необходимо -v, –verbose печатать сообщение о каждом созданном каталоге –help показать эту справку и выйти –version показать информацию о версии и выйти Об ошибках в mkdir сообщайте по адресу .

Некоторые программы не поддерживают параметр –help, но вы все равно про- буйте передать его. Часто в результате выводится сообщение об ошибке, содержа- щее ту же информацию о порядке использования.

man — вывод страниц справочного руководства

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

Получение документации с описанием команд

81

справочного руководства (man-страницу). Для просмотра этих страниц использу- ется специальная программа постраничного просмотра man, например:

man программа

где программа — имя команды.

Страницы справочного руководства могут несколько отличаться друг от друга оформлением, но в общем случае содержат:

 заголовок (название страницы);

 краткий обзор синтаксиса команды;

 описание назначения команды;

 список всех параметров с их описанием.

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

[me@linuxbox ~]$ man ls

В большинстве систем Linux man использует less для вывода страницы, поэтому при просмотре страницы можно использовать все известные команды less.

«Руководство», которое отображает man, разбито на разделы и охватывает не толь- ко пользовательские команды, но и команды системного администрирования, программные интерфейсы, форматы файлов и многое другое. В табл. 5.1 перечис- лены разделы справочного руководства.

Таблица 5.1. Организация справочного руководства

Раздел

Содержит

1

2

3

4

5

6

7

8

Пользовательские команды

Программные интерфейсы системных вызовов в ядре

Программные интерфейсы в библиотеке C

Специальные файлы, такие как узлы устройств и драйверы

Форматы файлов

Игры и развлечения, такие как хранители экрана

Прочее

Команды системного администрирования

82

Глава 5. Работа с командами

Иногда, чтобы найти искомое, нужно заглянуть в конкретный раздел. Это актуаль- но для форматов файлов, названия которых часто совпадают с именами команд. Если номер раздела не указан, man всегда будет возвращать первую найденную страницу, обычно из раздела 1. Ниже приведен пример прямого указания номера раздела:

man раздел искомый_термин

Например:

[me@linuxbox ~]$ man 5 passwd

выведет страницу с описанием формата файла /etc/passwd.

apropos — вывод списка подходящих команд

Кроме того, существует возможность найти страницы справочного руководства для близких совпадений с искомым термином. Несмотря на неточность, этот под- ход иногда оказывается полезным. Ниже приводится пример поиска страниц справочного руководства по слову partition:

[me@linuxbox ~]$ apropos partition addpart (8) - simple wrapper around the “add partition” ioctl all-swaps (7) - event signalling that all swap partitions have been ac… cfdisk (8) - display or manipulate disk partition table cgdisk (8) - Curses-based GUID partition table (GPT) manipulator delpart (8) - simple wrapper around the “del partition” ioctl fdisk (8) - manipulate disk partition table fixparts (8) - MBR partition table repair utility gdisk (8) - Interactive GUID partition table (GPT) manipulator mpartition (1) - partition an MSDOS hard disk partprobe (8) - inform the OS of partition table changes partx (8) - tell the Linux kernel about the presence and numbering… resizepart (8) - simple wrapper around the “resize partition” ioctl sfdisk (8) - partition table manipulator for Linux sgdisk (8) - Command-line GUID partition table (GPT) manipulator fo..1

1 Перевод:

addpart (8) - простая обертка для ioctl-команды “add partition” all-swaps (7) - событие, сигнализирующее, что все разделы подкачки были… cfdisk (8) - выводит или изменяет таблицу разделов диска cgdisk (8) - программа управления таблицами GUID разделов на основе curses delpart (8) - простая обертка для ioctl-команды “del partition” fdisk (8) - программа управления таблицей разделов диска fixparts (8) - утилита восстановления MBR таблицы разделов gdisk (8) - программа интерактивного управления таблицами GUID разделов (GPT)

Получение документации с описанием команд

83

Первое поле в каждой строке вывода — это имя страницы справочного руковод- ства, а второе поле — номер раздела. Обратите внимание, что команда man с пара- метром -k действует как apropos.

САМАЯ БРУТАЛЬНАЯ СТРАНИЦА СПРАВОЧНОГО РУКОВОДСТВА

Как вы могли убедиться, страницы справочного руководства, входящие в состав Linux и других Unix-подобных систем, играют роль справочной документации, но не инструкций по использованию. Многие страницы очень сложно читать, но, как мне кажется, первый приз за сложность можно было бы присудить странице с опи- санием bash. Работая над книгой, я очень внимательно прочитал эту страницу, чтобы убедиться, что не упустил ни одной важной темы. Когда я ее распечатал, у меня получилось больше 80 страниц чрезвычайно плотного текста, структура которого не имеет никакого смысла для начинающих пользователей.

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

whatis — вывод очень краткого описания команды

Программа whatis выводит имя и однострочное описание из страницы справочно- го руководства, соответствующей искомому слову:

[me@linuxbox ~]$ whatis ls ls (1) — выводит содержимое каталога

info — вывод записи из справочного руководства Info

В проекте GNU имеется альтернативное руководство Info, которое часто назы- вают info-страницами. Info-страницы выводятся с помощью программы чтения

mpartition (1) - создание разделов MSDOS на жестком диске partprobe (8) - информирует ОС об изменении таблицы разделов partx (8) - сообщает ядру Linux об имеющихся разделах и их нумерации… resizepart (8) - простая обертка для ioctl-команды “resize partition” sfdisk (8) - программа управления таблицами разделов для Linux sgdisk (8) - программа командной строки для управления таблицами GUID разделов (GPT)…

84

Глава 5. Работа с командами

с подходящим названием info. Info-страницы содержат гиперссылки, подобно веб-страницам. Например:

File: coreutils.info, Node: ls invocation, Next: dir invocation, Up: Directory listing 10.1 `ls’: List directory contents #

The ls' program lists information about files (of any type, including directories). Options and file arguments can be intermixed arbitrarily, as usual. For non-option command-line arguments that are directories, by default ls' lists the contents of directories, not recursively, and omitting files with names beginning with .'. For other non-option arguments, by default ls' lists just the filename. If no non-option argument is specified, ls' operates on the current directory, acting as if it had been invoked with a single argument of .’. By default, the output is sorted alphabetically, according to the –zz-Info: (coreutils.info.gz)ls invocation, 63 lines –Top———-1

Программа info читает info-файлы, организованные в древовидную структу- ру отдельных узлов, каждый из которых содержит отдельную тему. Info-файлы включают гиперссылки, с помощью которых можно перемещаться от узла к узлу. Гиперссылку можно узнать по начальному символу звездочки. Гиперссылки акти- вируются при установке текстового курсора на них и осуществляют переход при нажатии клавиши ENTER.

Чтобы вывести info-страницу, введите команду info и добавьте после нее необяза- тельное имя интересующей программы. В табл. 5.2 перечислены команды, которые можно использовать для управления программой во время чтения info-страницы.

1 Перевод:

Файл: coreutils.info, Узел: команда ls, Следующий: команда dir, Up: Содержимое каталога 10.1 `ls’: выводит содержимое каталога #

Программа ls' выводит информацию о файлах (любого типа, включая каталоги). Параметры и аргументы могут смешиваться произвольно, как обычно. Для аргументов без параметров, представляющих каталоги, по умолчанию ls' выводит содержимое каталогов нерекурсивно и пропускает файлы с именами, начинающимися с .'. Для других аргументов без параметров по умолчанию ls' выводит только указанный файл. В отсутствие аргументов без параметров ls' выводит содержимое текущего каталога, действуя, как если бы была вызвана с единственным аргументом .’. По умолчанию вывод сортируется в алфавитном порядке, согласно –zz-Info: (coreutils.info.gz)команда ls, 63 строки –Top———-

Создание собственных команд с помощью alias

85

Таблица 5.2. Команды программы info

Команда

Действие

?

Вывести справку

Page Up или Backspace

Вывести предыдущую страницу

Page Down или ПРОБЕЛ Вывести следующую страницу

n

p

u

ENTER

q

Вперед (next) — вывести следующий узел

Назад (previous) — вывести предыдущий узел

Вверх (up) — вывести узел, родительский по отношению к теку- щему, обычно меню

Перейти по гиперссылке, находящейся на позиции курсора

Завершить (quit)

Большинство программ из числа рассматривавшихся до сих пор, является частью пакета coreutils проекта GNU, поэтому о них можно получить дополнительную информацию командой

[me@linuxbox ~]$ info coreutils

Она выведет страницу с меню, состоящим из гиперссылок на документацию для каждой программы, входящей в состав пакета coreutils.

README и другие файлы с описанием программ

Многие программные пакеты, установленные в вашей системе, включают файлы с документацией, размещаемые в каталоге /usr/share/doc. Большинство из них имеют простой текстовый формат и могут просматриваться с помощью less. Не- которые файлы имеют формат HTML и могут просматриваться с помощью веб- браузера. Можно также встретить файлы с расширением .gz. Это сжатые файлы, обработанные программой-архиватором gzip. Пакет gzip включает специальную версию less с именем zless, которая выводит содержимое текстовых файлов, сжа- тых архиватором gzip.

Создание собственных команд с помощью alias

А теперь проведем первый опыт по программированию! У нас есть возможность создавать собственные команды с помощью команды alias. Но прежде чем начать,

86

Глава 5. Работа с командами

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

команда1; команда2; команда3…

Следующий пример демонстрирует этот прием:

[me@linuxbox ~]$ cd /usr; ls; cd - bin games include lib local sbin share src /home/me [me@linuxbox ~]$

Как видите, мы поместили три команды в одну строку. Первая выполняет пере- ход в каталог /usr, вторая выводит его содержимое, и третья осуществляет возврат в предыдущий каталог (команда cd -), поэтому по завершении мы оказываемся там же, где и были. Давайте теперь с помощью alias превратим эту последова- тельность в новую команду. Первое, что мы должны сделать, — придумать имя для новой команды. Пусть это будет test. Но прежде чем продолжить, хорошо бы проверить, не занято ли уже имя test. Для этого воспользуемся командой type:

[me@linuxbox ~]$ type test test встроена в оболочку

Ой! Имя test уже занято. Попробуем foo:

[me@linuxbox ~]$ type foo bash: type: foo: не найден

Отлично! Имя foo свободно. Теперь создадим наш псевдоним:

[me@linuxbox ~]$ alias foo=‘cd /usr; ls; cd -’

Обратите внимание на структуру этой команды:

alias имя=‘строка’

За командой alias следует имя, сразу за которым (то есть без пробелов) следует знак «равно» и строка в кавычках, описывающая действие, присваиваемое име- ни. После определения псевдонима его можно использовать везде, где ожидается коман да. Давайте попробуем:

[me@linuxbox ~]$ foo bin games inclide lib local sbin share src /home/me [me@linuxbox ~]$

Заключение

87

Команда type правильно определяет псевдонимы:

[me@linuxbox ~]$ type foo foo является алиасом для `cd /usr; ls; cd -'

Удалить псевдоним можно с помощью команды unalias:

[me@linuxbox ~]$ unalias foo [me@linuxbox ~]$ type foo bash: type: foo: не найден

Несмотря на то что в этом примере мы постарались не использовать имя суще- ствующей команды, иногда это бывает полезно. Часто это делается, чтобы при- менить наиболее желательные параметры к каждому вызову команды. Например, выше мы видели, что команда ls часто определяется как псевдоним, — это позво- ляет реализовать вывод информации в цвете:

[me@linuxbox ~]$ type ls ls является алиасом для `ls –color=tty'

Если вызвать команду alias без аргументов, она выведет список всех псевдони- мов в окружении. Ниже приводятся несколько псевдонимов, объявляемых в дис- трибутиве Fedora по умолчанию. Попробуйте понять, что они делают:

[me@linuxbox ~]$ alias alias l.=‘ls -d .* –color=tty’ alias ll=‘ls -l –color=tty’ alias ls=‘ls –color=tty’

Существует одна маленькая проблема, связанная с определением псевдонимов в командной строке. Они исчезают по завершении сеанса работы с командной оболочкой. В главе 11 будет показано, как добавить определения псевдонимов в файлы, чтобы они восстанавливались при каждом запуске командной оболочки, а пока насладимся нашим первым, пусть и крошечным, шагом в мир программи- рования на языке командной оболочки!

Заключение

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

6 Перенаправление

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

cat — объединяет файлы;

sort — сортирует строки текста;

uniq — сообщает о повторяющихся строках или удаляет их;

grep — находит и выводит строки, соответствующие шаблону;

wc — выводит число символов перевода строки, слов и байтов в каждом указанном файле;

head — выводит первые строки из файла;

tail — выводит последние строки из файла;

tee — читает данные со стандартного ввода и записывает в стандартный вывод и в файлы.

Стандартный ввод, вывод и вывод ошибок

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

 Результаты работы программы, то есть данные, для получения которых созда-

валась программа.

Перенаправление стандартного вывода

89

 Сообщения о состоянии или об ошибках, извещающие нас о самочувствии

программы.

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

Согласно центральной идее Unix, что «все сущее есть файл», такие программы, как ls, в действительности выводят свои результаты в специальный файл, кото- рый называется стандартным выводом (standard output, часто обозначается как stdout), а сообщения о состоянии — в специальный файл стандартный вывод оши- бок (standard error, stderr). По умолчанию оба файла, стандартный вывод и стан- дартный вывод ошибок, связаны с экраном и не сохраняются на диске.

Кроме того, многие программы принимают ввод из специального файла с назва- нием стандартный ввод (standard input, stdin), который по умолчанию связан с клавиатурой.

Механизм перенаправления ввода/вывода позволяет изменять направление вы- вода и ввода. Обычно вывод осуществляется на экран, а ввод — с клавиатуры, но механизм перенаправления ввода/вывода позволяет изменить этот порядок вещей.

Перенаправление стандартного вывода

Механизм перенаправления ввода/вывода позволяет явно указать, куда должен осуществляться стандартный вывод. Чтобы перенаправить стандартный вывод в другой файл вместо экрана, нужно добавить в команду оператор перенаправле- ния > и имя файла. Где это может пригодиться? Иногда полезно сохранить вывод команды в файл. Например, можно сообщить командной оболочке, что она долж- на направить вывод команды ls в файл ls-output.txt вместо экрана:

[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt

Здесь мы создали длинный список содержимого каталога /usr/bin и отправи- ли результаты в файл ls-output.txt. Давайте исследуем перенаправленный вывод команды:

[me@linuxbox ~]$ ls -l ls-output.txt -rw-rw-r– 1 me me 167878 2018-02-01 15:07 ls-output.txt

Неплохой файл получился. Если вывести содержимое ls-output.txt с помощью команды less, можно увидеть, что он действительно содержит результаты работы команды ls:

[me@linuxbox ~]$ less ls-output.txt

90

Глава 6. Перенаправление

Давайте теперь повторим эксперимент с перенаправлением, но с небольшим ус- ложнением: укажем имя несуществующего каталога:

[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt ls: невозможно получить доступ к ‘/bin/usr’: Нет такого файла или каталога

Мы получили сообщение об ошибке. Все логично — мы указали несуществующий каталог /bin/usr, но почему же сообщение появилось на экране, а не было перена- правлено в файл ls-output.txt? Дело в том, что программа ls не выводит сообщения об ошибках в стандартный вывод. Как и многие добропорядочные программы для Unix, она выводит сообщения об ошибках в стандартный поток вывода ошибок. Поскольку мы перенаправили только стандартный вывод, а стандартный вывод ошибок — нет, сообщение об ошибке появилось на экране. Как перенаправить стандартный вывод ошибок, будет показано чуть ниже, но перед этим посмотрим, что произошло с нашим файлом:

[me@linuxbox ~]$ ls -l ls-output.txt -rw-rw-r– 1 me me 0 2018-02-01 15:08 ls-output.txt

Файл очистился! Это объясняется тем, что при перенаправлении вывода с по- мощью оператора > файл назначения всегда перезаписывается с самого начала. Поскольку команда ls не вывела никаких результатов, а только сообщение об ошибке, оператор перенаправления перезаписал файл, а затем остановился из-за ошибки, что привело к его очистке. Получается, что если вам понадобится очи- стить какой-нибудь файл (или создать новый, пустой файл), это можно сделать с помощью следующего трюка:

[me@linuxbox ~]$ > ls-output.txt

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

Так как же добавить вывод в конец существующего файла, не затерев его? Для этого используем оператор перенаправления »:

[me@linuxbox ~]$ ls -l /usr/bin » ls-output.txt

Оператор » просто добавит результаты в конец файла. Если файл не существует, он будет создан, как при использовании оператора >. Давайте протестируем его:

[me@linuxbox ~]$ ls -l /usr/bin » ls-output.txt [me@linuxbox ~]$ ls -l /usr/bin » ls-output.txt [me@linuxbox ~]$ ls -l /usr/bin » ls-output.txt

Перенаправление стандартного вывода ошибок

91

[me@linuxbox ~]$ ls -l ls-output.txt -rw-rw-r– 1 me me 503634 2018-02-01 15:45 ls-output.txt

Мы повторили команду трижды и получили файл втрое большего размера.

Перенаправление стандартного вывода ошибок

Перенаправление стандартного вывода ошибок осуществляется не так просто, как стандартного вывода. Чтобы перенаправить стандартный вывод ошибок, нужно указать его дескриптор файла. Программа может производить вывод в любой из нескольких нумерованных файловых потоков. Первые три из них мы упомянули как стандартный ввод, вывод и вывод ошибок. Командная оболочка ссылается на них как на файловые дескрипторы 0, 1 и 2 соответственно. Командная оболоч- ка поддерживает синтаксис перенаправления файлов с использованием номеров файловых дескрипторов. Так как стандартному выводу ошибок соответствует файловый дескриптор 2, мы можем перенаправить его, как показано ниже:

[me@linuxbox ~]$ ls -l /bin/usr 2> ls-error.txt

Номер файлового дескриптора 2 помещается непосредственно перед операто- ром перенаправления, чтобы перенаправить стандартный вывод ошибок в файл ls-error.txt.

Перенаправление стандартного вывода и стандартного вывода ошибок в один файл

Иногда необходимо сохранить весь вывод команды в один файл. Для этого пере- направьте сразу два потока, стандартный вывод и стандартный вывод ошибок. Сделать это можно двумя способами. Первый — традиционный — работает в ста- рых версиях командной оболочки:

[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt 2>&1

Здесь выполняются два перенаправления. Сначала — перенаправление стандарт- ного вывода в файл ls-output.txt, а затем, с использованием нотации 2>&1, — пере- направление файлового дескриптора 2 (стандартный вывод ошибок) в файловый дескриптор 1 (стандартный вывод).

92

Глава 6. Перенаправление

ИМЕЙТЕ В ВИДУ, ЧТО ПОРЯДОК ПЕРЕНАПРАВЛЕНИЯ ИГРАЕТ ВАЖНУЮ РОЛЬ

Перенаправление стандартного вывода ошибок всегда должно производиться после перенаправления стандартного вывода, иначе этот трюк не сработает. Следующий пример перенаправит стандартный вывод ошибок в файл ls-output.txt:

ls-output.txt 2>&1

Если порядок перенаправления изменить, как показано ниже, стандартный вывод ошибок будет перенаправлен на экран:

2>&1 >ls-output.txt

Современные версии bash поддерживают второй, более простой метод выполне- ния перенаправления этого вида:

[me@linuxbox ~]$ ls -l /bin/usr &> ls-output.txt

В данном примере используется единственный оператор &>, перенаправляющий стандартный вывод и стандартный вывод ошибок в файл ls-output.txt. Аналогично можно перенаправить стандартный вывод и стандартный вывод ошибок с добав- лением в конец одного и того же файла:

[me@linuxbox ~]$ ls -l /bin/usr &» ls-output.txt

Удаление нежелательного вывода

Иногда молчание действительно золото, и вывод команды нужно отбросить. В особенности это касается служебных сообщений и сообщений об ошибках. Си- стема дает такую возможность, предоставляя специальный файл /dev/null, куда можно перенаправить вывод. Этот файл представляет системное устройство, на- зываемое битоприемником (bit bucket) или мусорной корзиной, которое прини- мает любой ввод и ничего с ним не делает. Чтобы подавить вывод сообщений об ошибках, достаточно проделать следующее:

[me@linuxbox ~]$ ls -l /bin/usr 2> /dev/null

/DEV/NULL В КУЛЬТУРЕ UNIX

«Битоприемник» — старое понятие в Unix, благодаря своему универсализму ши- роко используется в культуре Unix. Так, когда кто-то скажет, что посылает ваши комментарии в /dev/null, вы теперь будете знать, что это означает. Еще больше примеров вы найдете в статье Википедии https://ru.wikipedia.org/wiki//dev/null.

Перенаправление стандартного ввода

93

Перенаправление стандартного ввода

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

cat — объединение файлов

Команда cat читает содержимое одного или нескольких файлов и копирует его в стандартный вывод:

cat [файл…]

Часто команду cat можно считать аналогом команды TYPE в DOS. Она использу- ется для вывода содержимого файлов без возможности постраничного просмотра. Например,

[me@linuxbox ~]$ cat ls-output.txt

выведет содержимое файла ls-output.txt.

Команда cat часто используется для вывода коротких текстовых файлов. По- скольку cat способна принимать сразу несколько файлов, она используется для их объединения. Представьте, что вы загрузили большой файл, разбитый на мно- жество частей (в Usenet мультимедийные файлы часто разбиваются таким спосо- бом), и требуется объединить их в один файл. Если файлы имеют имена, такие как

movie.mpeg.001 movie.mpeg.002 … movie.mpeg.099

их можно объединить следующей командой:

[me@linuxbox ~]$ cat movie.mpeg.0* > movie.mpeg

Поскольку подстановка фактических имен взамен групповых символов всегда вы- полняется в порядке сортировки, аргументы окажутся расположенными в пра- вильном порядке.

Все это прекрасно, но при чем здесь стандартный ввод? Пока ни при чем, но да- вайте попробуем кое-что еще. Что получится, если вызвать cat без аргументов?

[me@linuxbox ~]$ cat

Ничего не произошло — такое ощущение, что команда зависла. Однако в действи- тельности команда делает именно то, что и предполагалось.

94

Глава 6. Перенаправление

Если вызвать cat без аргументов, она начнет читать данные со стандартного ввода, а поскольку стандартный ввод по умолчанию подключен к клавиатуре, получает- ся, что команда ждет, пока вы что-нибудь напечатаете! Попробуйте так:

[me@linuxbox ~]$ cat Съешь ещё этих мягких французских булок, да выпей чаю.

Затем нажмите комбинацию CTRL+D (то есть, удерживая нажатой клавишу CTRL, нажмите клавишу D), чтобы сообщить команде cat, что достигнут конец файла (end-of-file, EOF) на стандартном вводе:

[me@linuxbox ~]$ cat Съешь ещё этих мягких французских булок, да выпей чаю. Съешь ещё этих мягких французских булок, да выпей чаю.

В отсутствие аргументов с именами файлов cat копирует содержимое стандарт- ного ввода в стандартный вывод, поэтому-то мы и увидели, как она повторила введенную нами строку. Эту ее особенность можно использовать для создания коротких текстовых файлов. Представьте, что вам потребовалось создать файл с именем eat_more.txt, содержащий текст из примера, приведенного выше. Сделать это можно было бы так:

[me@linuxbox ~]$ cat > eat_more.txt Съешь ещё этих мягких французских булок, да выпей чаю.

Введите команду, затем текст, который нужно поместить в файл, и не забудьте на- жать комбинацию CTRL+D в конце. Используя командную строку, мы реализовали самый простой в мире текстовый процессор! Чтобы увидеть результат, воспользу- емся командой cat и скопируем файл в стандартный вывод:

[me@linuxbox ~]$ cat eat_more.txt Съешь ещё этих мягких французских булок, да выпей чаю.

Теперь, когда мы знаем, что команда cat может принимать данные не только из файлов, указанных в аргументах, но и со стандартного ввода, попробуем выпол- нить перенаправление стандартного ввода:

[me@linuxbox ~]$ cat . Выражаясь простым языком, оператор перенаправ- ления связывает команду с файлом, а оператор конвейера связывает вывод одной команды с вводом другой.

команда1 > файл1 команда1 | команда2

Многие, впервые познакомившись с идеей конвейера, пробуют проделать следу- ющий трюк, «только чтобы посмотреть, что из этого получится»:

команда1 > команда2

Не повторяйте этот эксперимент: иногда он может стать причиной больших про- блем.

Вот конкретный пример, представленный читателем, который по роду своей дея- тельности занимается администрированием сервера с ОС Linux. Зарегистрировав- шись как суперпользователь, он выполнил следующую пару команд:

cd /usr/bin #

ls > less #

Первая команда выполнила переход в каталог, где хранится большинство про- грамм, а вторая потребовала от командной оболочки очистить файл less и записать в него вывод команды ls. Так как в каталоге /usr/bin уже имеется файл с именем less (программа less), вторая команда затерла программу в файле less текстом, который вернула команда ls, и тем самым уничтожила программу less.

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

uniq — поиск или удаление повторяющихся строк

Команда uniq часто используется в комбинации с командой sort. uniq прини- мает сортированный список данных либо со стандартного ввода, либо из фай- ла, имя которого можно передать в единственном аргументе (за подробностями

Конвейеры

97

обращайтесь к странице справочного руководства (man) для команды uniq), и по умолчанию удаляет повторяющиеся строки из списка. Поэтому, чтобы гарантиро- вать отсутствие дубликатов в нашем списке (то есть любых программ с одинако- выми именами в каталогах /bin и /usr/bin), добавим uniq в конвейер:

[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | less

В этом примере мы использовали uniq для удаления любых повторяющихся строк в выводе команды sort. Если бы нам потребовалось, наоборот, получить список дубликатов, мы добавили бы в команду uniq параметр -d:

[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq -d | less

wc — вывод числа строк, слов и байтов

Команда wc (word count — счетчик слов) используется для подсчета числа строк, слов и байтов в файлах. Например:

[me@linuxbox ~]$ wc ls-output.txt 7902 64566 503634 ls-output.txt

В данном случае команда вывела три числа: число строк, число слов и число бай- тов в файле ls-output.txt. Подобно предыдущим командам, она может вызываться без аргументов, и в этом случае wc будет принимать данные со стандартного вво- да. Параметр -l ограничивает вывод результатов только числом строк. Команду удобно использовать в конвейерах для подсчета: например, подсчитать число эле- ментов в нашем сортированном списке можно так:

[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | wc -l 2728

grep — поиск строк, соответствующих шаблону

grep — очень мощная программа, она часто используется для поиска в файлах тек- ста по шаблону:

grep шаблон [файл…]

Когда grep находит в файле совпадение с «шаблоном», она выводит строку с най- денным совпадением. Шаблоны, используемые командой grep для поиска, могут быть очень сложными, но сейчас мы рассмотрим только поиск прямого совпаде- ния с текстом. Более сложные шаблоны, которые называют регулярными выраже- ниями, мы рассмотрим в главе 19.

98

Глава 6. Перенаправление

Допустим, что нам нужно найти все файлы в списке программ, которые имеют в своем имени последовательность символов zip. Результаты такого поиска могут подсказать нам, какие программы в системе имеют отношение к сжатию файлов. Сделать это можно так:

[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | grep zip bunzip2 bzip2 gunzip gzip unzip zip zipcloak zipgrep zipinfo zipnote zipsplit

Команда grep имеет пару удобных параметров:

 -i требует от grep игнорировать регистр символов в процессе поиска (обычно

поиск выполняется с учетом регистра символов);

 -v требует от grep выводить только строки, где совпадение с шаблоном не най-

дено.

head/tail — вывод первых/последних строк из файлов

Иногда требуется выводить не все результаты работы команды, а только несколько первых или несколько последних строк. Команда head выводит первые 10 строк из файла, а tail — последние 10 строк. По умолчанию обе команды выводят 10 строк текста, но это число можно изменить с помощью параметра -n:

[me@linuxbox ~]$ head -n 5 ls-output.txt total 343496 -rwxr-xr-x 1 root root 31316 2017-12-05 08:58 [ -rwxr-xr-x 1 root root 8240 2017-12-09 13:39 411toppm -rwxr-xr-x 1 root root 111276 2017-11-26 14:27 a2p -rwxr-xr-x 1 root root 25368 2016-10-06 20:16 a52dec [me@linuxbox ~]$ tail -n 5 ls-output.txt -rwxr-xr-x 1 root root 5234 2017-06-27 10:56 znew -rwxr-xr-x 1 root root 691 2005-09-10 04:21 zonetab2pot.py -rw-r–r– 1 root root 930 2017-11-01 12:23 zonetab2pot.pyc -rw-r–r– 1 root root 930 2017-11-01 12:23 zonetab2pot.pyo lrwxrwxrwx 1 root root 6 2016-01-31 05:22 zsoelim -> soelim

Конвейеры

99

Их также можно использовать в конвейерах:

[me@linuxbox ~]$ ls /usr/bin | tail -n 5 znew zonetab2pot.py zonetab2pot.pyc zonetab2pot.pyo zsoelim

Команда tail позволяет наблюдать, как изменяется содержимое файла в режи- ме реального времени. Эту ее особенность удобно использовать для наблюдения за появлением новых записей в файлах журналов. В следующем примере демон- стрируется наблюдение за файлом messages в каталоге /var/log. В некоторых дис- трибутивах Linux для этого требуется обладать привилегиями суперпользовате- ля, поскольку файл /var/log/messages может содержать секретную информацию.

[me@linuxbox ~]$ tail -f /var/log/messages Feb 8 13:40:05 twin4 dhclient: DHCPACK from 192.168.1.1 Feb 8 13:40:05 twin4 dhclient: bound to 192.168.1.4 – renewal in 1652 seconds. Feb 8 13:55:32 twin4 mountd[3953]: /var/NFSv4/musicbox exported to both 192.168.1.0/24 and twin7.localdomain in 192.168.1.0/24,twin7.localdomain Feb 8 14:07:37 twin4 dhclient: DHCPREQUEST on eth0 to 192.168.1.1 port 67 Feb 8 14:07:37 twin4 dhclient: DHCPACK from 192.168.1.1 Feb 8 14:07:37 twin4 dhclient: bound to 192.168.1.4 – renewal in 1771 seconds. Feb 8 14:09:56 twin4 smartd[3468]: Device: /dev/hda, SMART Prefailure Attribute: 8 Seek_Time_Performance changed from 237 to 236 Feb 8 14:10:37 twin4 mountd[3953]: /var/NFSv4/musicbox exported to both 192.168.1.0/24 and twin7.localdomain in 192.168.1.0/24,twin7.localdomain Feb 8 14:25:07 twin4 sshd(pam_unix)[29234]: session opened for user me by (uid=0) Feb 8 14:25:36 twin4 su(pam_unix)[29279]: session opened for user root by me(uid=500)

При вызове с параметром -f команда tail продолжает следить за файлом и при добавлении в конец этого файла новых строк немедленно выводит их. Так про- должается до тех пор, пока пользователь не нажмет комбинацию клавиш CTRL+C.

tee — чтение со стандартного ввода и запись в стандартный вывод и в файлы

Linux предоставляет команду tee, которая создает Т-образное разветвление в конвейере. Программа tee читает данные со стандартного ввода и копирует их в стандартный вывод (чтобы дать возможность передать их дальше по конвей- еру) и в один или несколько файлов. Это может пригодиться для сохранения промежуточных результатов обработки в конвейере. Ниже, продолжая один из

100

Глава 6. Перенаправление

предыдущих примеров, мы сохраним полный список файлов в каталогах в файле ls.txt, перед тем как он будет отфильтрован командой grep:

[me@linuxbox ~]$ ls /usr/bin | tee ls.txt | grep zip bunzip2 bzip2 gunzip gzip unzip zip zipcloak zipgrep zipinfo zipnote zipsplit

LINUX РАЗВИВАЕТ ВООБРАЖЕНИЕ

Когда меня просят объяснить разницу между Windows и Linux, я часто привожу аналогию с игрушками.

Windows — это как игровая приставка Game Boy. Вы идете в магазин и покупаете новенькую сияющую приставку с игрой в комплекте. Приносите ее домой, включа- ете и играете. Отличная графика, чудные звуки. Но спустя некоторое время игра надоедает. Вы опять идете в магазин и покупаете другую игру. Так повторяется снова и снова. Наконец, вы возвращаетесь в магазин и говорите человеку за при- лавком: «Я хочу игру, которая делает это!» — а в ответ слышите, что такой игры не существует, потому что на нее нет спроса. Тогда вы говорите: «Но мне нужно всего лишь изменить вот это!» А продавец за прилавком говорит, что это невоз- можно. Игры продаются зашитыми в картриджи. И тут вы понимаете, что ваша приставка ограничена кругом игр, при создании которых кто-то другой решил за вас, что вам нужно, а что нет.

Linux, напротив, можно сравнить с самым большим в мире конструктором. Вы от- крываете коробку и видите необозримую коллекцию деталей — огромное число железных планочек, болтиков, гаечек, шестеренок, колесиков и моторчиков и не- сколько рекомендаций по сборке. Вы начинаете играть. Сначала создаете один предлагаемый образец, затем другой. Затем вы обнаруживаете, что у вас появились собственные идеи новых конструкций и механизмов. И вам не нужно возвращаться в магазин, потому что у вас уже есть все, что требуется. Конструктор формирует ваше воображение. Он позволяет создать то, что вы хотите.

Выбор игрушки, конечно же, дело глубоко личное, но признайтесь честно: какая игрушка принесла бы вам больше удовольствия?

Заключение

101

Заключение

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

7 Взгляд на мир глазами

командной оболочки

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

echo — выводит строку текста.

Подстановка

Каждый раз, когда вы вводите команду и нажимаете ENTER, bash производит не- сколько операций с текстом, прежде чем выполнит вашу команду. Мы уже виде- ли пару примеров, где простая последовательность символов, например *, может много значить для командной оболочки. Процесс, который происходит при этом, называется подстановкой (expansion). То есть вы вводите что-то, и это что-то за- мещается чем-то другим, прежде чем командная оболочка продолжит обработку. Чтобы показать, что все это значит, возьмем для примера команду echo — встроен- ную команду, выполняющую очень простую операцию: она выводит свои тексто- вые аргументы в стандартный поток вывода.

[me@linuxbox ~]$ echo this is a test this is a test

Все очень просто. echo выведет любой свой аргумент. Давайте попробуем другой пример:

[me@linuxbox ~]$ echo * Desktop Documents ls-output.txt Music Pictures Public Templates Videos

Подстановка

103

Что это? Почему echo не вывела символ *? Как вы помните из опытов с групповы- ми символами, символ * означает «последовательность любых символов в имени файла», правда, в том обзоре не рассказывалось, как командная оболочка делает это. На самом деле все просто: перед тем, как выполнить команду echo, оболочка замещает символ * чем-то другим (в данном случае именами файлов в текущем рабочем каталоге). После нажатия клавиши ENTER командная оболочка автомати- чески производит подстановку любых групповых символов в командной строке, прежде чем выполнить ее, поэтому команда echo не увидела * — она получила уже готовый результат после подстановки. Теперь вы понимаете, что в действитель- ности echo действует в точности с нашими ожиданиями?

Подстановка путей

Механизм работы групповых символов называется подстановкой пути (pathname expansion). Если вернуться к некоторым приемам, продемонстрированным в пре- дыдущих главах, мы увидим, что в действительности они основаны на подстанов- ке. Допустим, содержимое домашнего каталога выглядит вот так:

[me@linuxbox ~]$ ls Desktop ls-output.txt Pictures Templates Documents Music Public Videos

Мы могли бы выполнить следующую подстановку:

[me@linuxbox ~]$ echo D* Desktop Documents

или

[me@linuxbox ~]$ echo *s Documents Pictures Templates Videos

или даже

[me@linuxbox ~]$ echo [[:upper:]]* Desktop Documents Music Pictures Public Templates Videos

И заглянуть за пределы домашнего каталога:

[me@linuxbox ~]$ echo /usr/*/share /usr/kerberos/share /usr/local/share

104

Глава 7. Взгляд на мир глазами командной оболочки

ПОДСТАНОВКА ПУТИ ДЛЯ СКРЫТЫХ ФАЙЛОВ

Как мы знаем, файлы с именами, начинающимися с точки, считаются скрытыми. Механизм подстановки пути также учитывает это. Подстановка, такая как

echo *

не покажет скрытые файлы.

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

echo .*

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

ls -d .* | less

Чтобы обеспечить правильную подстановку пути в такой ситуации, следует исполь- зовать специализированный шаблон. Следующий шаблон действует правильно:

ls -d .[!.]?*

Этот шаблон замещается именами файлов, начинающимися с единственной точ- ки, за которой следует любое количество других символов. Он будет работать правильно с большинством имен скрытых файлов (кроме тех, что начинаются с нескольких точек). Команда ls с параметром -A («almost all» — «почти все») выведет список всех скрытых файлов без ошибок.

ls -A

Подстановка тильды

Как вы помните из вводного пояснения команды cd, символ «тильда» (~) имеет специальное значение. Если он используется в начале слова, то замещается име- нем домашнего каталога указанного пользователя или, если пользователь не ука- зан, именем домашнего каталога текущего пользователя:

[me@linuxbox ~]$ echo ~ /home/me

Если в системе существует учетная запись пользователя foo, тогда

[me@linuxbox ~]$ echo ~foo /home/foo

Подстановка

105

Подстановка результатов арифметических выражений

Командная оболочка поддерживает также подстановку результатов арифметиче- ских выражений. Это позволяет использовать командную строку как калькуля- тор:

[me@linuxbox ~]$ echo $((2 + 2)) 4

Для подстановки арифметических выражений используется следующий формат:

$((выражение))

где выражение — это арифметическое выражение, состоящее из значений и ариф- метических операторов.

Механизм подстановки арифметических выражений позволяет использовать только целые числа (невещественные), зато поддерживает множество арифмети- ческих операций. В табл. 7.1 перечислены некоторые из поддерживаемых опера- торов.

Таблица 7.1. Арифметические операторы

Оператор

Описание

/

%

Сложение

Вычитание

Умножение

Деление (но помните, из-за того, что подстановка поддерживает только целочисленную арифметику, результатом будет целое число)

Деление по модулю или остаток от деления

**

Возведение в степень

Пробелы в арифметических выражениях не играют роли, а выражения могут со- держать вложенные выражения. Например, умножение 5^2 на 3:

[me@linuxbox ~]$ echo $(($((5**2)) * 3)) 75

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

106

Глава 7. Взгляд на мир глазами командной оболочки

переписать, как показано ниже, и получить тот же результат, но при этом будет использоваться одна операция подстановки вместо двух:

[me@linuxbox ~]$ echo $(((5**2) * 3)) 75

Следующий пример демонстрирует использование операторов деления и получе- ния остатка. Обратите внимание, как действует целочисленное деление:

[me@linuxbox ~]$ echo Пять разделить на два будет $((5/2)) Пять разделить на два будет 2 [me@linuxbox ~]$ echo и $((5%2)) в остатке. и 1 в остатке.

Подстановка результатов арифметических выражений подробнее будет рассма- триваться в главе 34.

Подстановка фигурных скобок

Самым малопонятным, пожалуй, выглядит результат подстановки фигурных скобок. С помощью этого механизма из одного шаблона, содержащего фигурные скобки, создается множество текстовых строк. Например:

[me@linuxbox ~]$ echo Впереди-{A,B,C}-позади Впереди-A-позади Впереди-B-позади Впереди-C-позади

Шаблоны с фигурными скобками могут содержать начальную часть, которая называется преамбулой, и заключительную часть, которая называется эпилогом. Внутри фигурных скобок находится список строк, разделенных запятыми, или диапазон целых чисел или одиночных символов. Использование пробелов внутри фигурных скобок не допускается. Ниже приводится пример с использованием диапазона целых чисел:

[me@linuxbox ~]$ echo Число_{1..5} Число_1 Число_2 Число_3 Число_4 Число_5

В версии bash 4.0 и выше целые числа можно дополнять ведущими нулями, напри- мер:

[me@linuxbox ~]$ echo {01..15} 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 [me@linuxbox ~]$ echo {001..15} 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015

В следующем примере используется диапазон символов в обратном порядке:

Подстановка

107

[me@linuxbox ~]$ 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

Допускается вложение фигурных скобок:

[me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b aA1b aA2b aB3b aB4b

Какую пользу можно извлечь из этого? Такая возможность может пригодиться для формирования списков файлов или каталогов, которые требуется создать. Например, фотограф, имеющий огромную коллекцию фотографий и желающий организовать ее по годам и месяцам, мог бы начать с создания группы каталогов с именами, состоящими из номера года и месяца. Благодаря этому имена ката- логов будут отсортированы в хронологическом порядке. Можно было бы ввести полный список каталогов, но это обременительно и чревато ошибками. Вместо этого выполним следующую команду:

[me@linuxbox ~]$ mkdir Photos [me@linuxbox ~]$ cd Photos [me@linuxbox Photos]$ mkdir {2007..2009}-{01..12} [me@linuxbox Photos]$ ls 2007-01 2007-07 2008-01 2008-07 2009-01 2009-07 2007-02 2007-08 2008-02 2008-08 2009-02 2009-08 2007-03 2007-09 2008-03 2008-09 2009-03 2009-09 2007-04 2007-10 2008-04 2008-10 2009-04 2009-10 2007-05 2007-11 2008-05 2008-11 2009-05 2009-11 2007-06 2007-12 2008-06 2008-12 2009-06 2009-12

Однако!

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

В этой главе мы лишь кратко коснемся подстановки параметров, а детально об- судим ее позже. Эта возможность полезнее в сценариях на языке командной обо- лочки, чем непосредственно в командной строке. Многие из ее возможностей имеют отношение к способности системы хранить маленькие фрагменты данных и присваивать этим фрагментам имена. Многие такие фрагменты, правильнее их называть переменными, уже существуют и доступны для исследования. На- пример, переменная с именем USER хранит ваше имя пользователя. Подстановка параметра и получение содержимого переменной USER выполняется следующим образом:

[me@linuxbox ~]$ echo $USER me

108

Глава 7. Взгляд на мир глазами командной оболочки

Чтобы увидеть список доступных переменных, выполните следующую команду:

[me@linuxbox ~]$ printenv | less

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

[me@linuxbox ~]$ echo $SUER [me@linuxbox ~]$

Подстановка команд

Подстановка команд позволяет использовать поток вывода команд в качестве ар- гументов других команд:

[me@linuxbox ~]$ echo $(ls) Desktop Documents ls-output.txt Music Pictures Public Templates Videos

Один из моих любимых вариантов выглядит так:

[me@linuxbox ~]$ ls -l $(which cp) -rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp

Здесь результат команды which cp передается как аргумент команде ls, благодаря чему мы получаем информацию о программе cp, не зная полного пути к ней. Под- становка команд не ограничивается такими простыми командами. Можно исполь- зовать целые конвейеры (здесь показана только часть вывода):

[me@linuxbox ~]$ file $(ls -d /usr/bin/* | grep zip) /usr/bin/bunzip2: symbolic link to bzip2' /usr/bin/bzip2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV ), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped /usr/bin/bzip2recover: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped /usr/bin/funzip: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped /usr/bin/gpg-zip: Bourne shell script text executable /usr/bin/gunzip: symbolic link to ../../bin/gunzip’ /usr/bin/gzip: symbolic link to ../../bin/gzip' /usr/bin/mzip: symbolic link to mtools'

В этом примере результаты конвейера превратились в список аргументов коман- ды file.

Экранирование

109

Механизм подстановки команд имеет альтернативный синтаксис, унаследо- ванный от более старых командных оболочек, который также поддерживается в bash. В нем вместо знака доллара и круглых скобок используются обратные апострофы:

[me@linuxbox ~]$ ls -l which cp -rwxr-xr-x 1 root root 71516 2012-12-05 08:58 /bin/cp

Экранирование

Теперь, после знакомства с множеством способов подстановки, поддерживаемых командной оболочкой, можно начинать учиться управлять ими. Например, взгля- ните на эту команду:

[me@linuxbox ~]$ echo this is a test this is a test

Или на эту:

[me@linuxbox ~]$ echo Итого $100.00 Итого 00.00

В первом примере механизм разбиения на слова удалил дополнительные пробелы из списка аргументов команды echo. Во втором — механизм подстановки параме- тров подставил пустую строку вместо $1, потому что не нашел такую переменную. Командная оболочка предоставляет механизм, который называется экранирова- нием (quoting), для выборочного подавления нежелательной подстановки.

Двойные кавычки

Первый тип экранирования, который мы рассмотрим, — двойные кавычки. Если заключить текст в двойные кавычки, все специальные символы потеряют свое специальное значение и будут интерпретироваться как обычные символы. Ис- ключение составляют: $ (знак доллара), \ (обратный слеш) и ` (обратный апо- строф). То есть разбиение на слова, подстановка путей, подстановка тильды и подстановка фигурных скобок выполняться не будут, но подстановка параме- тров, подстановка значений арифметических выражений и подстановка команд все еще будут выполняться. Благодаря двойным кавычкам мы сможем обраба- тывать имена файлов с пробелами. Представьте, что мы по ошибке создали файл с именем Два слова.txt. Если попытаться использовать это имя в командной строке, механизм разбиения слов будет интерпретировать его как два отдельных аргумента:

110

Глава 7. Взгляд на мир глазами командной оболочки

[me@linuxbox ~]$ ls -l Два слова.txt ls: невозможно получить доступ к ‘Два’: Нет такого файла или каталога ls: невозможно получить доступ к ‘слова.txt’: Нет такого файла или каталога

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

[me@linuxbox ~]$ ls -l “Два слова.txt” -rw-rw-r– 1 me me 18 2012-02-20 13:03 Два слова.txt [me@linuxbox ~]$ mv “Два слова.txt” Два_слова.txt

Вот так! Теперь не нужно вводить эти противные двойные кавычки.

Запомните: подстановка параметров, подстановка значений арифметических вы- ражений и подстановка команд все еще выполняются в двойных кавычках:

[me@linuxbox ~]$ echo “$USER $((2+2)) $(cal)” me 4 February 2020 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

Давайте отвлечемся и посмотрим, какой эффект оказывают двойные кавычки на подстановку команд. Сначала рассмотрим действие механизма разбиения на сло- ва. В одном из примеров, приведенных выше, мы видели, как механизм разбиения на слова удаляет дополнительные пробелы из текста:

[me@linuxbox ~]$ echo this is a test this is a test

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

[me@linuxbox ~]$ echo “this is a test” this is a test

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

Экранирование

111

Тот факт, что символы перевода строки интерпретируются механизмом разбие- ния на слова как разделители, вызывает интересный и трудноуловимый эффект при подстановке команд. Взгляните:

[me@linuxbox ~]$ echo $(cal) February 2020 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [me@linuxbox ~]$ echo “$(cal)” February 2020 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

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

Одиночные кавычки

Если вам требуется подавить все подстановки, используйте одиночные кавычки. Ниже для сравнения приводятся результаты неэкранированной команды и коман- ды, экранированной двойными и одиночными кавычками:

[me@linuxbox ~]$ echo text ~/.txt {a,b} $(echo foo) $((2+2)) $USER text /home/me/ls-output.txt a b foo 4 me [me@linuxbox ~]$ echo “text ~/.txt {a,b} $(echo foo) $((2+2)) $USER” text ~/.txt {a,b} foo 4 me [me@linuxbox ~]$ echo ’text ~/.txt {a,b} $(echo foo) $((2+2)) $USER’ text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER

Как видите, каждый следующий уровень экранирования все больше и больше по- давляет подстановку.

Экранирование символов

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

[me@linuxbox ~]$ echo “Баланс счета пользователя $USER: $5.00” Баланс счета пользователя me: $5.00

112

Глава 7. Взгляд на мир глазами командной оболочки

Экранирование символов также широко применяется для подавления специаль- ного значения символов в именах файлов. Например, в именах файлов допускает- ся использование символов, которые имеют специальное значение для командной оболочки. К их числу относятся: $, !, &, пробел и др. Чтобы включить специальный символ в имя файла, его достаточно экранировать, как показано ниже:

[me@linuxbox ~]$ mv bad&filename good_filename

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

Управляющие последовательности

Обратный слеш используется не только в роли экранирующего символа, но и как часть специальных символов, которые называют управляющими кодами (control codes). Первые 32 символа в схеме кодирования ASCII использовались для передачи различных команд в устройствах, таких как телетайп. Некоторые из этих кодов хорошо знакомы вам (табуляция, забой, перевод строки и возврат каретки), тогда как другие — нет (пустой символ, конец передачи и подтверж- дение).

В табл. 7.2 перечислены некоторые наиболее известные управляющие последова- тельности.

Таблица 7.2. Управляющие последовательности

Управляющая после- довательность

Значение

\a

\b

\n

\r

\t

Звонок («предупреждение» — заставляет компьютер подать звуковой сигнал)

Забой (backspace)

Новая строка (в Unix-подобных системах этот символ выполняет перевод строки)

Возврат каретки

Табуляция

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

Заключение

113

Параметр -e команды echo включает интерпретацию управляющих последова- тельностей. Их можно также заключать в конструкцию $’ ‘. Ниже демонстриру- ется использование команды sleep для создания элементарного таймера, простой программы, которая всего лишь ждет указанное число секунд и завершается.

sleep 10; echo -e “Time’s up\a”

То же самое можно выразить так:

sleep 10; echo “Time’s up” $’\a’

Заключение

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

8 Продвинутые приемы

работы с клавиатурой

Я часто шутливо описываю Unix как «операционную систему для тех, кто любит печатать». Казалось бы, сам факт наличия командной строки доказывает это. Но в действительности пользователи командной строки не любят печатать слишком много. Зачем, если есть так много команд с короткими именами, таких как cp, ls, mv и rm?

Фактически одной из самых заветных целей командной строки является умень- шение объема ввода — возможность выполнить большую часть работы всего не- сколькими нажатиями клавиш. Другая цель — не позволить рукам оторваться от клавиатуры и коснуться мыши. В этой главе мы рассмотрим возможности bash, увеличивающие скорость и эффективность использования клавиатуры.

Здесь будут представлены следующие команды:

clear — очищает экран;

history — выводит содержимое истории команд.

Редактирование командной строки

Для поддержки операций редактирования командной строки bash использует би- блиотеку (коллекцию подпрограмм, которую могут использовать разные програм- мы) с именем Readline. Мы уже видели некоторые из них. Например, нам знако- мы клавиши со стрелками влево и вправо, перемещающие курсор, но существует еще целое множество других операций. Рассматривайте их как дополнительные

Редактирование командной строки

115

инструменты, которые можно использовать в работе. Необязательно стремиться изучить их все, но многие из них весьма практичны. Выбирайте те, что вам по- нравятся.

ПРИМЕЧАНИЕ

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

Перемещение курсора

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

Таблица 8.1. Команды перемещения курсора

Клавиша Действие

CTRL+A

Перемещает курсор в начало строки

CTRL+E

Перемещает курсор в конец строки

CTRL+F

CTRL+B

Перемещает курсор на один символ вперед; действует так же, как клавиша со стрелкой вправо

Перемещает курсор на один символ назад; действует так же, как клавиша со стрелкой влево

ALT+F

Перемещает курсор на одно слово вперед

ALT+B

Перемещает курсор на одно слово назад

CTRL+L

Очищает экран и устанавливает курсор в левый верхний угол. То же самое делает команда clear

Изменение текста

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

116

Глава 8. Продвинутые приемы работы с клавиатурой

КЛАВИША META

Отважившиеся заглянуть в документацию к Readline, которая находится в разделе «READLINE», на странице справочного руководства (man) для bash, столкнутся с термином клавиша meta (meta key). На современных клавиатурах ей соответ- ствует клавиша ALT, но так было не всегда.

В стародавние времена (до появления IBM-совместимых персональных ком- пьютеров, но после появления Unix) персональные компьютеры не были так широко распространены. Иногда их заменяли устройства, называемые терми- налами. Терминал — это коммуникационное устройство с текстовым дисплеем и клавиатурой, имеющее внутри столько электроники, сколько необходимо для отображения символов и перемещения курсора. Терминалы подключались (обычно посредством последовательного кабеля) к большому компьютеру или коммуникационной сети большого компьютера. В то время существовало очень много различных терминалов, имевших разные клавиатуры и дисплеи с разными функциональными возможностями. Так как все они поддерживали как минимум набор символов ASCII, разработчикам программного обеспечения, пишущим переносимые приложения, необходимо было прийти к общему знаменателю. В системах Unix применяется очень сложный способ использования терминалов и их разнообразных возможностей. Поскольку разработчики Readline не были уверены в наличии специализированной управляющей клавиши, они изобрели ее и назвали meta. На современных клавиатурах роль клавиши meta играет ALT, однако если вы все еще используете терминал (до сих пор поддерживаются в Linux!), можно просто нажать и отпустить клавишу ESC, и вы получите эффект нажатия и удержания клавиши ALT.

Таблица 8.2. Команды редактирования текста

Клавиша Действие

CTRL+D

Удаляет символ в позиции курсора

CTRL+T

Меняет местами два символа — в позиции курсора и предшествующий ему

ALT+T

Меняет местами два слова — в позиции курсора и предшествующее ему

ALT+L

ALT+U

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

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

Дополнение

117

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

В документации к Readline используется термин killing and yanking (удаление и возврат), обозначающий операцию, которую обычно называют вырезанием и вставкой (cutting and pasting). В табл. 8.3 перечислены комбинации клавиш, вы- полняющие вырезание и вставку. Вырезанные элементы сохраняются в кольце- вом буфере, который называется kill-ring (кольцо удалений).

Таблица 8.3. Команды вырезания и вставки

Клавиша

Действие

CTRL+K

CTRL+U

ALT+D

Удаляет символы от позиции курсора до конца строки

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

Удаляет символы от позиции курсора до конца текущего слова

ALT+BACKSPACE

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

CTRL+Y

Извлекает текст из кольцевого буфера удалений и вставляет его в по- зицию курсора

Дополнение

Другой вариант помощи пользователям реализован в командной оболочке в виде механизма дополнения (completion). Дополнение происходит, когда в процессе ввода команды нажимается клавиша TAB. Давайте посмотрим, как это работает. Допустим, что ваш домашний каталог содержит следующее:

[me@linuxbox ~]$ ls Desktop ls-output.txt Pictures Templates Videos Documents Music Public

Попробуйте ввести следующую строку, но не нажимайте клавишу ENTER:

[me@linuxbox ~]$ ls l

Теперь нажмите клавишу TAB:

[me@linuxbox ~]$ ls ls-output.txt

Обратили ли вы внимание, как командная оболочка дополнила командную строку за вас? Попробуйте теперь набрать следующую строку — и снова не нажимайте ENTER):

[me@linuxbox ~]$ ls D

118

Глава 8. Продвинутые приемы работы с клавиатурой

Нажмите TAB:

[me@linuxbox ~]$ ls D

Дополнения не произошло — просто прозвучал звуковой сигнал. Так получилось потому, что символу D соответствует более одного элемента в каталоге. Чтобы ко- мандная оболочка дополнила вашу строку, предложенная вами «подсказка» долж- на иметь однозначное продолжение. Попробуйте продолжить ввод:

[me@linuxbox ~]$ ls Do

Затем нажмите TAB:

[me@linuxbox ~]$ ls Documents

Дополнение произошло.

Этот пример демонстрирует дополнение путей как наиболее частый случай ис- пользования дополнения. Однако дополнение также работает с именами перемен- ных (когда слово начинается с символа $), именами пользователей (когда слово начинается с символа ~), командами (когда дополняемое слово является первым в командной строке) и сетевыми именами компьютеров (когда слово начинается с символа @). Дополнение сетевых имен компьютеров действует только в отноше- нии имен, перечисленных в /etc/hosts.

С механизмом дополнения связано несколько управляющих комбинаций клавиш (табл. 8.4).

Таблица 8.4. Команды дополнения

Клавиша

Действие

ALT+?

ALT+*

Выводит список возможных дополнений. В большинстве систем тот же эффект можно получить, нажав клавишу TAB второй раз, что намного проще

Вставит все возможные дополнения. Это пригодится в том случае, если требуется использовать больше одного возможного варианта дополнения

Существует еще несколько команд, смысл которых для меня не совсем ясен. Пол- ный список вы сможете найти на странице справочного руководства (man) для bash, в разделе «READLINE».

Использование истории

119

ПРОГРАММИРУЕМОЕ ДОПОЛНЕНИЕ

Последние версии bash реализуют механизм программируемого дополнения. Программируемое дополнение дает возможность добавлять дополнительные правила. Обычно это делается с целью добавить поддержку определенных при- ложений. Например, можно добавить дополнение списка параметров команды или файлов определенного типа, поддерживаемых приложением. В Ubuntu определено огромное множество таких правил. Программируемое дополнение реализуется посредством функций командной оболочки — небольших мини-сценариев, о кото- рых будет рассказываться в следующих главах. Если вам любопытно, попробуйте выполнить команду

set | less

и вы увидите их. Однако не все дистрибутивы включают эти функции по умолчанию.

Использование истории

Как рассказывалось в главе 1, bash поддерживает историю вводившихся команд. Этот список команд хранится в домашнем каталоге, в файле с именем .bash_history. Механизм истории помогает уменьшить объем ручного ввода, особенно в сочета- нии с командами редактирования командной строки.

Поиск в истории

Просмотреть содержимое истории можно в любой момент с помощью команды:

[me@linuxbox ~]$ history | less

По умолчанию bash хранит последние 500 введенных команд, однако в большин- стве современных дистрибутивов этот предел увеличен до 1000. Как изменить это значение, мы узнаем в главе 11. А теперь представим, что вам понадобилось най- ти команды, использовавшиеся для получения списка содержимого /usr/bin. Вот один из возможных способов:

[me@linuxbox ~]$ history | grep /usr/bin

А теперь представим, что среди результатов нужно выбрать запись с интересую- щей вас командой:

88 ls -l /usr/bin > ls-output.txt

120

Глава 8. Продвинутые приемы работы с клавиатурой

Здесь число 88 — это порядковый номер записи команды в списке истории. Зная это число, можно воспользоваться еще одной разновидностью подстановки, ко- торая называется подстановкой записей истории (history expansion). Для этого введите:

[me@linuxbox ~]$ !88

и bash заменит !88 содержимым 88-й записи в списке истории. Подробнее об этой форме подстановки записей истории мы поговорим чуть ниже.

bash также дает возможность выполнять поступательный поиск в списке истории. Это означает, что bash может выполнять поиск в списке истории по мере ввода символов, уточняя результаты с вводом каждого нового символа. Чтобы запу- стить поступательный поиск, нажмите комбинацию CTRL+R и введите искомый текст. Закончив поиск, нажмите ENTER, чтобы выполнить команду, или CTRL+J, что- бы скопировать запись из списка истории в текущую командную строку. Чтобы найти следующее вхождение текста (переместиться «вверх» по списку истории), нажмите CTRL+R еще раз. Чтобы завершить поиск, нажмите CTRL+G или CTRL+C. Следующий пример демонстрирует, как действует поиск:

[me@linuxbox ~]$

Первое нажатие комбинации CTRL+R:

(reverse-i-search)`’:

Приглашение к вводу изменится, показывая, что выполняется поступательный поиск в обратном порядке. Под словами «в обратном порядке» подразумевается, что поиск выполняется от «текущего момента» до некоторого момента в прошлом. Далее мы начинаем ввод искомого текста, в данном примере /usr/bin:

(reverse-i-search)`/usr/bin’: ls -l /usr/bin > ls-output.txt

Механизм поиска сразу же возвращает результат. Теперь, чтобы выполнить най- денную команду, необходимо нажать ENTER, или вы можете скопировать команду в командную строку для дальнейшего редактирования, нажав CTRL+J. Давайте ско- пируем ее. Нажмите CTRL+J:

[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt

Механизм поиска вернет управление, командная строка заполнится и будет гото- ва для выполнения!

В табл. 8.5 перечислены некоторые комбинации клавиш, используемые для мани- пуляций со списком истории команд.

Использование истории

121

Таблица 8.5. Команды для работы с историей

Клавиша

Действие

CTRL+P

CTRL+N

Переход к предыдущей записи в истории. Действует так же, как клавиша со стрелкой вверх

Переход к следующей записи в истории. Действует так же, как клавиша со стрелкой вниз

ALT+

Переход в конец (к последней записи) списка истории

CTRL+R

ALT+P

Инкрементальный поиск в обратном порядке. Поиск выполняется поступа- тельно, от текущей записи вверх по списку истории

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

ALT+N

Поиск в прямом порядке, не поступательный

CTRL+O

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

Подстановка записей истории

Командная оболочка поддерживает специализированный вид подстановки — подстановку записей из списка истории при использовании символа !. Мы уже видели, как восклицательный знак, сопровождаемый числом, замещается запи- сью из списка истории. Этот вид подстановки имеет несколько разновидностей (табл. 8.6).

Таблица 8.6. Команды механизма подстановки записей истории

Последовательность

Действие

!!

!число

!строка

!?строка

Повторяет последнюю команду. Проще, пожалуй, нажать клави- шу со стрелкой вверх и ENTER

Повторяет команду из записи с указанным номером

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

Повторяет последнюю команду в списке истории, содержащую указанную строку

122

Глава 8. Продвинутые приемы работы с клавиатурой

Не используйте формы !строка и !?строка, если только вы абсолютно точно не знаете содержимого записей в списке истории.

Механизм подстановки записей истории поддерживает также другие комбина- ции, но эта тема становится слишком запутанной, и мы не станем перегружать себя лишней информацией. Желающие смогут обратиться к странице справочно- го руководства (man) для bash, в разделе «HISTORY EXPANSION». Загляните туда!

SCRIPT

В дополнение к истории команд в bash большинство дистрибутивов Linux вклю- чают программу script, которую можно использовать для записи в файлы целых сеансов работы с командной оболочкой. Базовый синтаксис команды:

script [файл]

где файл — это имя файла для записи. Если файл не будет указан, сохранение сеанса будет произведено в файл typescript. Полное описание параметров и возможностей программы можно найти на странице справочного руководства (man) для script.

Заключение

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

9 Привилегии

Операционные системы, следующие традициям Unix, отличаются от систем, сле- дующих традициям MS-DOS, тем, что являются не только многозадачными, но и многопользовательскими.

Что это означает на самом деле? Это означает, что компьютером могут одновре- менно пользоваться несколько человек. Несмотря на то что обычно компьютер имеет всего одну клавиатуру и монитор, это обстоятельство не мешает совмест- ному пользованию. Например, если компьютер подключен к локальной сети или к Интернету, удаленные пользователи смогут зайти на него через ssh (secure shell — безопасная командная оболочка) и выполнять операции. Фактически уда- ленные пользователи могут запускать приложения с графическим интерфейсом и получать изображение на удаленном дисплее. X Window System поддерживает такую возможность изначально.

Поддержка многопользовательского режима работы — не недавнее «изобретение» Linux, а возможность, глубоко внедренная в архитектуру операционной системы. Учитывая окружение, в котором создавалась система Unix, это имело определен- ный смысл. В те времена, когда компьютеры еще не были «персональными», они были большими и дорогими. Типичная компьютерная система университета, на- пример, состояла из большого центрального компьютера в одном здании и терми- налов, разбросанных по всему университетскому городку и соединенных с боль- шим центральным компьютером. Компьютер мог одновременно обслуживать множество пользователей.

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

124

Глава 9. Привилегии

В данной главе мы рассмотрим эту важную сторону безопасности системы и по- знакомимся со следующими командами:

id — выводит информацию об идентичности пользователя;

chmod — изменяет режим доступа к файлу;

umask — определяет разрешения доступа к файлам по умолчанию;

su — запускает командную оболочку от имени другого пользователя;

sudo — выполняет команду от имени другого пользователя;

chown — изменяет владельца файла;

chgrp — изменяет группу файла;

passwd — изменяет пароль пользователя.

Владельцы, члены группы и все остальные

Знакомясь с системой в главе 4, вы уже сталкивались со следующей проблемой при исследовании файлов, таких как /etc/shadow:

[me@linuxbox ~]$ file /etc/shadow /etc/shadow: Обычный файл, нет прав на чтение [me@linuxbox ~]$ less /etc/shadow /etc/shadow: Отказано в доступе

Причина этого сообщения об ошибке заключается в том, что обычные пользовате- ли не имеют права читать этот файл.

В модели безопасности Unix пользователь может владеть файлами и каталогами. Если пользователь владеет файлом или каталогом, он может управлять доступом к нему. Пользователи могут также принадлежать группе, состоящей из одного или нескольких пользователей, и получить права доступа к файлам и каталогам для членов группы, которые определяются владельцами. Кроме прав доступа для группы, владелец может также определить некоторые права доступа для всех остальных, их в терминологии Unix называют мир (world). Получить информа- цию о своей идентичности можно с помощью команды id:

[me@linuxbox ~]$ id uid=500(me) gid=500(me) groups=500(me)

Владельцы, члены группы и все остальные

125

Давайте рассмотрим этот вывод. Когда создается учетная запись пользовате- ля, ей присваивается число, которое называют идентификатором пользователя (user ID), или uid. Это число, исключительно ради удобства человека, отобра- жается как имя пользователя. Пользователю назначается идентификатор ос- новной группы (primary group ID), или gid, и дополнительно пользователь мо- жет включаться в состав других групп. Предыдущий пример взят из системы Fedora. В других системах, таких как Ubuntu, вывод команды может немного отличаться.

[me@linuxbox ~]$ id uid=1000(me) gid=1000(me) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio), 30(dip),44(video), 46(plugdev),108(lpadmin),114(admin),1000(me)

Как видите, в Ubuntu числа uid и gid отличаются. Это объясняется тем, что в Fedora нумерация учетных записей обычных пользователей начинается с 500, тогда как в Ubuntu — с 1000. Кроме того, пользователь в Ubuntu принадлежит множеству других групп. Это связано с особенностями управления привилегиями доступа к системным устройствам и службам в Ubuntu.

А где же вся эта информация хранится? Как и многое другое в Linux, она хра- нится в паре текстовых файлов. Учетные записи пользователей хранятся в фай- ле /etc/passwd, а информация о группах — в файле /etc/group. Когда создаются новые учетные записи и группы, эти файлы изменяются вместе с файлом /etc/ shadow, где хранится информация о пароле пользователя. Для каждой учетной за- писи в файле /etc/passwd определяется имя пользователя (для входа), числовой идентификатор пользователя (uid), числовой идентификатор основной группы (gid), действительное имя пользователя, путь к домашнему каталогу и команд- ная оболочка входа (login shell). Заглянув внутрь /etc/passwd и /etc/group, можно заметить, что помимо учетных записей обычных пользователей здесь также хра- нятся учетные записи суперпользователя (uid 0) и различных других системных пользователей.

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

Несмотря на то что во многих Unix-подобных системах обычных пользователей включают в общую группу, такую как users, в современных дистрибутивах Linux принято создавать для каждого пользователя свою, уникальную группу с одним членом и именем, совпадающим с именем пользователя. Это упрощает распреде- ление определенных типов привилегий.

126

Глава 9. Привилегии

Чтение, запись и выполнение

Права доступа к файлам и каталогам определяются в терминах права на чтение, права на запись и права на выполнение. Если взглянуть на вывод команды ls, можно увидеть некоторые подсказки о том, как эти права реализованы:

[me@linuxbox ~]$ > foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw-rw-r– 1 me me 0 2018-03-06 14:52 foo.txt

Первые 10 символов в выводе — это атрибуты файла. Первый из этих символов определяет тип файла. В табл. 9.1 перечислены типы файлов, которые чаще все- го встречаются на практике (существуют также другие, реже используемые типы файлов).

Таблица 9.1. Типы файлов

Атрибут

Тип файла

d

l

c

b

Обычный файл

Каталог

Символическая ссылка. Обратите внимание, что для символических ссылок все остальные атрибуты имеют значение rwxrwxrwx и не отражают действи- тельные права доступа. Фактические права доступа к файлу определяются атрибутами самого файла, на который указывает символическая ссылка

Специальный файл символьного устройства. Файлы этого типа соответ- ствуют устройствам, таким как терминал или модем, которые обрабатывают данные как потоки байтов

Специальный файл блочного устройства. Файлы этого типа соответствуют устройствам, таким как привод жесткого диска или CD-ROM, которые обраба- тывают данные блоками

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

Владелец

Группа

rwx

rwx

Мир

rwx

Установленные атрибуты режима r, w и x оказывают определенное влияние на файлы и каталоги, как показано в табл. 9.2.

Чтение, запись и выполнение

127

Таблица 9.2. Атрибуты прав доступа

Атрибут Файлы

Каталоги

r

w

x

Разрешается открывать и читать содержимое файла

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

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

Разрешается читать содер- жимое каталога, если вместе с этим атрибутом установлен атрибут права на выполнение

Разрешается создавать, удалять и переименовывать файлы внутри каталога, если вместе с этим атрибутом установлен атрибут права на выполнение

Разрешается входить в ката- лог, то есть выполнять коман- ду cd для перехода в него

В табл. 9.3 приводится несколько примеров установки атрибутов файлов.

Таблица 9.3. Примеры установки атрибутов прав доступа к файлам

Атрибуты файлов Значение

-rwx——

-rw——-

-rw-r–r–

-rwxr-xr-x

-rw-rw—-

Lrwxrwxrwx

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

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

Обычный файл, доступный владельцу для чтения и записи. Чле- ны группы имеют право читать файл. Все остальные имеют право читать файл

Обычный файл, доступный владельцу для чтения, записи и выпол- нения. Все остальные имеют право читать и выполнять файл

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

Символическая ссылка. Все символические ссылки имеют недействи- тельные значения атрибутов. Фактические права доступа к файлу определяются атрибутами самого файла, на который указывает символическая ссылка

128

Глава 9. Привилегии

Таблица 9.3 (окончание)

Атрибуты файлов Значение

drwxrwx—

drwxr-x—

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

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

chmod — изменение режима доступа к файлу

Для изменения режима (прав) доступа к файлу или каталогу используется коман- да chmod. Имейте в виду, что права доступа к файлу или каталогу может изменить только владелец или суперпользователь. Команда chmod поддерживает два разных способа изменения режима:

 с использованием восьмеричных чисел;

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

Сначала рассмотрим использование восьмеричных чисел. При использовании восьмеричной формы записи шаблон желаемых привилегий определяется вось- меричными числами. Так как каждая цифра в восьмеричном числе определяется тремя двоичными разрядами, она точно укладывается в схему хранения режима доступа к файлу. В табл. 9.4 поясняется, что мы имеем в виду.

Таблица 9.4. Режимы доступа к файлу в двоичном и восьмеричном представлениях

Восьмеричное

Двоичное

Режим доступа

0

1

2

3

4

5

6

7

000

001

010

011

100

101

110

111


–x

-w-

-wx

r–

r-x

rw-

rwx

Чтение, запись и выполнение

129

А ПОЧЕМУ ИМЕННО ВОСЬМЕРИЧНОЕ?

Восьмеричная (по основанию 8) и родственная ей шестнадцатеричная (по основанию 16) системы счисления часто используются для представления чисел в компьютерах. Мы, люди, рождаемся с десятью пальцами на руках (по крайней мере большинство из нас), поэтому для счета используем систему счисления с основанием 10. Компьютеры, напротив, рождаются с одним пальцем и потому используют для вычисления двоичную систему счисления (по основанию 2). Их числа состоят всего из двух цифр, нуля и единицы. Поэтому в двоичной системе счет выглядит так:

0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010, 1011…

В восьмеричной системе используются цифры от нуля до семи:

0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21…

В шестнадцатеричной системе используются цифры от нуля до девяти плюс буквы от A до F:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13…

В двоичной системе счисления еще можно увидеть смысл (поскольку компьютеры имеют лишь один палец), но в чем польза восьмеричной и шестнадцатеричной систем счисления? Они были придуманы для удобства человека. Очень часто небольшие порции данных представляются в компьютерах битовыми шаблонами. Примером может служить представление цвета в формате RGB. В большинстве дисплеев компьютеров цвет каждого пиксела определяется тремя цветовыми составляющими: 8 бит для красного цвета, 8 бит для зеленого и 8 бит для синего. Красивый сине-голубой цвет можно представить в виде 24-разрядного числа:

010000110110111111001101.

Хотели бы вы видеть и читать такие числа весь день? Я так не думаю. Именно в таких случаях на выручку приходят другие системы счисления. Каждая цифра в шестнадцатеричной системе счисления представляет четыре двоичные цифры. В восьмеричной системе каждой цифре соответствуют три двоичные цифры. То есть 24-значное значение сине-голубого цвета можно сжать до 6-значного шестнадцатеричного числа: 436FCD. Поскольку цифры в шестнадцатеричных числах «выстраиваются в ряд» с битами в двоичных числах, можно заметить, что красный компонент нашего цвета имеет значение 43, зеленый — 6F и си- ний — CD.

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

130

Глава 9. Привилегии

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

[me@linuxbox ~]$ > foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw-rw-r– 1 me me 0 2018-03-06 14:52 foo.txt [me@linuxbox ~]$ chmod 600 foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw——- 1 me me 0 2018-03-06 14:52 foo.txt

Передав аргумент 600, мы установили права для владельца, позволяющие ему читать данные из файла и записывать их в файл, и при этом отобрали все права у группы и остального мира. Несмотря на кажущееся неудобство необходимости запоминания соответствий между восьмеричными и двоичными представления- ми, вам, скорее всего, придется использовать лишь несколько наиболее популяр- ных шаблонов: 7 (rwx), 6 (rw-), 5 (r-x), 4 (r–) и 0 (—).

Команда chmod поддерживает также символическую форму определения режимов доступа к файлу. Символическая форма записи делится на три части:

 для кого устанавливаются разрешения;

 какие операции с разрешениями будут выполняться;

 на какие разрешения эти операции будут влиять.

Чтобы указать, для кого устанавливаются разрешения, используется комбинация символов u, g, o и a, как показано в табл. 9.5.

Таблица 9.5. Символическая форма записи аргументов команды chmod

Символ

Значение

u

g

o

a

Сокращенно от user (пользователь), означает владельца файла или каталога

Группа

Сокращенно от others (другие, остальные), означает весь остальной мир

Сокращенно от all (все); комбинация из всех трех символов: u, g и o

Если не указан ни один символ, предполагается a (all — все). Операцией может быть знак +, соответствующий добавлению заданных разрешений, знак -, соответ- ствующий отъему заданных разрешений, или знак =, указывающий, что только за- данные разрешения должны быть установлены, а все остальные отобраны.

Чтение, запись и выполнение

131

Разрешения определяются символами r, w и x. В табл. 9.6 перечислены некоторые примеры символической формы записи.

Таблица 9.6. Примеры символической формы записи прав доступа к файлам

Атрибуты файлов

Значение

u+x

u-x

+x

o-rw

go=rw

u+x,go=rx

Добавляет право на выполнение, но только для владельца

Отнимает право на выполнение у владельца

Добавляет право на выполнение для владельца, группы и осталь- ного мира. Эквивалент записи a+x

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

Устанавливает право на чтение и запись для всех, кроме владель- ца. Если прежде файл имел разрешение на выполнение для группы и всего мира, это право отнимается

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

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

Дополнительную информацию и полный список параметров команды chmod мож- но найти на странице справочного руководства (man). А теперь несколько слов о параметре –recursive: он воздействует и на файлы, и на каталоги, поэтому он не так полезен, как можно было бы предположить, потому что редко требуется устанавливать одинаковые разрешения для файлов и каталогов.

Установка режима доступа к файлу с помощью графического интерфейса

Теперь, ознакомившись с тем, как устанавливаются разрешения для файлов и ка- талогов, вы лучше поймете диалоги установки разрешений в графическом интер- фейсе. В Files (GNOME) и Dolphin (KDE) можно щелкнуть правой кнопкой мыши на файле или на каталоге и вывести диалог со свойствами. На рис. 9.1 изображен такой диалог из GNOME. Здесь вы видите, какие разрешения установлены для владельца, группы и остального мира.

132

Глава 9. Привилегии

Рис. 9.1. Диалог с правами доступа к файлу в GNOME

umask — определение разрешений доступа к файлам по умолчанию

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

[me@linuxbox ~]$ rm -f foo.txt [me@linuxbox ~]$ umask 0002 [me@linuxbox ~]$ > foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw-rw-r– 1 me me 0 2018-03-06 14:53 foo.txt

Сначала мы удалили существующий файл foo.txt, чтобы, так сказать, начать с чи- стого листа. Далее мы выполнили команду umask без аргумента, чтобы увидеть текущее значение маски. Она вернула нам значение 0002 (часто также использует- ся значение 0022) — восьмеричное представление действующей маски. Затем мы создали новый файл foo.txt и вывели для него разрешения.

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

Чтение, запись и выполнение

133

[me@linuxbox ~]$ rm foo.txt [me@linuxbox ~]$ umask 0000 [me@linuxbox ~]$ > foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw-rw-rw- 1 me me 0 2018-03-06 14:58 foo.txt

После установки маски в значение 0000 (таким способом мы фактически выклю- чили ее) вновь созданный файл получил разрешение на запись для всего мира. Чтобы лучше понять суть происходящего, мы снова должны вернуться к восьме- ричным числам. Если развернуть маску в двоичное представление и сравнить ее с двоичным представлением атрибутов, можно понять, что произошло:

Исходный режим доступа к файлу

Маска

Результат

— rw- rw- rw-

000 000 000 010

— rw- rw- r–

Забудем пока про начальные нули (мы вернемся к ним чуть позже) и обратим внимание, что атрибут, соответствующий той позиции, где в маске стоит 1, был сброшен, — в данном случае право на запись для всего мира. Теперь понятно, что делает маска. В любой позиции, где в маске появляется 1, соответствующий атри- бут сбрасывается. Если посмотреть на значение маски 0022, легко увидеть, что оно делает:

Исходный режим доступа к файлу

Маска

Результат

— rw- rw- rw-

000 000 010 010

— rw- r– r–

И снова атрибуты, соответствующие позициям, где в маске стоит 1, были сбро- шены. Поэкспериментируйте с другими значениями (попробуйте несколько 7), чтобы лучше усвоить, как действует маска. Закончив эксперименты, не забудьте все вернуть в исходное состояние:

[me@linuxbox ~]$ rm foo.txt; umask 0002

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

134

Глава 9. Привилегии

Некоторые специальные разрешения

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

Первый атрибут — бит setuid (восьмеричное значение 4000). Если это разреше- ние применяется к выполняемому файлу, в качестве эффективного идентифика- тора пользователя для процесса устанавливается не идентификатор реального пользователя (пользователя, фактически запустившего программу), а иденти- фикатор владельца программы. Чаще этот бит устанавливается для программ, владельцем которых является суперпользователь. Когда обычный пользователь запускает программу с установленным битом setuid, принадлежащую пользова- телю root, программа выполняется с эффективными привилегиями суперполь- зователя. Это дает возможность программам обращаться к файлам и каталогам, недоступным для обычного пользователя. Очевидно, что из-за возникающих проблем безопасности число таких программ в системе должно быть сведено к минимуму.

Второй редко используемый атрибут — бит setgid (восьмеричное значение 2000). По аналогии с битом setuid он устанавливает эффективный идентификатор груп- пы для процесса, выбирая вместо идентификатора группы реального пользователя группу владельца файла. Если установить бит setgid для каталога, вновь создава- емые файлы в этом каталоге будут принадлежать группе владельца каталога, а не группе владельца файла, создавшего его. Это разрешение может пригодиться для установки на каталоги, содержимое которых должно быть доступно всем членам основной группы владельца каталога, независимо от принадлежности к основной группе владельца файла.

Третий атрибут называется битом sticky (восьмеричное значение 1000). Это пере- житок, оставшийся от первых версий Unix, которые предоставляли возможность пометить выполняемый файл как «невытесняемый». Linux игнорирует бит sticky у файлов, но если установить его для каталога, он не позволит пользователю уда- лять или переименовывать файлы, если только пользователь не является владель- цем каталога, владельцем файла или суперпользователем. Это разрешение часто применяется для управления доступом к общим каталогам, таким как /tmp.

Ниже приводится несколько примеров использования chmod с символической формой определения этих специальных разрешений. Первый пример — установка бита setuid на файл программы:

chmod u+s program

Изменение идентичности

135

Далее — установка бита setgid на каталог:

chmod g+s dir

Наконец, установка бита sticky на каталог:

chmod +t dir

Специальные разрешения мы видим в выводе команды ls. Ниже приводится не- сколько примеров. Первый — программа с битом setuid:

-rwsr-xr-x

Теперь — каталог с атрибутом setgid:

drwxrwsr-x

Наконец, каталог с битом sticky:

drwxrwxrwt

Изменение идентичности

Время от времени возникает необходимость приобрести идентичность другого пользователя. Обычно это требуется для получения привилегий суперпользо- вателя, чтобы выполнить некоторые административные задачи, но точно так же можно «превратиться» в другого обычного пользователя, чтобы, к примеру, про- верить настройки учетной записи. Существует три способа приобрести альтерна- тивную идентичность:

  1. Выйти из системы и войти вновь с учетными данными другого пользователя.
  2. Воспользоваться командой su.
  3. Воспользоваться командой sudo.

Мы пропустим первый способ, потому что уже знаем, как им воспользоваться, и он не так удобен, как два других. В рамках сеанса работы с командной оболоч- кой команда su позволяет приобрести идентичность другого пользователя и либо начать новый сеанс командной оболочки с идентификатором этого пользователя, либо запустить одиночную команду от его имени. Команда sudo позволяет адми- нистратору записать настройки в конфигурационный файл с именем /etc/sudoers и определить конкретные команды, которые сможет выполнять тот или иной пользователь под приобретенной идентичностью. Выбор между su и sudo в значи- тельной степени определяется используемым дистрибутивом Linux. Большинство

136

Глава 9. Привилегии

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

su — запуск командной оболочки с подстановкой идентификаторов пользователя и группы

Команда su используется для запуска нового сеанса работы с командной оболоч- кой от имени другого пользователя. Команда имеет следующий синтаксис:

su [-[l]] [пользователь]

Если указан параметр -l, запущенная командная оболочка станет оболочкой вхо- да для указанного пользователя. Это означает, что будет загружено окружение пользователя и текущим рабочим каталогом станет домашний каталог пользова- теля. Часто это именно то, что требуется. Если пользователь не указан, подразу- мевается суперпользователь. Обратите внимание, что (довольно необычно) пара- метр -l можно сократить до -, и эта особенность часто используется на практике. Запустить командную оболочку от имени суперпользователя можно следующим образом:

[me@linuxbox ~]$ su - Пароль: [root@linuxbox ~]#

После ввода команды будет запрошен пароль суперпользователя. После ввода правильного пароля появится новое приглашение к вводу, показывающее, что данная командная оболочка обладает привилегиями суперпользователя (символ # в конце вместо символа $) и текущим рабочим каталогом теперь стал домашний каталог суперпользователя (обычно /root). После запуска новой оболочки можно выполнять команды с привилегиями суперпользователя. Завершим работу, введя команду exit, чтобы вернуться в предыдущую командную оболочку:

[root@linuxbox ~]# exit [me@linuxbox ~]$

С помощью su можно так же просто выполнить единственную команду, не запу- ская новый интерактивный сеанс:

su -c ‘команда’

При использовании этой формы команде su передается единственная команд- ная строка для выполнения. Не забудьте заключить команду в кавычки, чтобы

Изменение идентичности

137

предотвратить дополнительную ее интерпретацию механизмами подстановки те- кущей командной оболочки:

[me@linuxbox ~]$ su -c ’ls -l /root/*' Пароль: -rw——- 1 root root 754 2007-08-11 03:19 /root/anaconda-ks.cfg

/root/Mail: итого 0 [me@linuxbox ~]$

sudo — выполнение команды от имени другого пользователя

Команда sudo во многом подобна команде su, но имеет некоторые важные допол- нительные особенности. Администратор может определить порядок использо- вания sudo обычными пользователями, ограничив возможность запуска команд от имени другого пользователя (обычно суперпользователя). В частности, поль- зователю может быть разрешен доступ к одним командам и запрещен к другим.

Еще одно важное отличие состоит в том, что sudo не требует ввода пароля супер- пользователя. Для аутентификации в команде sudo пользователь должен ввести свой пароль. Например, допустим, что настройки sudo позволяют выполнить некоторую мифическую программу резервного копирования с именем backup_ script, требующую привилегий суперпользователя. С помощью sudo ее можно запустить так:

[me@linuxbox ~]$ sudo backup_script Пароль: System Backup Starting…

После ввода команды вам будет предложено ввести пароль (ваш, а не суперполь- зователя), и по завершении аутентификации указанная команда будет выполнена. Одно важное отличие между su и sudo — последняя не запускает новую команд- ную оболочку и не загружает окружение другого пользователя. Это означает, что команды не требуется экранировать как-то иначе, чем при запуске той же коман- ды без использования sudo. Имейте в виду, что такое ее поведение можно пере- определить с помощью различных параметров. Также отметьте, что sudo можно использовать для запуска интерактивного сеанса с привилегиями суперпользо- вателя (почти так же, как командой su -), передав ей параметр -i. Подробности ищите на странице справочного руководства (man) для sudo.

138

Глава 9. Привилегии

Чтобы увидеть, какие привилегии дает команда sudo, вызовите ее с параметром -l:

[me@linuxbox ~]$ sudo -l User me may run the following commands on this host: (ALL) ALL

UBUNTU И SUDO

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

В мире Unix, вследствие многопользовательской природы этой операционной системы, всегда проводилась четкая грань между обычными пользователями и администраторами. Идеология Unix заключается в том, чтобы предоставлять привилегии суперпользователя, только когда они действительно необходимы. Для этого часто используются команды su и sudo.

Еще несколько лет тому назад большинство дистрибутивов Linux использовали с этой целью команду su. Команда su не требует настройки, как команда sudo, а наличие учетной записи root — давняя традиция в Unix. Вместе это порождает проблему. Пользователи могут испытывать соблазн действовать от имени root без всякой необходимости. Фактически некоторые пользователи вообще работают в своих системах, регистрируясь исключительно как root, чтобы избежать появ- ления раздражающих сообщений «permission denied» (доступ запрещен). Такой подход ухудшает защищенность Linux, низводя ее до уровня Windows. Не самое лучшее решение.

Создатели Ubuntu предприняли иной подход. По умолчанию Ubuntu запрещает регистрироваться в системе с учетной записью root (не позволяя устанавливать пароль для этой учетной записи), а для получения привилегий суперпользователя предлагает использовать sudo. Начальная учетная запись пользователя обладает полным доступом к привилегиям суперпользователя через sudo и может наделять аналогичными привилегиями другие, вновь создаваемые учетные записи.

Изменение идентичности

139

chown — изменение владельца и группы файла

Команда chown используется для изменения владельца и группы файла или ката- лога. Для использования этой команды необходимы привилегии суперпользова- теля. Команда chown имеет следующий синтаксис:

chown [владелец][:[группа]] файл…

chown может изменить владельца и/или группу файла в зависимости от первого аргумента. В табл. 9.7 приводится несколько примеров команды.

Таблица 9.7. Примеры аргументов команды chown

Аргумент

Результаты

bob

Изменит принадлежность файла, назначив владельцем пользователя bob

bob:users

Изменит принадлежность файла, назначив владельцем пользователя bob и группу users

:admins

Изменит принадлежность файла, назначив группу admins

bob:

Изменит принадлежность файла, назначив владельцем пользователя bob и группу этого пользователя

Представьте, что существуют два пользователя: janet, имеющий доступ к приви- легиям суперпользователя, и tony, лишенный таких привилегий. Пользователю janet нужно скопировать файл из своего домашнего каталога в домашний каталог пользователя tony. Поскольку пользователь janet хочет, чтобы пользователь tony смог редактировать файл, janet должен изменить владельца скопированного фай- ла, назначив владельцем tony:

[janet@linuxbox ~]$ sudo cp myfile.txt ~tony Пароль: [janet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt -rw-r–r– 1 root root 8031 2018-03-20 14:30 /home/tony/myfile.txt [janet@linuxbox ~]$ sudo chown tony: ~tony/myfile.txt [janet@linuxbox ~]$ sudo ls -l ~tony/myfile.txt -rw-r–r– 1 tony tony 8031 2018-03-20 14:30 /home/tony/myfile.txt

Здесь видно, как пользователь janet копирует файл из своего каталога в домашний каталог пользователя tony. Далее janet заменяет владельца файла root (результат использования sudo) на tony. Добавив двоеточие в конец первого аргумента, janet

140

Глава 9. Привилегии

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

Заметили ли вы, что после первого использования команда sudo не предложила пользователю janet вновь ввести пароль? Это объясняется тем, что в большин- стве конфигураций sudo продолжает «доверять» пользователю в течение несколь- ких минут (пока не истечет время ее действия).

chgrp — изменение группы файла

В старых версиях Unix команда chown изменяла только владельца файла, но не группу. Чтобы изменить группу, предоставлялась другая команда, chgrp. Она дей- ствует практически так же, как chown, но имеет больше ограничений.

Использование привилегий

Теперь, когда мы разобрались, как действует механизм привилегий, самое время научиться пользоваться ими. Далее демонстрируется решение типичной задачи — настройка общего каталога. Представьте себе двух пользователей, bill и karen. Оба имеют коллекции музыкальных произведений и хотели бы настроить общий каталог, где могли бы хранить файлы в формате Ogg Vorbis или MP3. Пользова- тель bill имеет доступ к привилегиям суперпользователя через sudo.

Первое, что нужно сделать, — это создать группу, куда будут входить оба пользо- вателя, bill и karen. С помощью графического инструмента для управления поль- зователями bill создает группу с именем music и добавляет в нее пользователей bill и karen, как показано на рис. 9.2.

Рис. 9.2. Создание новой группы

Использование привилегий

141

Далее bill создает каталог для музыкальных файлов:

[bill@linuxbox ~]$ sudo mkdir /usr/local/share/Music Пароль:

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

[bill@linuxbox ~]$ ls -ld /usr/local/share/Music drwxr-xr-x 2 root root 4096 2018-03-21 18:05 /usr/local/share/Music

Как видите, каталогом владеет root, и для него установлен режим доступа 755. Чтобы сделать каталог общим, bill должен изменить группу каталога и права до- ступа для группы:

[bill@linuxbox ~]$ sudo chown :music /usr/local/share/Music [bill@linuxbox ~]$ sudo chmod 775 /usr/local/share/Music [bill@linuxbox ~]$ ls -ld /usr/local/share/Music drwxrwxr-x 2 root music 4096 2018-03-21 18:05 /usr/local/share/Music

И что все это означает? А означает это следующее: владельцем каталога /usr/ local/share/Music является root, и члены группы music получают права на запись и чтение в этом каталоге. Группа music включает пользователей bill и karen; то есть bill и karen могут создавать файлы в каталоге /usr/local/share/Music. Другие пользователи могут просматривать содержимое каталога, но не могут создавать файлы в нем.

Но остается нерешенной еще одна проблема. С текущими разрешениями файлы и каталоги внутри каталога Music будут создаваться с обычными разрешениями для пользователей bill и karen:

[bill@linuxbox ~]$ > /usr/local/share/Music/test_file [bill@linuxbox ~]$ ls -l /usr/local/share/Music -rw-r–r– 1 bill bill 0 2018-03-24 20:03 test_file

В действительности здесь наблюдаются две проблемы. Во-первых, маска umask в этой системе имеет значение 0022, что не позволяет членам группы записывать в файлы, принадлежащие другим членам группы. Это не проблема, если общий каталог хранит только файлы, но так как в данном каталоге предполагается хра- нить музыкальные произведения, а музыкальные произведения обычно принято организовывать в иерархии по исполнителям и альбомам, членам группы может понадобиться создавать файлы в каталогах, принадлежащих другим членам. Нам нужно изменить маску umask для пользователей bill и karen на 0002.

142

Глава 9. Привилегии

Во-вторых, каждый файл и каталог, созданный одним членом группы, будет при- надлежать основной группе пользователя, а не группе music. Исправить этот недо- статок можно установкой бита setgid на каталог:

[bill@linuxbox ~]$ sudo chmod g+s /usr/local/share/Music [bill@linuxbox ~]$ ls -ld /usr/local/share/Music drwxrwsr-x 2 root music 4096 2018-03-24 20:03 /usr/local/share/Music

Теперь можно проверить, устранили ли проблему вновь добавленные разрешения. bill устанавливает маску umask в значение 0002, удаляет предыдущий провероч- ный файл и создает новый проверочный файл и каталог:

[bill@linuxbox ~]$ umask 0002 [bill@linuxbox ~]$ rm /usr/local/share/Music/test_file [bill@linuxbox ~]$ > /usr/local/share/Music/test_file [bill@linuxbox ~]$ mkdir /usr/local/share/Music/test_dir [bill@linuxbox ~]$ ls -l /usr/local/share/Music drwxrwsr-x 2 bill music 4096 2018-03-24 20:24 test_dir -rw-rw-r– 1 bill music 0 2018-03-24 20:22 test_file [bill@linuxbox ~]$

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

Осталась только проблема с маской umask. Дело в том, что установленная маска действует лишь до конца сеанса и сбрасывается по его завершении. В главе 11 мы узнаем, как сохранить действие измененной маски umask между сеансами.

Изменение своего пароля

Последняя тема этой главы: изменение собственного пароля (и паролей других пользователей при наличии привилегий суперпользователя). Для установки и из- менения пароля используется команда passwd. Она имеет следующий синтаксис:

passwd [пользователь]

Чтобы изменить свой пароль, просто введите команду passwd. Вам будет предло- жено ввести старый, а затем новый пароль:

[me@linuxbox ~]$ passwd Смена пароля для me. (текущий) пароль UNIX: Введите новый пароль UNIX:

Заключение

143

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

[me@linuxbox ~]$ passwd Смена пароля для me. (текущий) пароль UNIX: Введите новый пароль UNIX: BAD PASSWORD: is too similar to the old one Введите новый пароль UNIX: Выберите пароль большей длины Введите новый пароль UNIX: BAD PASSWORD: it is based on a dictionary word

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

Заключение

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

10 Процессы

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

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

В этой главе будут представлены следующие команды:

ps — выводит список процессов, выполняющихся в текущий момент;

top — выводит задачи;

jobs — выводит список активных заданий;

bg — переводит задание в фоновый режим работы;

fg — переводит задание в режим работы на переднем плане;

kill — посылает сигнал процессу;

killall — останавливает процессы по именам;

shutdown — останавливает или перезагружает систему.

Как действует процесс

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

Просмотр списка процессов

145

init выполняет последовательность сценариев командной оболочки (находятся в /etc), называемых сценариями начальной загрузки (init scripts), которые запу- скают все системные службы. Многие из этих служб реализованы как программы- демоны (daemon programs), то есть программы, действующие в фоновом режиме и выполняющие свою работу без участия пользователя. Поэтому, даже в отсут- ствие зарегистрированных пользователей, система выполняет определенные слу- жебные процедуры.

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

Ядро хранит информацию обо всех процессах, чтобы упорядочить их работу. На- пример, каждому процессу присваивается номер, который называют идентифи- катором процесса (Process ID, PID). Идентификаторы процессов присваиваются в порядке возрастания, при этом процесс init всегда получает идентификатор PID 1. Ядро также следит за памятью, выделенной каждому процессу, и за готов- ностью процессов возобновить выполнение. Подобно файлам, процессы также имеют идентификаторы владельца и пользователя, эффективный (или действую- щий) идентификатор пользователя и т. д.

Просмотр списка процессов

Чаще всего для просмотра списка процессов используется команда ps (существу- ют и другие команды). Программа ps имеет множество параметров, но в самом простейшем случае она используется следующим образом:

[me@linuxbox ~]$ ps PID TTY TIME CMD 5198 pts/1 00:00:00 bash 10129 pts/1 00:00:00 ps

В этом примере команда вывела список с двумя процессами: процесс 5198 и процесс 10129 — программы bash и ps соответственно. Как можно заметить, по умолчанию ps выводит не очень много информации, только процессы, связанные с текущим сеан- сом. Чтобы увидеть больше, следует передать дополнительные параметры, но пре- жде чем мы сделаем это, давайте рассмотрим другие поля в выводе команды ps. Поле TTY — это сокращение от teletype (телетайп), оно содержит информацию об управ- ляющем терминале процесса. В Unix в этом поле выводится тип терминала. Поле TIME содержит объем процессорного времени, потребленного процессом. Как ви- дите, ни один из процессов не является слишком обременительным для компьютера.

Если добавить параметр x, можно получить более богатую информацию о проис- ходящем в системе:

146

Глава 10. Процессы

[me@linuxbox ~]$ ps x PID TTY STAT TIME COMMAND 2799 ? Ssl 0:00 /usr/libexec/bonobo-activation-server –ac 2820 ? Sl 0:01 /usr/libexec/evolution-data-server-1.10 – 15647 ? Ss 0:00 /bin/sh /usr/bin/startkde 15751 ? Ss 0:00 /usr/bin/ssh-agent /usr/bin/dbus-launch – 15754 ? S 0:00 /usr/bin/dbus-launch –exit-with-session 15755 ? Ss 0:01 /bin/dbus-daemon –fork –print-pid 4 –pr 15774 ? Ss 0:02 /usr/bin/gpg-agent -s –daemon 15793 ? S 0:00 start_kdeinit –new-startup +kcminit_start 15794 ? Ss 0:00 kdeinit Running… 15797 ? S 0:00 dcopserver –nosid и еще много других процессов…

Дополнительный параметр x (обратите внимание на отсутствие дефиса) сообщает команде ps, что та должна вывести все процессы, независимо от того, какие терми- налы (если таковые имеются) управляют ими. Символ ? в поле TTY указывает на отсутствие управляющего терминала. Таким образом, параметр x позволяет уви- деть все процессы в системе, которыми мы владеем.

Так как в системе одновременно выполняется множество процессов, ps произво- дит довольно длинные списки. Часто бывает полезно передать вывод ps команде less через конвейер, чтобы его проще было просматривать. Некоторые комбина- ции параметров приводят к выводу очень длинных строк, поэтому нелишним бу- дет также распахнуть окно эмулятора терминала на весь экран.

В этом примере в выводе появился новый столбец — STAT. Название STAT — это сокращение от state (состояние), столбец содержит информацию о текущем со- стоянии процесса, как показано в табл. 10.1.

Таблица 10.1. Состояния процессов

Состояние

Значение

R

S

D

T

Z

Выполняется. Процесс выполняется или готов к выполнению

Приостановлен. Процесс временно не выполняется; скорее всего, находит- ся в ожидании определенного события, такого как нажатие клавиши или прибытие сетевого пакета

Приостановлен без возможности прерывания. Процесс ожидает заверше- ния операции ввода/вывода, например, дисковым устройством

Остановлен. Процесс принудительно остановлен (подробнее об этом рас- сказывается ниже)

Недействующий процесс-«зомби». Это дочерний процесс, который завер- шился, но не был удален родителем

Просмотр списка процессов

147

Состояние

Значение

for information

~ ~ type :q to exit ~ type :help or for on-line help ~ type :help version7 for version info ~ ~ Running in Vi compatible mode ~ type :set nocp for Vim defaults ~ type :help cp-default for info on this ~ ~ ~

РЕЖИМ СОВМЕСТИМОСТИ

В примере выше, где показан экран, который выводится сразу после запуска vim, можно заметить текст: «Running in Vi compatible mode» (запущен в режиме совме- стимости с vi). Это означает, что vim был запущен в режиме, близко повторяющем обычное поведение vi, а не в расширенном режиме vim. Чтобы беспрепятственно следить за нитью повествования в этой главе, запустите vim в расширенном ре- жиме. Для этого в вашем распоряжении имеется пара возможностей. Попробуйте запустить редактор командой vim вместо vi. Если этот прием сработает, подумайте о том, чтобы добавить псевдоним vi=‘vim’ в свой файл .bashrc. Или выполните следующую команду, чтобы добавить строку в конфигурационный файл vim:

echo “set nocp” » ~/.vimrc

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

Так же как при знакомстве с nano, которое произошло в предыдущей главе, снача- ла научимся выходить из редактора. Для этого введите следующую команду (об- ратите внимание: двоеточие — это часть команды):

:q

176

Глава 12. Плавное введение в vi

В окне терминала должно появиться приглашение к вводу командной оболочки. Если по какой-то причине выход из vi не получился (скорее всего, потому что вы внесли какие-то изменения и еще не сохранили их), сообщите vi, что вы действи- тельно хотите выйти, добавив в команду восклицательный знак:

:q!

СОВЕТ

Если вы «заблудились» в vi, попробуйте дважды нажать ESC, чтобы вернуться на верный путь.

Режимы редактирования

Давайте снова запустим vi, но на этот раз укажем имя несуществующего файла. Именно так можно с помощью vi создать новый файл:

[me@linuxbox ~]$ rm -f foo.txt [me@linuxbox ~]$ vi foo.txt

В случае успеха на экране должно появиться следующее:

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ “foo.txt” [New File]

Режимы редактирования

177

Начальные символы тильды (~) сообщают об отсутствии текста в соответствующих строках. Таким способом vi сообщает нам, что файл пуст. Не вводите пока ничего!

Вторая важная вещь, которую нужно усвоить (после того, как вы научились вы- ходить), — vi является режимным редактором. Сразу после запуска vi оказыва- ется в командном режиме. В этом режиме практически каждая клавиша является командой, поэтому если вы начнете ввод, vi может запутаться сам и запутать вас.

Переход в режим вставки

Чтобы добавить какой-то текст в файл, необходимо сначала перейти в режим вставки. Для этого нажмите клавишу i. Вслед за этим, если vim работает в обычном расширенном режиме, в нижней части экрана появится надпись (она не появится, если редактор работает в режиме совместимости с vi):

– INSERT –

Теперь можно ввести какой-нибудь текст. Попробуйте, например:

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

Чтобы выйти из режима вставки и вернуться в командный режим, нажмите ESC.

Сохранение изменений

Чтобы сохранить изменения в файл, введите ex-команду, находясь в командном режиме. Для этого нажмите клавишу :. После этого в нижней части должен по- явиться символ двоеточие:

:

Чтобы выполнить запись изменений в файл, вслед за двоеточием введите w и на- жмите ENTER:

:w

Файл будет записан на жесткий диск, и в нижней части появится подтверждение:

“foo.txt” [New] 1L, 46C written

СОВЕТ

Если заглянуть в документацию к vim, можно заметить, что (по непонятной при- чине) командный режим в ней называется нормальным режимом, а ex-команды называются командным режимом. Имейте эту неточность в виду.

178

Глава 12. Плавное введение в vi

Перемещение курсора

Находясь в командном режиме, vi предлагает большое число команд управления курсором, часть из которых также используется программой less. В табл. 12.1 пе- речислены некоторые из этих команд.

Таблица 12.1. Клавиши управления курсором

Клавиша

Перемещает курсор

l или стрелка вправо

Вправо на один символ

h или стрелка влево

Влево на один символ

j или стрелка вниз

Вниз на одну строку

k или стрелка вверх

Вверх на одну строку

0 (ноль)

В начало текущей строки

^

$

w

W

b

B

К первому непробельному символу в текущей строке

В конец текущей строки

В начало следующего слова или к знаку препинания

В начало следующего слова, минуя знаки препинания

В начало предыдущего слова или к знаку препинания

В начало предыдущего слова, минуя знаки препинания

CTRL+F или Page Down

Вниз на одну страницу

CTRL+B или Page Up

Вверх на одну страницу

числоG

G

К строке с указанным номером (например, команда 1G выполнит переход к первой строке в файле)

К последней строке в файле

Почему для перемещения курсора были выбраны клавиши h, j, k и l? Потому что, когда был написан редактор vi, не все видеотерминалы имели кнопки со стрел- ками на клавиатуре. Таким образом, опытные пользователи, хорошо владеющие клавиатурой, могли управлять курсором, не отрывая пальцев от клавиш.

Основы редактирования

179

Многие команды в vi могут начинаться с числа, как команда G в табл. 12.1. До- бавляя число в команду, можно указать, сколько раз она должна быть выполнена. Например, команда 5j переместит курсор на пять строк вниз.

Основы редактирования

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

Добавление текста в конец

vi поддерживает несколько способов входа в режим вставки. Мы уже использова- ли команду i для вставки текста.

Давайте вернемся к нашему файлу foo.txt:

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

Если попытаться добавить текст в конец приложения, можно обнаружить, что команда i не позволяет сделать это, не давая переместить курсор за конец стро- ки. vi поддерживает команду добавления текста в конец, разумно названную a. Если переместить курсор в конец строки и ввести a, курсор переместится за ко- нец строки и vi перейдет в режим вставки. Это позволит нам добавить следую- щий текст:

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

Не забудьте нажать ESC, чтобы выйти из режима вставки.

Поскольку добавлять текст в конец строки требуется довольно часто, vi предла- гает сокращенную команду для перемещения в конец строки и перехода в режим добавления. Это команда A. Давайте попробуем с ее помощью добавить еще не- сколько строк в наш файл.

Сначала командой 0 (ноль) переместите курсор в начало строки. Затем введите A и добавьте следующие строки текста:

180

Глава 12. Плавное введение в vi

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5

Снова нажмите клавишу ESC, чтобы выйти из режима вставки.

Как видите, команда A очень удобна, потому что помещает курсор в конец строки перед переходом в режим вставки.

Вставка строки

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

Таблица 12.2. Команды вставки строк

Команда

Вставляет

o

O

Строку ниже текущей

Строку выше текущей

Рассмотрим действие этих команд на следующих примерах: поместите курсор в строку с текстом Строка 3 и введите o.

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3

Строка 4 Строка 5

Под третьей строкой появилась пустая строка, и редактор перешел в режим встав- ки. Выйдите из режима вставки нажатием ESC. Введите u, чтобы отменить измене- ния. Введите O, чтобы вставить пустую строку выше курсора:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2

Строка 3 Строка 4 Строка 5

Основы редактирования

181

Выйдите из режима вставки нажатием ESC и введите u, чтобы отменить изме- нения.

Удаление текста

Как можно догадаться, vi предлагает несколько способов удаления текста, и все они требуют нажатия одной или двух клавиш. Первый способ: команда x удаляет символ в позиции курсора. Команде x может предшествовать число, определяю- щее количество удаляемых символов. Команда d более универсальна. Команде d также может предшествовать число, определяющее количество операций удале- ния. Кроме того, команда d всегда сопровождается командой перемещения курсо- ра, управляющей размером удаляемой области. В табл. 12.3 приводится несколько примеров команды удаления.

Таблица 12.3. Команды удаления текста

Команда

Удалит

x

3x

dd

5dd

dW

d$

d0

d^

dG

Текущий символ

Текущий символ и следующие за ним два символа

Текущую строку

Текущую строку и следующие за ней четыре строки

От символа в текущей позиции курсора до начала следующего слова

От символа в текущей позиции курсора до конца текущей строки

От символа в текущей позиции курсора до начала строки

От символа в текущей позиции курсора до первого непробельного символа в строке

От текущей строки до конца файла

d20G

От текущей строки до 20-й строки файла

Поместите курсор на слово Это в первой строке. Вводите x, пока текст до конца предложения не будет удален. Затем введите несколько раз команду u, чтобы от- менить удаление.

ПРИМЕЧАНИЕ

Настоящий редактор vi поддерживает отмену только самой последней команды. vim поддерживает отмену множества команд.

182

Глава 12. Плавное введение в vi

Теперь давайте проведем операцию удаления еще раз, но теперь воспользуемся командой d. Снова установите курсор на слово Это и введите dW, чтобы удалить слово:

Съешь же ещё этих мягких французских булок, да выпей чаю. классно. Строка 2 Строка 3 Строка 4 Строка 5

Введите d$, чтобы удалить все от текущей позиции курсора до конца строки:

Съешь же ещё этих мягких французских булок, да выпей чаю. Строка 2 Строка 3 Строка 4 Строка 5

Введите dG, чтобы удалить все от текущей строки до конца файла:

~ ~ ~ ~ ~

Введите u три раза, чтобы отменить операции удаления.

Вырезание, копирование и вставка текста

Команда d не просто удаляет текст, она «вырезает» его. Каждый раз, когда выпол- няется команда d, удаленный текст копируется в буфер вставки (своего рода бу- фер обмена — clipboard), откуда позднее его можно извлечь командой p и вставить правее позиции курсора или левее — командой P.

Команда y выполняет копирование (yank) текста в буфер вставки почти так же, как команда d. В табл. 12.4 перечислены некоторые примеры комбинирования коман ды y с разными командами перемещения курсора.

Таблица 12.4. Команды копирования текста

Команда

Скопирует

yy

5yy

Текущую строку

Текущую и следующие четыре строки

Основы редактирования

183

Команда

Скопирует

yW

y$

y0

y^

yG

От текущей позиции курсора до начала следующего слова

От текущей позиции курсора до конца текущей строки

От текущей позиции курсора до начала строки

От текущей позиции курсора до первого непробельного символа в строке

От текущей строки до конца файла

y20G

От текущей строки до 20-й строки файла

Давайте попробуем что-нибудь скопировать и вставить. Поместите курсор на пер- вую строку и введите yy, чтобы скопировать текущую строку. Далее, переместите курсор в последнюю строку (G) и введите p, чтобы вставить скопированную стро- ку ниже текущей:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5 Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно.

Введите команду u, чтобы отменить изменение. Оставив курсор в последней стро- ке, введите P, чтобы вставить текст выше текущей строки:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 5

Попробуйте другие команды y из табл. 12.4 и посмотрите, как действуют коман- ды p и P. Закончив эксперименты, верните файл в исходное состояние.

Объединение строк

vi очень строго относится к понятию строки. Обычно он не дает возможности пе- реместить курсор в конец строки и удалить символ конца строки, чтобы объеди- нить текущую строку со следующей за ней. По этой причине в vi была добавлена специальная команда J (не путайте с командой j, которая перемещает курсор на одну строку вниз) для объединения строк.

184

Глава 12. Плавное введение в vi

Если поместить курсор в третью строку и ввести команду J, получится следующее:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5

Поиск и замена

Редактор vi имеет возможность перемещать курсор, опираясь на результаты по- иска. Он может это делать в пределах одной строки или всего файла. Он может также выполнять замену текста с подтверждением или без подтверждения поль- зователя.

Поиск в пределах строки

Команда f выполняет поиск в строке и перемещает курсор к следующему вхожде- нию указанного символа. Например, команда fa переместит курсор к следующему вхождению символа a в текущей строке. После выполнения операции поиска сим- вола в строке ее можно повторить, введя точку с запятой.

Поиск во всем файле

Для перемещения курсора к следующему вхождению слова или фразы использует- ся команда /. Она действует точно так же, как в программе less, о которой расска- зывалось в главе 3. После ввода команды / в нижней части экрана появится прямой слеш, вслед за которым нужно ввести искомое слово или фразу и нажать ENTER. По- сле этого курсор переместится к следующему вхождению искомой строки. Поиск следующего вхождения той же строки можно повторить командой n. Например:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5

Поместите курсор в первую строку и введите

/Строка

затем нажмите ENTER. Курсор переместится в строку 2. Затем введите команду n, и курсор переместится в строку 3. С каждой следующей командой n курсор будет

Поиск и замена

185

перемещаться вниз по файлу, пока не достигнет последнего вхождения искомого фрагмента. В примерах выше мы использовали для поиска только слова и фразы, однако vi позволяет применять регулярные выражения — очень мощное средство выражения сложных шаблонов текста. Мы подробно обсудим регулярные выра- жения в главе 19.

Глобальный поиск и замена

Для выполнения поиска с заменой (в vi эта операция называется подстановкой) в диапазоне строк или во всем файле vi использует ex-команды. Например, за- менить слово Строка словом строка во всем файле можно следующей командой:

:%s/Строка/строка/g

Давайте разобьем эту команду на элементы и рассмотрим их по отдельности (табл. 12.5).

Таблица 12.5. Пример синтаксиса команды глобального поиска с заменой

Элемент

Значение

:

%

s

Символ «двоеточие» начинает ex-команду

Определяет диапазон строк, где будет выполняться поиск. % — со- кращение, означающее «от первой строки до последней». В этом примере можно было бы указать диапазон номеров строк 1,5 (по- тому что в нашем файле всего пять строк) или 1,$, что означает «от строки с номером 1 до последней». Если диапазон строк не указан, операция применяется только к текущей строке

Определяет операцию — в данном случае подстановку (substitution), или поиск с заменой

/Строка/строка/

Шаблон поиска и текст замены

g

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

После выполнения поиска с заменой наш файл будет выглядеть так:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. строка 2 строка 3 строка 4 строка 5

186

Глава 12. Плавное введение в vi

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

:%s/строка/Строка/gc

Эта команда вернет содержимое файла в прежнее состояние, но перед каждой за- меной vi будет останавливаться и спрашивать подтверждение, выдавая следую- щее сообщение:

заменить на Строка? (y/n/a/q/l/^E/^Y)

В круглых скобках перечислены возможные варианты ответов, описание которых приводится в табл. 12.6.

Таблица 12.6. Клавиши подтверждения замены

Клавиша

Действие

y

n

a

Выполнить замену

Пропустить найденное вхождение

Выполнить замену этого и всех последующих вхождений

q или ESC

Завершить операцию

l

Выполнить замену этого вхождения и завершить операцию. Сокра- щенно от last (последняя)

CTRL+E, CTRL+Y

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

Редактирование нескольких файлов

Иногда бывает необходимо редактировать сразу несколько файлов. Например, может понадобиться внести изменения в файлы или скопировать содержимое из одного файла в другой. Редактор vi позволяет открыть несколько файлов, пере- числив их в командной строке:

vi файл1 файл2 файл3…

Редактирование нескольких файлов

187

Давайте закроем текущий сеанс работы vi и создадим новый файл для редактиро- вания. Введите :wq, чтобы выйти из vi с сохранением изменений в тексте. Далее, создайте новый файл в домашнем каталоге, который мы будем использовать в на- ших экспериментах. Создайте файл, захватив в него вывод команды ls:

[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt

Теперь откройте в vi старый и новый файлы:

[me@linuxbox ~]$ vi foo.txt ls-output.txt

После запуска vi вы увидите на экране первый файл:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5

Переключение между файлами

Чтобы переключиться с одного файла на следующий, выполните ex-команду:

:bn

Чтобы вернуться обратно, в предыдущий файл, выполните:

:bp

Теперь мы можем переключаться между файлами, но vi проводит политику, за- прещающую переключаться между файлами, если в текущем файле имеются несохраненные изменения. Чтобы заставить vi переключиться между файлами с потерей всех несохраненных изменений, добавьте в команду восклицательный знак (!).

В дополнение к методам переключения между файлами, описанным выше, vim (и некоторые версии vi) предоставляет дополнительные ex-команды, упрощаю- щие управление множеством файлов. Например, командой :buffers можно вы- вести список редактируемых файлов. В этом случае список появляется в нижней части экрана:

:buffers

1 %a “foo.txt” line 1 2 “ls-output.txt” line 0 Нажмите ENTER или введите команду для продолжения

188

Глава 12. Плавное введение в vi

Чтобы перейти к другому буферу (файлу), введите :buffer и номер искомого бу- фера. Например, переключиться с буфера 1, содержащего файл foo.txt, на буфер 2, содержащий файл ls-output.txt, можно командой:

:buffer 2

после выполнения этой команды на экране появится второй файл.

Открытие дополнительных файлов для редактирования

Также существует возможность добавлять файлы в текущий сеанс редактирова- ния. Команда :e (сокращенно от edit — редактировать) с именем файла откроет дополнительный файл. Завершите текущий сеанс редактирования и вернитесь в командную строку.

Запустите vi снова, но на этот раз с единственным файлом:

[me@linuxbox ~]$ vi foo.txt

Чтобы добавить второй файл, введите:

:e ls-output.txt

и на экране должен появиться второй файл. Первый файл останется открытым в редакторе, в чем легко убедиться:

:buffers 1 # “foo.txt” line 1 2 %a “ls-output.txt” line 0 Нажмите ENTER или введите команду для продолжения

Копирование содержимого из одного файла в другой

Часто в процессе редактирования множества файлов бывает необходимо скопи- ровать фрагмент текста из одного файла в другой. Это легко сделать с помощью обычных команд копирования и вставки, представленных выше. Посмотрим, как можно это осуществить. Сначала, в случае с использованием двух наших файлов, переключитесь на буфер 1 (foo.txt), выполнив команду:

:buffer 1

Редактирование нескольких файлов

189

В результате на экране должно появиться следующее:

Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5

Далее переместите курсор на первую строку и введите yy, чтобы скопировать строку.

Переключитесь на второй буфер командой:

:buffer 2

Теперь на экране должен появиться некий список файлов (здесь показана только часть):

total 343700 -rwxr-xr-x 1 root root 31316 2017-12-05 08:58 [ -rwxr-xr-x 1 root root 8240 2017-12-09 13:39 411toppm -rwxr-xr-x 1 root root 111276 2018-01-31 13:36 a2p -rwxr-xr-x 1 root root 25368 2017-10-06 20:16 a52dec -rwxr-xr-x 1 root root 11532 2017-05-04 17:43 aafire -rwxr-xr-x 1 root root 7292 2017-05-04 17:43 aainfo

Переместите курсор на первую строку и вставьте строку, скопированную в преды- дущем файле, введя команду p:

total 343700 Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. -rwxr-xr-x 1 root root 31316 2017-12-05 08:58 [ -rwxr-xr-x 1 root root 8240 2017-12-09 13:39 411toppm -rwxr-xr-x 1 root root 111276 2018-01-31 13:36 a2p -rwxr-xr-x 1 root root 25368 2017-10-06 20:16 a52dec -rwxr-xr-x 1 root root 11532 2017-05-04 17:43 aafire -rwxr-xr-x 1 root root 7292 2017-05-04 17:43 aainfo

Вставка целого файла в другой файл

Кроме того, мы можем вставить файл целиком в другой файл. Для выполнения этого приема завершите сеанс vi и запустите новый, с одним файлом:

[me@linuxbox ~]$ vi ls-output.txt

190

Глава 12. Плавное введение в vi

На экране снова появится список файлов:

total 343700 -rwxr-xr-x 1 root root 31316 2017-12-05 08:58 [ -rwxr-xr-x 1 root root 8240 2017-12-09 13:39 411toppm -rwxr-xr-x 1 root root 111276 2018-01-31 13:36 a2p -rwxr-xr-x 1 root root 25368 2017-10-06 20:16 a52dec -rwxr-xr-x 1 root root 11532 2017-05-04 17:43 aafire -rwxr-xr-x 1 root root 7292 2017-05-04 17:43 aainfo

Переместите курсор в третью строку и введите следующую ex-команду:

:r foo.txt

Команда :r (сокращенно от read — читать) вставит указанный файл перед позици- ей курсора. Теперь экран должен выглядеть так:

total 343700 -rwxr-xr-x 1 root root 31316 2017-12-05 08:58 [ -rwxr-xr-x 1 root root 8240 2017-12-09 13:39 411toppm Съешь же ещё этих мягких французских булок, да выпей чаю. Это классно. Строка 2 Строка 3 Строка 4 Строка 5 -rwxr-xr-x 1 root root 111276 2018-01-31 13:36 a2p -rwxr-xr-x 1 root root 25368 2017-10-06 20:16 a52dec -rwxr-xr-x 1 root root 11532 2017-05-04 17:43 aafire -rwxr-xr-x 1 root root 7292 2017-05-04 17:43 aainfo

Сохранение результатов работы

И здесь vi предлагает нам несколько способов сохранения отредактированных файлов. Мы уже знакомы с ex-командой :w, но существуют и другие команды, ко- торые могут оказаться полезными.

В командном режиме можно ввести ZZ, чтобы сохранить текущий файл и выйти из vi. Аналогично, ex-команда :wq сочетает в себе команды :w и :q, первая из которых сохраняет файл, а вторая закрывает редактор.

В команде :w можно также указать имя файла. В этом случае она будет действовать как команда Save As (Сохранить как). Например, если вы редактируете foo.txt и хоти- те сохранить альтернативную версию с именем foo1.txt, введите следующую команду:

:w foo1.txt

Заключение

191

ПРИМЕЧАНИЕ

Эта команда сохранит файл с новым именем, но она не изменит имя текущего редактируемого файла. Если после этого продолжить редактирование, вы будете редактировать foo.txt, а не foo1.txt.

Заключение

Освоив эти основные навыки, вы сможете решать простейшие задачи админи- стрирования типичной системы Linux, связанные с редактированием текста. Вре- мя, потраченное на изучение приемов использования vim, обязательно окупится в будущем. vi-подобные редакторы глубоко укоренились в культуре Unix, и мы увидим множество других программ, подвергшихся их влиянию. Ярким приме- ром такого влияния может служить программа less.

13 Настройка

приглашения к вводу

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

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

Устройство строки приглашения к вводу

По умолчанию строка приглашения к вводу имеет следующий вид:

[me@linuxbox ~]$

Обратите внимание, что она содержит имя пользователя, имя хоста (сетевое имя компьютера) и имя текущего рабочего каталога. Но как она образовалась? Все, оказывается, очень просто. Форма приглашения к вводу определяется в перемен- ной окружения PS1 (сокращенно от prompt string 1 — строка приглашения 1). Уви- деть содержимое переменной PS1 можно с помощью команды echo:

[me@linuxbox ~]$ echo $PS1 [\u@\h \W]$

Устройство строки приглашения к вводу

193

ПРИМЕЧАНИЕ

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

Мы видим, что PS1 содержит несколько символов, например: квадратные скобки, знак @ и знак доллара, но все остальное — сплошная абракадабра. Наиболее догад- ливые из вас сразу поймут, что символы, экранированные слешем, — специальные символы, как те, что мы видели в главе 7. В табл. 13.1 приводится неполный список символов, которые командная оболочка интерпретирует специальным образом в строке приглашения.

Таблица 13.1. Экранированные последовательности, используемые в строке приглашения

Последова- тельность

Отображаемое значение

\a

\d

\h

\H

\j

\l

\n

\r

\s

\t

\T

@

\A

Звонок. Заставляет компьютер издавать звуковой сигнал

Текущая дата в формате: день недели месяц число; например, «Mon May 26» (Пн Май 26)

Имя хоста локальной машины минус имя домена

Полное имя хоста

Число заданий, действующих в текущем сеансе

Имя текущего устройства терминала

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

Возврат каретки

Имя программы командной оболочки

Текущее время в 24-часовом формате

Текущее время в 12-часовом формате

Текущее время в 12-часовом формате AM/PM

Текущее время в 24-часовом формате, часы:минуты

194

Глава 13. Настройка приглашения к вводу

Последова- тельность

Отображаемое значение

Таблица 13.1 (окончание)

\u

\v

\V

\w

\W

!

#

$

[

]

Имя пользователя

Номер версии командной оболочки

Номер версии и выпуска командной оболочки

Имя текущего рабочего каталога

Последняя часть в имени текущего рабочего каталога

Номер текущей команды в истории

Число команд, введенных в текущем сеансе командной оболочки

Выводит символ $, если пользователь не является суперпользовате- лем, иначе выводит символ #

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

Отмечает конец последовательности непечатаемых символов

Альтернативные варианты оформления приглашения

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

[me@linuxbox ~]$ ps1_old="$PS1"

Здесь создается новая переменная с именем ps1_old, и ей присваивается значение переменной PS1. Убедиться, что значение скопировано, можно с помощью коман- ды echo:

[me@linuxbox ~]$ echo $ps1_old [\u@\h \W]$

Альтернативные варианты оформления приглашения

195

Это позволит вам в любой момент восстановить исходное оформление приглаше- ния, выполнив обратную процедуру:

[me@linuxbox ~]$ PS1="$ps1_old"

Теперь, когда все готово, давайте посмотрим, что получится, если определить пу- стую строку приглашения:

[me@linuxbox ~]$ PS1=

Если определить приглашение как пустую строку, мы ничего не увидим. Стро- ка приглашения просто исчезнет! В действительности она все еще существует, но поскольку она пустая, на экране ничего не отображается, — собственно, как мы и просили. Так как пустая строка приглашения дезориентирует, давайте опреде- лим минимальное оформление:

PS1="$ "

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

Добавим в строку приглашения сигнал:

$ PS1="[\a]$ "

Теперь при каждом выводе строки приглашения вы должны слышать короткий звуковой сигнал. Постоянно звучащий сигнал может раздражать, но в некоторых случаях он может быть полезен, например, если нужно получать звуковое опо- вещение об удачном завершении долго выполняющихся команд. Обратите вни- мание, что мы добавили последовательности [ и ]. Поскольку управляющий ASCII-символ «звонок» (\a) не является «печатаемым», то есть не перемещает курсор при выводе, мы должны сообщить об этом командной оболочке bash, что- бы та могла правильно определить длину строки приглашения.

А теперь попробуйте сделать приглашение более информативным, добавив имя хоста и время суток:

$ PS1="\A \h $ " 17:33 linuxbox $

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

196

Глава 13. Настройка приглашения к вводу

17:37 linuxbox $ PS1="$ " $

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

Добавление цвета

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

НЕДОРАЗУМЕНИЯ С ТЕРМИНАЛАМИ

В стародавние времена, когда дискеты были большими, а терминалы подключа- лись к удаленным компьютерам, существовало великое многообразие моделей терминалов, и все они работали по-разному. Они имели разные клавиатуры и по- разному интерпретировали управляющую информацию. В Unix и в Unix-подобных системах имеются две очень сложные подсистемы (которые называются termcap и terminfo), решающие все проблемы, связанные с управлением терминалами. Если заглянуть в самые потаенные кладовые настроек эмулятора терминала, можно обнаружить параметр, определяющий тип эмулируемого терминала.

Чтобы заставить терминалы говорить на едином языке, Американский националь- ный институт стандартов (American National Standards Institute, ANSI) разработал набор последовательностей символов для управления видеотерминалами. Заслу- женные пользователи DOS еще помнят файл ANSI.SYS, который применялся для интерпретации этих последовательностей.

Цветом символов можно управлять, посылая эмулятору терминала экранирован- ные последовательности ANSI внутри потока символов, предназначенных для вы- вода на экран. Экранированные последовательности не «выводятся» на экран; они интерпретируются терминалом как инструкции. Как показано в табл. 13.1, для включения непечатаемых символов используются последовательности [ и ]. Экранированные последовательности ANSI начинаются с восьмеричного кода 033 (код, генерируемый клавишей ESC), за которым следует необязательный атри- бут символа и инструкция. Например, вот как выглядит код, определяющий текст как простой (атрибут = 0), черного цвета

\033[0;30m.

Добавление цвета

197

В табл. 13.2 перечислены поддерживаемые цвета текста. Обратите внимание, что цвета делятся на две группы, отличаясь наличием атрибута жирного текста (1), из-за которого возникает впечатление более «светлых» (light) цветов.

Таблица 13.2. Экранированные последовательности, используемые для определения цвета текста

Последовательность

Цвет

Последовательность

Цвет

\033[0;30m

\033[0;31m

\033[0;32m

Черный

Красный

Зеленый

\033[1;30m

Темно-серый

\033[1;31m

\033[1;32m

Светло-красный

Светло-зеленый

\033[0;33m

Коричневый

\033[1;33m

Желтый

\033[0;34m

Синий

\033[1;34m

Светло-синий

\033[0;35m

Пурпурный

\033[1;35m

Светло-пурпурный

\033[0;36m

Бирюзовый

\033[1;36m

Светло-бирюзовый

\033[0;37m

Светло-серый

\033[1;37m

Белый

Давайте попробуем окрасить строку приглашения в красный цвет (здесь она вы- глядит как серая). Добавьте в начало экранированную последовательность:

$ PS1="[\033[0;31m]$ " $

Получилось, но обратите внимание, что весь текст, который вводится с клавиату- ры вслед за строкой приглашения, также окрашивается в красный цвет. Для устра- нения этого эффекта добавьте еще одну экранированную последовательность в конец определения приглашения — этим вы сообщите эмулятору терминала, что тот должен восстановить нормальный цвет:

$ PS1="[\033[0;31m]$[\033[0m] " $

Так лучше!

Кроме того, существует возможность изменить цвет фона, для чего предназначе- ны экранированные последовательности, перечисленные в табл. 13.3. Цвет фона не поддерживает атрибут жирного текста.

198

Глава 13. Настройка приглашения к вводу

Таблица 13.3. Экранированные последовательности, используемые для определения цвета фона

Последовательность

Цвет

Последовательность

Цвет

\033[0;40m

\033[0;41m

\033[0;42m

\033[0;43m

Черный

Красный

Зеленый

\033[0;44m

\033[0;45m

\033[0;46m

Синий

Пурпурный

Бирюзовый

Коричневый

\033[0;47m

Светло-серый

Чтобы вывести приглашение на красном фоне, достаточно изменить первую экра- нированную последовательность:

$ PS1="[\033[0;41m]$[\033[0m] " $

Попробуйте другие цвета и посмотрите, что из этого получится!

ПРИМЕЧАНИЕ

Помимо атрибутов символов нормального (0) и жирного (1) текста, есть также атрибут подчеркивания (4), мигания (5) и инверсии (7). В целях воспитания хо- рошего вкуса многие эмуляторы терминалов не поддерживают атрибут мигания.

Перемещение курсора

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

Таблица 13.4. Экранированные последовательности, управляющие позицией курсора

Последовательность

Действие

\033[l;cH

\033[nA

\033[nB

\033[nC

Перемещает курсор в строку l и позицию c

Перемещает курсор вверх на n строк

Перемещает курсор вниз на n строк

Перемещает курсор вперед на n символов

Перемещение курсора

199

Последовательность

Действие

\033[nD

\033[2J

\033[K

\033[s

\033[u

Перемещает курсор назад на n символов

Очищает экран и помещает курсор в левый верхний угол (строка 0, позиция 0)

Очищает экран от позиции курсора до конца текущей строки

Сохраняет текущую позицию курсора

Восстанавливает сохраненную позицию курсора

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

PS1="[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u]$ "

В табл. 13.5 приведены отдельные части этого определения, они помогут понять, как это работает.

Таблица 13.5. Экранированные последовательности, управляющие позицией курсора

Последовательность

Действие

[

\033[s

\033[0;0H

\033[0;41m

\033[K

Начинает последовательность непечатаемых символов. Ис- тинное назначение этой последовательности — позволить bash правильно вычислить длину строки приглашения на экране. Без этого функция редактирования командной строки неправильно позиционировала бы курсор

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

Перемещает курсор в левый верхний угол, в строку 0, позицию 0

Устанавливает красный цвет фона

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

\033[1;33m

Устанавливает желтый цвет текста

200

Глава 13. Настройка приглашения к вводу

Таблица 13.5 (окончание)

Последовательность

Действие

\t

\033[0m

\033[u

]

Выводит текущее время. Хотя это «печатаемый» элемент, он находится в непечатаемом блоке строки приглашения, потому что нам не нужно, чтобы командная оболочка bash учитывала размер часов при расчете истинного размера отображаемой строки приглашения

Выключает цвет. Сбрасывает настройки цвета для текста и фона

Восстанавливает позицию курсора, сохраненную ранее

Завершает последовательность непечатаемых символов

$

Строка приглашения

Сохранение определения приглашения

Я думаю, что мало у кого возникло желание вводить это монструозное определе- ние каждый раз, поэтому нам нужно где-то сохранить строку приглашения. Со- хранить определение можно в файле .bashrc. Для этого добавьте следующие две строки в файл:

PS1="[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u]$ " export PS1

Заключение

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

Часть III ТИПИЧНЫЕ ЗАДАЧИ И ОСНОВНЫЕ ИНСТРУМЕНТЫ

14 Управление пакетами

Общаясь с другими членами сообщества Linux, мы услышим массу мнений о том, какой дистрибутив Linux лучше. Часто обзоры дистрибутивов выглядят довольно глупыми, скатываясь к сравнению, например, привлекательности обоев рабочего стола (некоторые отвергают Ubuntu, потому что им не нравится цветовая схема по умолчанию!) и других тривиальных особенностей.

Самой важной отличительной чертой дистрибутива является система управления пакетами и активность сообщества, поддерживающего дистрибутив. Поработав с Linux достаточно долгое время, легко заметить, насколько динамичен программ- ный ландшафт этой системы. Он находится в постоянном движении. Большин- ство создателей основных дистрибутивов Linux выпускают новые версии каждые шесть месяцев, а множество отдельных программ обновляется каждый день. Что- бы не отставать от этой лавины программного обеспечения, нам нужен хороший инструмент для управления пакетами.

Управление пакетами (package management) — это методика установки и управле- ния программным обеспечением в системе. В наши дни большинство может удов- летворить все свои потребности в программном обеспечении, устанавливая пакеты, подготовленные создателями соответствующих дистрибутивов Linux. Это отлича- ется от ситуации, возникшей в первые годы развития Linux, когда для установки программ требовалось загружать и компилировать исходный код. Нельзя сказать, что было бы неправильно устанавливать программы из исходных кодов; в действи- тельности наличие доступа к исходному коду является самым большим достоин- ством в Linux. Это предоставляет возможность исследовать и улучшать систему. Просто работать с заранее скомпилированными пакетами проще и быстрее.

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

Как действует система пакетов

203

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

Системы пакетов

Разные дистрибутивы используют различные системы пакетов, и, как правило, па- кеты, подготовленные для одного дистрибутива, несовместимы с другими. В боль- шинстве дистрибутивов используется одна из двух основных технологий упаковки: разработанная создателями дистрибутива Debian с пакетами .deb и разработанная создателями дистрибутива Red Hat с пакетами .rpm. Существует несколько важных исключений, таких как Gentoo, Slackware и Arch, но в большинстве других дистри- бутивов используется одна из двух основных систем, что показано в табл. 14.1.

Таблица 14.1. Основные системы пакетов

Система пакетов

Дистрибутивы (неполный список)

Debian (.deb)

Red Hat (.rpm)

Debian, Ubuntu, Linux Mint, Raspbian

Fedora, CentOS, Red Hat Enterprise Linux, OpenSUSE

Как действует система пакетов

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

Linux действует иначе. Практически все программное обеспечение для системы Linux находится в Интернете. Большая его часть предоставляется создателями дистрибутивов в форме файлов пакетов, а остальная часть доступна в исходном коде, который можно установить вручную. Мы еще поговорим об установке про- грамм путем компиляции исходного кода в главе 23.

Файлы пакетов

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

204

Глава 14. Управление пакетами

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

Файлы пакетов создаются людьми, ответственными за сопровождение пакетов (package maintainer), часто (но не всегда) являющимися сотрудниками компании- производителя дистрибутива. Ответственный за пакет получает программное обеспечение в исходном коде от поставщика (автора программы), компилирует его и создает метаданные для пакета вместе со всеми необходимыми сценария- ми установки. Часто ответственный за сопровождение пакета вносит изменения в оригинальный исходный код с целью улучшения интеграции программы с дру- гими компонентами дистрибутива Linux.

Репозитории

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

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

Дистрибутив может также иметь сторонние репозитории. Они необходимы для распространения программного обеспечения, которое по юридическим причинам, связанным с патентами или законами об управлении цифровыми правами (Digital Rights Management, DRM), не может быть включено в дистрибутив. Самым из- вестным случаем является поддержка шифрования DVD, которая считается не- законной в Соединенных Штатах. Сторонние репозитории располагаются в стра- нах, где патенты или законы, ограничивающие распространение программного обеспечения, отсутствуют или действуют иначе. Эти репозитории обычно полно- стью независимы от поддерживаемого ими дистрибутива, и для их использования нужно знать об их существовании и вручную включать их в конфигурационные файлы с настройками системы управления пакетами.

Как действует система пакетов

205

Зависимости

Программы редко действуют в одиночку; чаще они полагаются на наличие других программных компонентов. Стандартные операции, такие как ввод/вывод, на- пример, выполняются процедурами, которые совместно используются многими программами. Эти процедуры хранятся в так называемых разделяемых библиоте- ках (shared libraries), предоставляющих важные услуги нескольким программам. Если пакету требуется некий общий ресурс, такой как разделяемая библиотека, про него говорят, что он имеет зависимость. Современные системы управления пакетами поддерживают некоторые методы разрешения зависимостей, — это га- рантирует, что после установки пакета в системе будут также установлены все его зависимости.

Высоко- и низкоуровневые инструменты управления пакетами

Системы управления пакетами обычно включают инструменты двух типов:

 низкоуровневые инструменты, решающие такие задачи, как установка и удале-

ние файлов пакетов;

 и высокоуровневые инструменты, выполняющие поиск в метаданных и разре-

шение зависимостей.

В этой главе мы посмотрим, какие инструменты входят в состав систем на ос- нове Debian (таких, как Ubuntu и многих других), а также в состав последних продуктов Red Hat. Несмотря на то что все дистрибутивы на основе Red Hat опираются на одну и ту же низкоуровневую программу (rpm), они используют разные высокоуровневые инструменты. В ходе обсуждения мы познакомимся с высокоуровневой программой yum, используемой в дистрибутивах Fedora, Red Hat Enterprise Linux и CentOS. Другие дистрибутивы на основе Red Hat предо- ставляют высокоуровневые инструменты, сопоставимые по своим возможно- стям (табл. 14.2).

Таблица 14.2. Инструменты управления пакетами

Дистрибутивы

На основе Debian

Fedora, Red Hat Enterprise Linux, CentOS

Низкоуровневые инструменты

Высокоуровневые инструменты

dpkg

rpm

apt-get, apt, aptitude

yum, dnf

206

Глава 14. Управление пакетами

Типичные задачи управления пакетами

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

В следующем обсуждении под термином имя_пакета будет подразумеваться фак- тическое имя пакета, а под термином файл_пакета — имя файла пакета.

Поиск пакета в репозитории

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

Таблица 14.3. Команды поиска пакетов

Дистрибутив

Команды

Debian

Red Hat

apt-get update apt-cache search искомая_строка

yum search искомая_строка

Вот пример поиска текстового редактора emacs в системе Red Hat с помощью коман ды yum:

yum search emacs

Установка пакета из репозитория

Высокоуровневые инструменты позволяют загрузить пакет из репозитория и установить его с полным разрешением всех зависимостей (табл. 14.4).

Таблица 14.4. Команды установки пакетов

Дистрибутив

Команды

Debian

Red Hat

apt-get update apt-cache install имя_пакета

yum install имя_пакета

Типичные задачи управления пакетами

207

Вот пример установки текстового редактора emacs в системе Debian из репозито- рия apt:

apt-get update; apt-get install emacs

Установка пакета из файла пакета

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

Таблица 14.5. Низкоуровневые команды установки пакетов

Дистрибутив

Команды

Debian

Red Hat

dpkg -i файл_пакета

rpm -i файл_пакета

Пример: если с некоторого сайта, не являющегося репозиторием, был загружен файл emacs-22.1-7.fc7-i386.rpm, его можно установить в систему Red Hat командой

rpm -i emacs-22.1-7.fc7-i386.rpm

ПРИМЕЧАНИЕ

Поскольку этот прием установки основан на использовании низкоуровневой программы rpm, он не выполняет разрешения зависимостей. Если программа rpm обнаружит неразрешенную зависимость, она завершится с сообщением об ошибке.

Удаление пакета

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

Таблица 14.6. Команды удаления пакетов

Дистрибутив

Команды

Debian

Red Hat

apt-get remove имя_пакета

yum erase имя_пакета

208

Глава 14. Управление пакетами

Пример: удалить пакет emacs из системы Debian можно командой:

apt-get remove emacs

Обновление пакетов из репозитория

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

Таблица 14.7. Команды обновления пакетов

Дистрибутив

Команды

Debian

Red Hat

apt-get update; apt-get upgrade

yum update

Пример: следующая команда применит все обновления, доступные для пакетов, установленных в системе на основе Debian:

apt-get update; apt-get upgrade

Обновление пакета из файла пакета

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

Таблица 14.8. Низкоуровневые команды обновления пакетов

Дистрибутив

Команды

Debian

Red Hat

dpkg -i файл_пакета

rpm -U файл_пакета

Пример: обновить установленную программу emacs до версии, содержащей в фай- ле пакета emacs-22.1-7.fc7-i386.rpm, в системе Red Hat можно командой

rpm -U emacs-22.1-7.fc7-i386.rpm

ПРИМЕЧАНИЕ

dpkg не имеет параметра, отвечающего за обновление пакета вместо установки, как в программе rpm.

Типичные задачи управления пакетами

209

Список установленных пакетов

Команды в табл. 14.9 можно использовать для вывода списка всех пакетов, уста- новленных в системе.

Таблица 14.9. Команды вывода списка пакетов

Дистрибутив

Команды

Debian

Red Hat

dpkg –l

rpm –qa

Определение, установлен ли пакет

С помощью низкоуровневых инструментов из табл. 14.10 можно определить, был ли установлен определенный пакет.

Таблица 14.10. Команды определения состояния пакетов

Дистрибутив

Команды

Debian

Red Hat

dpkg -s имя_пакета

rpm –q имя_пакета

Пример: определить, был ли установлен пакет emacs в системе Debian, можно командой

dpkg -s emacs

Вывод информации об установленном пакете

Если известно имя установленного пакета, с помощью команд из табл. 14.11 мож- но получить описание пакета.

Таблица 14.11. Команды получения информации о пакетах

Дистрибутив

Команды

Debian

Red Hat

apt-cache show имя_пакета

yum info имя_пакета

210

Глава 14. Управление пакетами

Пример: получить описание пакета emacs в системе Debian можно командой

apt-cache show emacs

Поиск пакета по установленному файлу

Определить, в составе какого пакета был установлен некий файл, можно с помо- щью команд из табл. 14.12.

Таблица 14.12. Команды идентификации принадлежности файлов

Дистрибутив

Команды

Debian

Red Hat

dpkg -S имя_файла

rpm –qf имя_файла

Пример: узнать, в составе какого пакета был установлен файл /usr/bin/vim в си- стеме Red Hat, можно командой

rpm -qf /usr/bin/vim

Заключение

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

МИФ ОБ УСТАНОВКЕ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ В LINUX

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

Заключение

211

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

Драйверы устройств распространяются почти так же, только они не выделяются в отдельные пакеты в репозитории дистрибутива, а включаются в ядро Linux. Мож- но сказать, что в Linux нет такого понятия, как «диск с драйверами». Либо ядро поддерживает данное устройство, либо нет, а ядро Linux поддерживает огромное число устройств. В действительности намного больше, чем Windows. Конечно, едва ли вас утешит информация, что нужное вам устройство не поддерживается ядром. Однако если такое случится, ищите причину. Отсутствие драйвера под- держки обычно обусловлено одной из следующих причин:

• Устройство слишком новое. Так как многие производители аппаратного обе- спечения не очень активно поддерживают Linux, задача написать драйвер для включения в ядро ложится на членов сообщества Linux. А это требует времени.

• Устройство чересчур экзотическое. Не все дистрибутивы включают все возможные драйверы устройств. Для каждого дистрибутива настраивается свое ядро, и так как ядра настраиваются до мелочей (благодаря чему от- крывается возможность использовать Linux в самых разных устройствах, от наручных часов до больших ЭВМ), создатели дистрибутива могли пропустить ваше устройство. Найдя и загрузив исходный код драйвера, вы (да, да — вы) сможете скомпилировать и установить драйвер самостоятельно. Это не очень сложно, скорее утомительно. О компиляции программного обеспечения мы поговорим в последующих главах.

• Производители аппаратного обеспечения что-то скрывают. Произво- дитель не выпустил либо исходный код драйвера для Linux, либо документа- цию, на основе которой можно было бы написать драйвер. Это означает, что производитель аппаратного обеспечения пытается сохранить программные интерфейсы устройства в секрете. Так как мы предпочитаем не использовать засекреченные устройства в своих компьютерах, я предлагаю удалить это не- толерантное устройство и отправить его в кучу из других бесполезных гаджетов.

15 Устройства хранения

В предыдущих главах мы познакомились с приемами работы с данными на уровне файлов. В этой главе мы будем рассматривать данные на уровне устройств. Linux обладает удивительными возможностями работы с устройствами хранения, такими как жесткие диски, сетевые хранилища или виртуальные устройства хранения, на- пример RAID (Redundant Array of Independent Disks — избыточный массив из неза- висимых дисков) и LVM (Logical Volume Manager — диспетчер логических томов).

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

Для выполнения упражнений к этой главе нам понадобится флеш-диск (флешка), подключаемый к порту USB компьютера и диск CD-RW (для систем, оборудован- ных пишущим приводом CD-ROM).

Мы познакомимся со следующими командами:

mount — монтирует файловые системы;

umount — размонтирует файловые системы;

fsck — проверяет и восстанавливает файловые системы;

fdisk — инструмент для работы с таблицей разделов;

mkfs — создает файловые системы;

dd — выполняет запись данных блоками непосредственно в устройство;

genisoimage (mkisofs) — создает файл образа ISO 9660;

wodim (cdrecord) — записывает данные на оптический носитель;

md5sum — вычисляет контрольную сумму MD5.

Монтирование и размонтирование устройств хранения

213

Монтирование и размонтирование устройств хранения

Последние достижения Linux на настольных компьютерах сделали управление устройствами хранения чрезвычайно простым для обычных пользователей. До- статочно подключить устройство к компьютеру, и оно тут же готово к работе. Раньше (года этак до 2004-го) все необходимые операции требовалось выпол- нять вручную. В серверных системах эти операции по большей части все еще вы- полняются вручную, потому что серверы часто предъявляют особые требования к устройствам хранения и настройкам.

Первый шаг в управлении устройствами хранения — подключение самого устройства к дереву файловой системы. Этот процесс называется монтировани- ем и позволяет устройству участвовать в работе операционной системы. Как рас- сказывалось в главе 2, Unix-подобные операционные системы, такие как Linux, поддерживают единое дерево файловой системы, к разным точкам которого под- ключаются дополнительные устройства. Этот подход отличается от используемо- го в других операционных системах, таких как Windows, где каждому устройству соответствует отдельное дерево файлов и каталогов (например, C:, D:\ и т. д.).

В файле с именем /etc/fstab (сокращенно от «file system table» — таблица файло- вых систем) перечисляются устройства (обычно разделы жесткого диска), мон- тируемые на этапе загрузки. Ниже приводится пример содержимого /etc/fstab из системы Fedora:

LABEL=/12 / ext4 defaults 1 1 LABEL=/home /home ext4 defaults 1 2 LABEL=/boot /boot ext4 defaults 1 2 tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 LABEL=SWAP-sda3 swap swap defaults 0 0

Большинство файловых систем из перечисленных в приведенном примере явля- ются виртуальными, и наше обсуждение к ним неприменимо. Наибольший инте- рес для нас в рамках исследования данной темы представляют первые три:

LABEL=/12 / ext4 defaults 1 1 LABEL=/home /home ext4 defaults 1 2 LABEL=/boot /boot ext4 defaults 1 2

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

214

Глава 15. Устройства хранения

Таблица 15.1. Поля в файле /etc/fstab

Поле

Содержит

Описание

1

Устройство

Традиционно это поле содержит фактическое имя файла устройства, связанного с физическим устройством, такое как /dev/hda1 (первый раздел ведущего (master) устройства на первом канале IDE). Но, учитывая, что в современных ком- пьютерах может быть множество динамически подключаемых устройств (таких, как устройства USB), многие современные дистрибутивы Linux связывают устройства с текстовыми метками. Такая метка (записываемая в устройство хранения во время форматирования) может быть простым текстом или случайно сгенерированным универсально уникальным идентификатором (Universally Unique Identifier, UUID). Эта метка читается операционной системой в момент подключе- ния устройства. Благодаря этому становится неважно, с каким файлом устройства связано физическое устройство, оно в лю- бом случае будет идентифицировано верно

2

3

4

5

6

Точка монтиро- вания

Каталог в файловой системе, к которому подключается устрой- ство

Тип файловой системы

Параметры

Linux позволяет монтировать множество типов файловых систем. Наиболее близкой к Linux является файловая система ext4, но точно так же поддерживаются другие типы, такие как FAT16 (msdos), FAT32 (vfat), NTFS (ntfs), CD-ROM (iso9660), и пр.

Файловые системы могут монтироваться с разными параметра- ми. Например, можно смонтировать файловую систему в режи- ме «только для чтения» или запретить выполнять какие-либо программы из нее (очень полезная мера предосторожности для съемных носителей)

Частота

Порядок

Единственное число, определяющее, когда должно выполнять- ся резервное копирование файловой системы командой dump

Единственное число, определяющее, в каком порядке файло- вая система должна проверяться командой fsck

Просмотр списка смонтированных файловых систем

Для монтирования файловых систем используется команда mount. Если ввести команду без аргументов, она выведет список файловых систем, смонтированных в настоящий момент:

[me@linuxbox ~]$ mount /dev/sda2 on / type ext4 (rw)

Монтирование и размонтирование устройств хранения

215

proc on /proc type proc (rw) sysfs on /sys type sysfs (rw) devpts on /dev/pts type devpts (rw,gid=5,mode=620) /dev/sda5 on /home type ext4 (rw) /dev/sda1 on /boot type ext4 (rw) tmpfs on /dev/shm type tmpfs (rw) none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw) fusectl on /sys/fs/fuse/connections type fusectl (rw) /dev/sdd1 on /media/disk type vfat (rw,nosuid,nodev,noatime, uhelper=hal,uid=500,ut f8,shortname=lower) twin4:/musicbox on /misc/musicbox type nfs4 (rw,addr=192.168.1.4)

Список имеет следующий формат: устройство on точка_монтирования type тип_ файловой_системы (параметры). Например, первая строка соответствует устрой- ству /dev/sda2, смонтированному как корневая файловая система типа ext4, до- ступная для чтения и записи (параметр rw). В конце списка можно заметить две интересные записи. Предпоследняя запись соответствует 2-гигабайтной SD-карте памяти в устройстве для чтения карт памяти, смонтированной в каталог /media/ disk, последняя запись соответствует сетевому приводу, смонтированному в ката- лог /misc/musicbox.

Для первого эксперимента возьмем привод CD-ROM. Сначала посмотрим, что имеется в системе, перед тем как вставить компакт-диск:

[me@linuxbox ~]$ mount /dev/mapper/VolGroup00-LogVol00 on / type ext4 (rw) proc on /proc type proc (rw) sysfs on /sys type sysfs (rw) devpts on /dev/pts type devpts (rw,gid=5,mode=620) /dev/hda1 on /boot type ext4 (rw) tmpfs on /dev/shm type tmpfs (rw) none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)

Этот список получен в системе CentOS 5, где для создания корневой файловой системы используется диспетчер LVM. Подобно многим современным дистрибу- тивам Linux, эта система пытается автоматически монтировать компакт-диски. Вставив в привод компакт-диск, мы увидим следующее:

[me@linuxbox ~]$ mount /dev/mapper/VolGroup00-LogVol00 on / type ext4 (rw) proc on /proc type proc (rw) sysfs on /sys type sysfs (rw) devpts on /dev/pts type devpts (rw,gid=5,mode=620) /dev/hda1 on /boot type ext4 (rw)

216

Глава 15. Устройства хранения

tmpfs on /dev/shm type tmpfs (rw) none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw) /dev/sdc on /media/live-1.0.10-8 type iso9660 (ro,noexec,nosuid,nodev,uid=500)

Это практически тот же список, с одной дополнительной записью. Последняя за- пись в списке сообщает, что компакт-диск в приводе CD-ROM (устройство /dev/ sdc в этой системе) смонтирован в каталог /media/live-1.0.10-8 и имеет файловую систему iso9660 (типичную для компакт-дисков). Обратите внимание на имя устройства. Когда вы будете проводить эксперимент в своей системе, очень веро- ятно, что имя устройства у вас будет отличаться.

ВНИМАНИЕ

В примерах, демонстрируемых ниже, особое внимание обращайте на фактиче- ские имена устройств в вашей системе и не используйте имена, приводящиеся в примерах здесь! Также отметьте, что аудиодиск — это не то же самое, что CD- ROM. Аудиодиск не имеет файловой системы и потому не может быть смонти- рован в общепринятом смысле.

Теперь, когда мы знаем имя устройства для привода CD-ROM, размонтируем диск и повторно смонтируем его в другой каталог в дереве файловой системы. Для это- го необходимо получить права суперпользователя (способом, соответствующим вашей системе) и размонтировать диск командой umount:

[me@linuxbox ~]$ su - Password: [root@linuxbox ~]# umount /dev/sdc

Следующий шаг: создать новую точку монтирования диска. Точка монтирова- ния — это самый обычный каталог где-то в дереве файловой системы. В таком ка- талоге нет ничего необычного. Он даже не должен быть пустым каталогом, правда, монтирование устройства в непустой каталог сделает его прежнее содержимое недоступным, пока устройство не будет размонтировано. Итак, создадим новый каталог:

[root@linuxbox ~]# mkdir /mnt/cdrom

И наконец, смонтируем CD-ROM в новую точку монтирования. Параметр -t по- зволяет указать тип файловой системы:

[root@linuxbox ~]# mount -t iso9660 /dev/sdc /mnt/cdrom

Монтирование и размонтирование устройств хранения

217

ПОЧЕМУ ВАЖНО РАЗМОНТИРОВАТЬ УСТРОЙСТВА

Если взглянуть на вывод команды free, показывающей статистику использования памяти, можно увидеть статистику с названием buffers (буферы). Компьютерные системы проектируются так, чтобы работать максимально быстро. Но медленные устройства препятствуют этому. Ярким примером служат принтеры. Даже самый быстрый принтер выглядит чрезвычайно медлительным по компьютерным стан- дартам. Компьютеры работали бы крайне медленно, если бы действительно были вынуждены ждать, пока принтер завершит печать страницы. В давние времена (когда персональные компьютеры еще не были многозадачными) это представляло настоящую проблему. При попытке распечатать электронную таблицу или тексто- вый документ компьютер мог стать недоступным до конца печати. Компьютер не мог посылать данные принтеру быстрее, чем тот мог их обработать, а принтеры не могли работать быстрее, потому что не могли быстро печатать. Эта проблема была решена созданием буфера печати, устройства, содержащего некоторый объем ОЗУ и находящегося между компьютером и принтером. При наличии буфе- ра печати компьютер мог послать данные в буфер печати, который сохранял их в быстрой памяти ОЗУ, и компьютер возвращался к работе, не дожидаясь конца печати. В то же время буфер печати мог передавать данные принтеру из своей памяти со скоростью, приемлемой для принтера.

Идея буферизации широко используется для увеличения производительности компьютеров — необходимость работы с медленными устройствами не должна ухудшать производительность системы. Операционные системы хранят дан- ные, прочитанные с устройства и предназначенные для записи в устройство, так долго, насколько это возможно, и используют их, прежде чем фактически обратиться к медленному устройству. В системе Linux, например, можно за- метить, что при продолжительной работе она заполняет всю память. Это не означает, что Linux «использует» всю память, это означает лишь то, что Linux использует в своих интересах всю доступную память и буферизует как можно больше данных.

Буферизация позволяет очень быстро выполнять запись в устройства хранения, потому что запись в физическое устройство откладывается «на потом». Данные, предназначенные для устройства, накапливаются в памяти. Время от времени операционная система записывает эти данные в физическое устройство.

Размонтирование устройства влечет за собой запись всех оставшихся данных в это устройство, чтобы его можно было безопасно извлечь. Если носитель извлечь, не выполнив размонтирования, есть вероятность, что не все данные, предназначен- ные для устройства, будут записаны в него. Иногда эти данные могут включать жизненно важные обновления каталогов, отсутствие которых может привести к повреждению файловой системы — одной из самых больших неприятностей, которые могут случиться с компьютером.

218

Глава 15. Устройства хранения

После этого можно исследовать содержимое компакт-диска в новой точке монти- рования:

[root@linuxbox ~]# cd /mnt/cdrom [root@linuxbox cdrom]# ls

Обратите внимание, что происходит при попытке размонтировать компакт-диск:

[root@linuxbox cdrom]# umount /dev/sdc umount: /mnt/cdrom: device is busy

В чем причина? Устройство нельзя размонтировать, если оно используется каким- то пользователем или другим процессом. В данном случае мы изменили текущий рабочий каталог, перенеся его в точку монтирования компакт-диска, что и стало причиной занятости устройства. Эту проблему легко исправить, перенеся текущий рабочий каталог куда-нибудь в другое место за пределами точки монтирования:

[root@linuxbox cdrom]# cd [root@linuxbox ~]# umount /dev/hdc

Теперь устройство было успешно размонтировано.

Определение названий устройств

Иногда сложно определить название (имя) устройства. В прошлом это было про- ще. Устройство всегда находилось в одном месте и никогда не менялось. Unix- подобные системы именно так и действовали. Во времена, когда разрабатывалась система Unix, для «смены дискового устройства» требовалось использовать подъ- емник, чтобы извлечь из комнаты с ЭВМ устройство размером со стиральную ма- шину. В последние годы типовая аппаратная конфигурация настольного компью- тера стала намного динамичнее, и система Linux вынуждена быть более гибкой, чем ее предшественницы.

В примерах, приведенных выше, мы использовали способность современной си- стемы Linux «как по волшебству» монтировать устройства, чтобы узнавать их названия постфактум. Но как быть тем, кто управляет сервером или каким-то другим окружением, где автоматическое монтирование не поддерживается? Как в этом случае определить название устройства?

Сначала давайте посмотрим, как система выбирает названия для устройств. Если вывести содержимое каталога /dev (где живут все устройства), можно увидеть значительное число устройств:

[me@linuxbox ~]$ ls /dev

Монтирование и размонтирование устройств хранения

219

Содержимое списка показывает некоторые шаблоны в именовании устройств, не- полный список которых приводится в табл. 15.2.

Таблица 15.2. Названия устройств хранения данных в Linux

Шаблон

Устройство

/dev/fd*

/dev/hd*

Приводы гибких дисков

Диски IDE (PATA) в старых системах. Обычно материнские платы содержат два разъема, или канала IDE, к каждому из которых можно подключить шлейф, рассчитанный на два устройства. Первое устройство, подключен- ное к такому шлейфу, называется ведущим устройством (master device), а второе — подчиненным устройством (slave device). Имена устройств упорядочены так, что ведущее устройство, подключенное к первому каналу, получает имя /dev/hda, ведомое устройство, подключенное к перво- му каналу, получает имя /dev/hdb; ведущее устройство, подключенное ко второму каналу, получает имя /dev/hdc, и т. д. Цифра в конце определяет номер раздела на устройстве. Например, имя /dev/hda1 соответствует первому разделу на первом жестком диске в системе, тогда как имя /dev/ hda соответствует всему устройству в целом

/dev/lp*

Принтеры

/dev/sd*

Диски SCSI. В последних версиях системы Linux ядро интерпретирует все дисковые устройства (включая жесткие диски PATA/SATA, флеш-диски и съемные накопители USB, такие как портативные музыкальные плееры и цифровые камеры) как диски SCSI. В остальном система именования напоминает прежнюю систему /dev/hd*, описанную выше

/dev/sr*

Приводы оптических дисков (приводы CD/DVD, как пишущие, так и нет)

Кроме того, во многих системах можно увидеть такие символические ссылки, как /dev/cdrom, /dev/dvd и /dev/floppy, которые ссылаются на фактические файлы устройств и предусмотрены для удобства.

Если вам доведется работать в системе, которая не монтирует автоматически съемные носители, вы можете использовать следующий прием для определения названий таких устройств после их подключения. Сначала запустите мониторинг содержимого файла /var/log/messages или /var/log/syslog в режиме реального вре- мени (для этого могут потребоваться права суперпользователя):

[me@linuxbox ~]$ sudo tail -f /var/log/messages

Эта команда выведет несколько последних строк из файла и приостановится. Далее подключите извлекаемое устройство. В этом примере мы использовали

220

Глава 15. Устройства хранения

16-мегабайтный флеш-диск. Практически сразу же ядро обнаружит новое устрой- ство и проверит его:

Jul 23 10:07:53 linuxbox kernel: usb 3-2: new full speed USB device using uhci_hcd and address 2 Jul 23 10:07:53 linuxbox kernel: usb 3-2: configuration #1 chosen from 1 choice Jul 23 10:07:53 linuxbox kernel: scsi3 : SCSI emulation for USB Mass Storage devices Jul 23 10:07:58 linuxbox kernel: scsi scan: INQUIRY result too short (5), using 36 Jul 23 10:07:58 linuxbox kernel: scsi 3:0:0:0: Direct-Access Easy Disk 1.00 PQ: 0 ANSI: 2 Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] 31263 512-byte hardware sectors (16 MB) Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Write Protect is off Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Assuming drive cache: write through Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] 31263 512-byte hardware sectors (16 MB) Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Write Protect is off Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Assuming drive cache: write through Jul 23 10:07:59 linuxbox kernel: sdb: sdb1 Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Attached SCSI removable disk Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: Attached scsi generic sg3 type 0

Когда вывод опять приостановится, нажмите CTRL+C, чтобы вернуться в пригла- шение командной строки. Наибольший интерес для нас представляют строки с упоминанием имени устройства [sdb], соответствующего нашим ожиданиям в отношении названия устройства диска SCSI. Cледующие две строки являются для нас особенно показательными:

Jul 23 10:07:59 linuxbox kernel: sdb: sdb1 Jul 23 10:07:59 linuxbox kernel: sd 3:0:0:0: [sdb] Attached SCSI removable disk

Они сообщают, что имя /dev/sdb соответствует всему устройству, а имя /dev/ sdb1 — первому разделу на этом устройстве. Как видите, работая с Linux, иногда приходится проводить массу интересных детективных расследований!

СОВЕТ

Прием с использованием команды tail -f /var/log/messages демонстрирует отличный способ наблюдения за происходящим в системе в режиме реального времени.

Создание новых файловых систем

221

Зная имя устройства, можно смонтировать флеш-диск:

[me@linuxbox ~]$ sudo mkdir /mnt/flash [me@linuxbox ~]$ sudo mount /dev/sdb1 /mnt/flash [me@linuxbox ~]$ df Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda2 15115452 5186944 9775164 35% / /dev/sda5 59631908 31777376 24776480 57% /home /dev/sda1 147764 17277 122858 13% /boot tmpfs 776808 0 776808 0% /dev/shm /dev/sdb1 15560 0 15560 0% /mnt/flash

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

Создание новых файловых систем

Представьте, что вам нужно отформатировать флеш-диск и вместо файловой си- стемы FAT32 создать на нем файловую систему, родную для Linux. Для этого сле- дует выполнить две операции:

  1. Создать (при необходимости) новое распределение разделов, если имеющееся

вас не устраивает.

  1. Создать новую, пустую файловую систему.

ВНИМАНИЕ

Следующее упражнение производит форматирование флеш-диска. Используйте диск, не содержащий ничего, что вам было бы нужно, потому что вся информа- ция на диске будет стерта! И снова: убедитесь, что используете имя устройства, верное для вашей системы, а не то, которое показано в примере. Игнорирование этого предупреждения может привести к форматированию (то есть к стиранию) другого диска!

Управление разделами с помощью fdisk

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

222

Глава 15. Устройства хранения

размонтировать (если прежде он был смонтирован) и затем запустить программу fdisk, как показано ниже:

[me@linuxbox ~]$ sudo umount /dev/sdb1 [me@linuxbox ~]$ sudo fdisk /dev/sdb

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

Команда (m для справки):

После ввода команды m на экране появится меню программы:

Команда описание a изменить флаг загрузочного раздела b изменить вложенную BSD-метку диска c переключить флаг совместимости с DOS d удалить раздел l список известных типов разделов m показать это меню n добавить новый раздел p показать таблицу разделов q выйти без сохранения изменений s создать новую пустую таблицу разделов Sun t изменить тип раздела u изменить единицы отображения/ввода v проверить таблицу разделов w сохранить таблицу на диск и выйти x дополнительные функции (только для экспертов)

Команда (m для справки):

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

Command (m for help): p Disk /dev/sdb: 16 MB, 16006656 bytes 1 heads, 31 sectors/track, 1008 cylinders Units = cylinders of 31 * 512 = 15872 bytes

Device Boot Start End Blocks Id System /dev/sdb1 2 1008 15608+ b W95 FAT32

Обратите внимание, что устройство имеет объем 16 Мбайт и единственный раз- дел (1), занимающий 1006 цилиндров из 1008 доступных на устройстве. Раздел

Создание новых файловых систем

223

идентифицирован как раздел Windows 95 FAT32. Некоторые программы исполь- зуют этот идентификатор, ограничивая виды операций с диском, но чаще изме- нение идентификатора не влечет серьезных последствий. Однако ради демон- страции мы изменим его, чтобы показать, что это раздел Linux. Для этого нужно сначала узнать, какой идентификатор обозначает разделы Linux. В листинге, при- веденном выше, мы видели, что существующий раздел имеет идентификатор b (столбец Id). Чтобы увидеть список известных типов разделов, вернитесь к меню программы и обратите внимание на пункт:

l список известных типов разделов

Если ввести команду l, появится длинный список допустимых типов разделов. Среди них можно увидеть идентификатор b типа существующего раздела и иден- тификатор 83 для Linux. Вернемся обратно к меню программы, где можно увидеть команду изменения идентификатора раздела:

t изменить тип раздела

Введите t и затем новый идентификатор:

Command (m for help): t Selected partition 1 Hex code (type L to list codes): 83 Changed system type of partition 1 to 83 (Linux)

Это все изменения, которые нам нужно было сделать. До этого момента никаких изменений на самом устройстве не было произведено (все изменения пока просто зафиксированы в памяти программы, а не на физическом устройстве), поэтому теперь запишем измененную таблицу разделов на устройство и выйдем. Для этого введите команду w:

Command (m for help): w The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: If you have created or modified any DOS 6.x partitions, please see the fdisk manual page for additional information. Syncing disks. [me@linuxbox ~]$

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

224

Глава 15. Устройства хранения

Предупреждающее сообщение, выглядящее зловещим, можно просто игнори- ровать1.

Создание новой файловой системы с помощью mkfs

Завершив редактирование разделов (довольно простое, хотя так бывает не всегда), мы создадим на флеш-диске новую файловую систему. Для этого воспользуемся программой mkfs (сокращенно от make filesystem — создать файловую систему), спо- собной создавать разные файловые системы. Чтобы создать на устройстве файло- вую систему ext4, следует передать команде параметр -t с типом файловой системы ext4, затем указать имя устройства и раздел, который требуется отформатировать:

[me@linuxbox ~]$ sudo mkfs -t ext4 /dev/sdb1 mke2fs 2.23.2 (12-Jul-2011) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 3904 inodes, 15608 blocks 780 blocks (5.00%) reserved for the super user First data block=1 Maximum filesystem blocks=15990784 2 block groups 8192 blocks per group, 8192 fragments per group 1952 inodes per group Superblock backups stored on blocks: 8193

Writing inode tables: done Creating journal (1024 blocks): done Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 34 mounts or 180 days, whichever comes first. Use tune2fs -c or -i to override. [me@linuxbox ~]$

Когда выбирается тип файловой системы ext4, программа выводит массу ин- формации. Чтобы восстановить на устройстве оригинальную файловую систему FAT32, следует указать тип файловой системы vfat:

[me@linuxbox ~]$ sudo mkfs -t vfat /dev/sdb1

1 Оно гласит: «ВНИМАНИЕ: если вы создали или изменили разделы DOS 6.x, прочитай- те дополнительную информацию на странице справочного руководства для команды fdisk». — Примеч. пер.

Проверка и восстановление файловой системы

225

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

Проверка и восстановление файловой системы

Знакомясь с файлом /etc/fstab, мы видели некие странные цифры в конце каждой строки. Каждый раз, когда система загружается, она проверяет целостность фай- ловых систем перед их монтированием. Эту проверку выполняет программа fsck (сокращенно от filesystem check — проверка файловой системы). Последнее число в каждой записи в файле fstab определяет порядок проверки файловых систем. В примере, приведенном выше, видно, что корневая файловая система проверяет- ся первой, вслед за ней проверяются файловые системы home и boot. Устройства с нулем в последнем поле не проверяются стандартными механизмами.

Программа fsck может не только проверить целостность, но и восстановить по- врежденные файловые системы с той или иной степенью успеха в зависимости от масштаба повреждений. В Unix-подобных системах восстановленные фрагменты файлов помещаются в каталог lost+found, находящийся в корне каждой файловой системы.

Проверить наш флеш-диск (который предварительно необходимо размонтиро- вать) можно с помощью следующей команды:

[me@linuxbox ~]$ sudo fsck /dev/sdb1 fsck 1.40.8 (13-Mar-2016) e2fsck 1.40.8 (13-Mar-2016) /dev/sdb1: clean, 11/3904 files, 1661/15608 blocks

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

ЧТО ТАКОЕ FSCK?

В культуре Unix слово «fsck» часто используется взамен распространенного руга- тельства, в котором три буквы совпадают с буквами в имени команды. Это показа- тельно — вы почти наверняка будете произносить упомянутое слово, оказавшись в ситуации, вынуждающей запустить fsck.

226

Глава 15. Устройства хранения

Непосредственное перемещение данных между устройствами

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

Эту задачу решает программа dd. Она копирует блоки данных из одного места в другое. По историческим причинам команда имеет уникальный синтаксис:

dd if=входной_файл of=выходной_файл [bs=размер_блока [count=число_блоков]]

ВНИМАНИЕ

dd — очень мощная команда. Ее название происходит от data definition (опреде- ление данных), но иногда его расшифровывают как destroy disk (уничтожить диск), потому что пользователи часто допускают ошибки в параметрах if и of. Всегда дважды проверяйте их, прежде чем нажать ENTER!

Представьте, что у вас есть два флеш-диска USB одинакового размера и вам нужно создать точную копию первого диска на втором. Допустим, что после подключе- ния к компьютеру им назначаются имена устройств /dev/sdb и /dev/sdc соответ- ственно. В этом случае скопировать содержимое первого диска на второй можно следующей командой:

dd if=/dev/sdb of=/dev/sdc

Как вариант, если к компьютеру подключено только первое устройство, можно скопировать его содержимое в обычный файл, который впоследствии использо- вать для восстановления или копирования:

dd if=/dev/sdb of=flash_drive.img

Создание образа компакт-диска

Запись на компакт-диски (CD-R или CD-RW) выполняется в два этапа.

 Создается файл образа ISO, являющийся точным образом файловой системы

компакт-диска.

 Файл образа записывается на носитель (то есть на сам компакт-диск).

Непосредственное перемещение данных между устройствами

227

Создание образа-копии компакт-диска

Чтобы создать ISO-образ имеющегося компакт-диска, необходимо с помощью dd прочитать все блоки с данными с этого компакт-диска и скопировать их в ло- кальный файл. Например, допустим, что у нас есть компакт-диск с дистрибути- вом Ubuntu, и мы хотим создать файл ISO-образа, который потом можно будет использовать для создания нескольких копий. Вставив компакт-диск в привод CD-ROM и определив имя устройства (пусть это будет /dev/cdrom), мы сможем создать файл ISO-образа следующим способом:

dd if=/dev/cdrom of=ubuntu.iso

Этот прием также применим к дискам DVD с данными, но он не будет работать с аудиодисками, так как для хранения данных на них файловая система не ис- пользуется. Если вы хотите скопировать аудиодиск, обратитесь к команде cdrdao.

Создание образа из коллекции файлов

Создать файл ISO-образа, включающий содержимое некоего каталога, можно с помощью программы genisoimage. Для этого сначала создадим каталог со все- ми необходимыми файлами для включения в образ и затем командой genisoimage создадим файл образа. Например, если предположить, что вы создали каталог ~/cd-rom-files и наполнили его файлами для записи на компакт-диск, следующая команда создаст файл образа с именем cd-rom.iso:

genisoimage -o cd-rom.iso -R -J ~/cd-rom-files

Параметр -R требует добавить метаданные расширений Rock Ridge, позволяющих использовать длинные имена файлов и права доступа к файлам в стиле POSIX. Аналогично, параметр -J включает расширения Joliet, разрешающие использовать длинные имена файлов в Windows.

ЧТО В ИМЕНИ ТВОЕМ…

В руководствах по созданию и записи оптических дисков, таких как CD-ROM и DVD, которых в избытке на просторах Интернета, часто можно встретить упомина- ние двух программ, mkisofs и cdrecord. Эти программы были некогда частью популярного пакета cdrtools, созданного Йоргом Шиллингом (Jörg Schilling). Летом 2006-го мистер Шиллинг изменил лицензию в части, касающейся пакета cdrtools, из-за чего она, по мнению многих в сообществе пользователей Linux, стала несовместимой с GNU GPL. Как результат, на основе cdrtools был создан альтернативный проект, включающий программы wodim и genisoimage взамен cdrecord и mkisofs соответственно.

228

Глава 15. Устройства хранения

Запись образа компакт-диска

После подготовки файла образа его можно записать на оптический носитель. Большинство команд, обсуждаемых ниже, применимы и для записи на носители CD-ROM и DVD.

Непосредственное монтирование файла ISO-образа

Существует один трюк, позволяющий монтировать ISO-образы, хранящиеся на жестком диске, и работать с ними, как если бы это были оптические носители. Параметр -o loop, добавленный в команду mount (вместе с обязательным пара- метром -t iso9660, определяющим тип файловой системы), позволяет смонти- ровать файл образа в дерево файловой системы, как если бы это было обычное устройство:

mkdir /mnt/iso_image mount -t iso9660 -o loop image.iso /mnt/iso_image

В примере, приведенном выше, мы создали точку монтирования с именем /mnt/ iso_image и затем смонтировали в нее файл образа image.iso. После монтирования образа с ним можно работать как с настоящим диском CD-ROM или DVD. Не за- будьте размонтировать образ, когда он станет не нужен.

Очистка перезаписываемых компакт-дисков

Перезаписываемые компакт-диски CD-RW нужно стирать, или очищать, перед повторным использованием. Для этого воспользуемся командой wodim, указав ей имя устройства пишущего привода компакт-дисков и тип очистки. Программа wodim предлагает несколько типов очистки. Для минимальной (и самой быстрой) очистки следует указать тип fast:

wodim dev=/dev/cdrw blank=fast

Запись образа

Записать образ можно с помощью все той же программы wodim, указав ей имя устройства пишущего привода компакт-дисков и имя файла образа:

wodim dev=/dev/cdrw image.iso

Дополнительные сведения

229

Помимо имени устройства и файла образа программа wodim поддерживает массу дополнительных параметров. Чаще других используются параметры -v (обеспе- чивает вывод подробной информации в ходе записи) и -dao (выполняет запись на диск в режиме disc-at-once — диск целиком). Режим «диск целиком» следует ис- пользовать, если вы собираетесь воспроизводить диски в коммерческих целях. По умолчанию wodim использует режим track-at-once (по одной дорожке), который хорошо подходит для записи музыкальных треков.

Заключительное замечание

В этой главе мы познакомились с основами управления устройствами хранения данных. Эту тему можно было бы продолжать и продолжать. Linux поддерживает широкий спектр устройств хранения и файловых систем, а также предлагает раз- нообразные возможности для взаимодействий с другими системами.

Дополнительные сведения

Часто бывает полезно проверить целостность ISO-образа, загруженного из Ин- тернета. В большинстве случаев распространители ISO-образов сопровождают их файлами с контрольными суммами. Контрольная сумма — это результат экзоти- ческих математических вычислений в виде числа, представляющего содержимое целевого файла. Если содержимое файла образа изменится хотя бы в одном бите, его контрольная сумма будет отличаться от указанной распространителем. Для вычисления контрольной суммы чаще всего используется программа md5sum, воз- вращающая уникальное шестнадцатеричное число:

md5sum image.iso 34e354760f9bb7fbf85c96f6a3f94ece image.iso

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

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

230

Глава 15. Устройства хранения

2048-байтных блоков в образе (запись на оптические носители всегда выполня- ется блоками по 2048 байт) и прочитайте с носителя ровно столько блоков. Для некоторых типов носителей это не обязательно. Например, компакт-диск, запи- санный в режиме disc-at-once, можно проверить так:

md5sum /dev/cdrom 34e354760f9bb7fbf85c96f6a3f94ece /dev/cdrom

Многие типы носителей, такие как DVD, требуют точного вычисления числа бло- ков. Следующий пример демонстрирует проверку целостности файла образа dvd- image.iso и диска в устройстве /dev/dvd привода DVD. Вам понятно, как работает эта команда?

md5sum dvd-image.iso; dd if=/dev/dvd bs=2048 count=$(( $(stat -c “%s” dvd-image. iso) / 2048 )) | md5sum

16 Сети

Когда дело доходит до сетевых возможностей, трудно представить что-то, что было бы невозможно для Linux. Linux используется для создания всех видов се- тевых систем, программных компонентов и устройств, включая брандмауэры, маршрутизаторы, серверы имен, сетевые устройства хранения данных (Network- Attached Storage, NAS) и так далее и тому подобное.

Насколько обширна тема сетей, настолько же обширна коллекция команд, ко- торые можно использовать для настройки и управления ими. Мы сосредоточим свое внимание лишь на тех из них, которые чаще всего используются на практике. В число команд, выбранных для исследования в этой главе, входят команды, ис- пользуемые для мониторинга сетей и передачи файлов. Дополнительно мы иссле- дуем программу ssh, используемую для входа в удаленные системы. В этой главе рассматриваются следующие команды:

ping — посылает пакеты ICMP ECHO_REQUEST узлам в сети;

traceroute — выводит трассировку маршрута пакетов к сетевому узлу;

ip — отображает информацию о маршрутах, устройствах, политиках маршрутиза- ции и туннелях и позволяет управлять ими;

netstat — выводит список сетевых соединений, таблицы маршрутов, статистику интерфейсов, маскируемые соединения и сведения о членстве в широковещатель- ных группах;

ftp — программа передачи файлов через Интернет;

wget — неинтерактивный загрузчик файлов из сети;

ssh — клиент OpenSSH SSH (программа для входа в удаленные системы).

232

Глава 16. Сети

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

 IP-адрес (адрес протокола Интернета).

 Имя хоста и домена.

 URI (Uniform Resource Identifier — унифицированный идентификатор ре-

сурса).

ПРИМЕЧАНИЕ

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

Исследование и мониторинг сети

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

ping

Команда ping является самой простой сетевой командой. Она посылает специаль- ные сетевые пакеты IMCP ECHO_REQUEST указанному сетевому узлу. Боль- шинство сетевых устройств принимает эти пакеты и отвечает на них, — это позво- ляет проверить сетевые соединения.

ПРИМЕЧАНИЕ

Многие сетевые устройства (в том числе и компьютеры с Linux) могут на- страиваться так, чтобы игнорировать эти пакеты. Обычно это делается для по вышения безопасности и отчасти — чтобы ввести в заблуждение потен- циального злоумышленника. Кроме того, многие брандмауэры блокируют трафик IMCP.

Исследование и мониторинг сети

233

Например, с помощью команды ping можно проверить достижимость сетевого узла http://www.linuxcommand.org/ (один из моих любимых сайтов ;-)):

[me@linuxbox ~]$ ping linuxcommand.org

Сразу после запуска программа ping начинает посылать пакеты с определенным интервалом (по умолчанию 1 секунда), пока ее выполнение не будет прервано:

[me@linuxbox ~]$ ping linuxcommand.org PING linuxcommand.org (66.35.250.210) 56(84) bytes of data. 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=1 ttl=43 time=107 ms 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=2 ttl=43 time=108 ms 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=3 ttl=43 time=106 ms 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=4 ttl=43 time=106 ms 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=5 ttl=43 time=105 ms 64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=6 ttl=43 time=107 ms

— linuxcommand.org ping statistics — 6 packets transmitted, 6 received, 0% packet loss, time 6010ms rtt min/avg/max/mdev = 105.647/107.052/108.118/0.824 ms

После прерывания нажатием CTRL+C (в данном примере после шестого пакета) ping выводит результаты своей работы. Если сеть функционирует должным об- разом, число потерянных пакетов (packet loss) будет составлять ноль процентов. Успешная работа ping может служить признаком того, что сетевые компоненты (интерфейсные карты, кабели, маршрутизаторы и шлюзы) находятся в рабочем состоянии.

traceroute

Программа traceroute (в некоторых системах используется похожая на нее про- грамма tracepath) выводит список всех «переходов» (hops) на пути сетевого тра- фика между локальной системой и указанным узлом сети. Например, увидеть, как выглядит маршрут к сайту http://www.slashdot.org/, можно с помощью следующей команды:

[me@linuxbox ~]$ traceroute slashdot.org

Ее вывод выглядит примерно так:

traceroute to slashdot.org (216.34.181.45), 30 hops max, 40 byte packets 1 ipcop.localdomain (192.168.1.1) 1.066 ms 1.366 ms 1.720 ms 2 * * *

234

Глава 16. Сети

3 ge-4-13-ur01.rockville.md.bad.comcast.net (68.87.130.9) 14.622 ms 14.885 ms 15.169 ms 4 po-30-ur02.rockville.md.bad.comcast.net (68.87.129.154) 17.634 ms 17.626 ms 17.899 ms 5 po-60-ur03.rockville.md.bad.comcast.net (68.87.129.158) 15.992 ms 15.983 ms 16.256 ms 6 po-30-ar01.howardcounty.md.bad.comcast.net (68.87.136.5) 22.835 ms 14.23 3 ms 14.405 ms 7 po-10-ar02.whitemarsh.md.bad.comcast.net (68.87.129.34) 16.154 ms 13.600 ms 18.867 ms 8 te-0-3-0-1-cr01.philadelphia.pa.ibone.comcast.net (68.86.90.77) 21.951 ms 21.073 ms 21.557 ms 9 pos-0-8-0-0-cr01.newyork.ny.ibone.comcast.net (68.86.85.10) 22.917 ms 21 .884 ms 22.126 ms 10 204.70.144.1 (204.70.144.1) 43.110 ms 21.248 ms 21.264 ms 11 cr1-pos-0-7-3-1.newyork.savvis.net (204.70.195.93) 21.857 ms cr2-pos-0-0-3-1. newyork.savvis.net (204.70.204.238) 19.556 ms cr1-pos-0-7-3-1.newyork.savvis.net (204.70.195.93) 19.634 ms 12 cr2-pos-0-7-3-0.chicago.savvis.net (204.70.192.109) 41.586 ms 42.843 ms cr2- tengig-0-0-2-0.chicago.savvis.net (204.70.196.242) 43.115 ms 13 hr2-tengigabitethernet-12-1.elkgrovech3.savvis.net (204.70.195.122) 44.215 ms 41.833 ms 45.658 ms 14 csr1-ve241.elkgrovech3.savvis.net (216.64.194.42) 46.840 ms 43.372 ms 47.041 ms 15 64.27.160.194 (64.27.160.194) 56.137 ms 55.887 ms 52.810 ms 16 slashdot.org (216.34.181.45) 42.727 ms 42.016 ms 41.437 ms

Здесь можно видеть, что на пути между нашей тестовой системой и http://www. slashdot.org/ находится 16 маршрутизаторов. Для маршрутизаторов, предостав- ляющих идентификационную информацию, выводятся имена хостов, IP-адреса и информация о производительности, которая включает три интервала времени, понадобившихся для передачи/подтверждения пакетов между локальной систе- мой и маршрутизатором. Для маршрутизаторов, не предоставляющих идентифи- кационной информации (например, из-за особенностей настройки маршрутиза- тора, заторов в сети, действий брандмауэров и т. д.), выводятся звездочки, как это можно видеть в строке, соответствующей второму переходу. Иногда, когда инфор- мация о маршрутизаторе блокируется, это ограничение можно преодолеть, пере- дав команде traceroute параметр -T или -I.

ip

Программа ip — это многофункциональный инструмент для настройки пара- метров подключения к сети, использующий весь спектр сетевых функций, до- ступных в современных ядрах Linux. Она пришла на замену ныне устаревшей

Исследование и мониторинг сети

235

программе ifconfig. С помощью ip можно исследовать сетевые интерфейсы и та- блицу маршрутизации системы.

[me@linuxbox ~]$ ip a 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever

2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether ac:22:0b:52:cf:84 brd ff:ff:ff:ff:ff:ff inet 192.168.1.14/24 brd 192.168.1.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::ae22:bff:fe52:cf84/64 scope link valid_lft forever preferred_lft forever

Пример, приведенный выше, показывает, что наша тестовая система имеет два се- тевых интерфейса. Первый, с именем lo, — это петлевой интерфейс (loopback), виртуальный интерфейс, который система использует, чтобы разговаривать «сама с собой»; второй, с именем eth0, — это интерфейс Ethernet.

Выполняя причинно-следственную диагностику, первое, на что следует обра- тить внимание, — наличие слова UP в первой строке с информацией о каждом интерфейсе, указывающего, что сетевой интерфейс включен, и присутствие допустимого IP-адреса в поле inet во третьей строке. Для систем, использую- щих протокол динамической настройки хостов (Dynamic Host Configuration Protocol, DHCP), наличие допустимого IP-адреса в этом поле подтвердит нор- мальную работу DHCP.

netstat

Программа netstat используется для исследования различных настроек сети и статистик. С помощью множества параметров этой команды можно просматри- вать самые разные аспекты настройки сети. С помощью параметра -ie, например, можно исследовать сетевые интерфейсы в системе:

[me@linuxbox ~]$ netstat -ie eth0 Link encap:Ethernet HWaddr 00:1d:09:9b:99:67 inet addr:192.168.1.2 Bcast:192.168.1.255 Mask:255.255.255.0

236

Глава 16. Сети

    inet6 addr: fe80::21d:9ff:fe9b:9967/64 Scope:Link
    UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
    RX packets:238488 errors:0 dropped:0 overruns:0 frame:0
    TX packets:403217 errors:0 dropped:0 overruns:0 carrier:0
    collisions:0 txqueuelen:100
    RX bytes:153098921 (146.0 MB) TX bytes:261035246 (248.9 MB)
    Memory:fdfc0000-fdfe0000

lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:2208 errors:0 dropped:0 overruns:0 frame:0 TX packets:2208 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:111490 (108.8 KB) TX bytes:111490 (108.8 KB)

Использование параметра -r позволит получить таблицу маршрутизации ядра. По этой таблице можно судить, как настроена передача пакетов между сетями:

[me@linuxbox ~]$ netstat -r Таблица маршутизации ядра протокола IP Destination Gateway Genmask Flags MSS Window irtt Iface 192.168.1.0 * 255.255.255.0 U 0 0 0 eth0 default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0

В этом простом примере представлена типичная таблица маршрутизации для клиентской машины, подключенной к локальной сети (Local Area Network, LAN), находящейся за брандмауэром/маршрутизатором. В первой строке демонстри- руется адрес назначения 192.168.1.0. IP-адреса, оканчивающиеся нулем, соот- ветствуют целым сетям, а не отдельным узлам в них, поэтому такой адрес под- разумевает: «любой узел в локальной сети». Следующее поле, Gateway (шлюз), определяет имя или IP-адрес шлюза (маршрутизатора) для выхода текущего узла в указанную сеть. Звездочка в этом поле указывает, что использовать шлюз не тре- буется.

В последней строке в качестве адреса назначения указано слово default (по умол- чанию). Эта строка управляет трафиком, адресованным любым сетям, не перечис- ленным в таблице. В данном примере видно, что роль шлюза выполняет марш- рутизатор с адресом 192.168.1.1, который, по всей видимости, знает, что делать с трафиком.

Так же как ip, программа netstat имеет множество параметров, из которых мы рассмотрели только пару. Полный их список вы найдете на странице справочного руководства (man) для netstat.

Передача файлов по сети

237

Передача файлов по сети

Что толку от сети, если не знать, как перемещать файлы через нее? Существует множество программ, перемещающих данные по сети. Сейчас мы рассмотрим две из них, а еще несколько — в последующих разделах.

ftp

Одна из по-настоящему «классических» программ — ftp — получила свое имя от используемого ею протокола, протокола передачи файлов (File Transfer Protocol, FTP). Протокол FTP широко используется в Интернете для передачи файлов. Он поддерживается большинством веб-браузеров, если не всеми, и вам часто будут встречаться идентификаторы URI, начинающиеся с префикса протоко- ла ftp://.

Программа ftp появилась задолго до веб-браузеров. Она использовалась для об- мена данными с серверами FTP, компьютерами, хранящими файлы, которые мож- но выгружать и загружать по сети.

Протокол FTP (в своем первоначальном виде) небезопасен, потому что пере- сылает имена и пароли в открытом текстовом виде. То есть они не шифруются, и любой, кто способен перехватить сетевой трафик, сможет увидеть их. По этой причине практически все операции по протоколу FTP в Интернете выполняются анонимными северами FTP. Анонимный сервер позволяет любому желающему подключиться с учетной записью anonymous без пароля.

В следующем примере показан типичный сеанс работы с программой ftp для загрузки ISO-образа с дистрибутивом Ubuntu из каталога /pub/cd_images/ Ubuntu-18.04 анонимного сервера FTP fileserver.

[me@linuxbox ~]$ ftp fileserver Connected to fileserver.localdomain. 220 (vsFTPd 2.0.1) Name (fileserver:me): anonymous 331 Please specify the password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> cd pub/cd_images/Ubuntu-18.04 250 Directory successfully changed. ftp> ls 200 PORT command successful. Consider using PASV.

238

Глава 16. Сети

150 Here comes the directory listing. -rw-rw-r– 1 500 500 733079552 Apr 25 03:53 ubuntu-18.04-desktop- amd64.iso 226 Directory send OK. ftp> lcd Desktop Local directory now /home/me/Desktop ftp> get ubuntu-18.04-desktop-amd64.iso local: ubuntu-18.04-desktop-amd64.iso remote: ubuntu-18.04-desktop-amd64.iso 200 PORT command successful. Consider using PASV. 150 Opening BINARY mode data connection for ubuntu-18.04-desktop-amd64.iso (733079552 bytes). 226 File send OK. 733079552 bytes received in 68.56 secs (10441.5 kB/s) ftp> bye

В табл. 16.1 приводится описание команд, вводившихся в ходе этого сеанса.

Таблица 16.1. Примеры команд интерактивного сеанса ftp

Команда

ftp fileserver

anonymous

cd pub/cd_images/Ubuntu-18.04

ls

lcd Desktop

Значение

Вызывает программу ftp и предлагает ей подключиться к серверу fileserver

Имя пользователя для входа. После ввода имени пользователя появится приглашение ввести пароль. Некоторые серверы прини- мают пустой пароль. Другие могут требовать пароль в формате адреса электронной почты. В данном случае попробуйте ввести что- нибудь похожее на user@example.com

Выполняет переход в каталог в удаленной системе, где находится требуемый файл. Обратите внимание, что на большинстве ано- нимных серверов FTP файлы, доступные для загрузки любым желающим, находятся где-то в каталоге pub

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

Выполняет переход в каталог ~/Desktop в ло- кальной системе. В этом примере программа ftp была вызвана в текущем рабочем ката- логе ~. Данная команда назначает текущим рабочий каталог ~/Desktop

Передача файлов по сети

239

Команда

Значение

get ubuntu-18.04-desktop-amd64.iso

bye

Посылает удаленной системе запрос на передачу файла ubuntu-18.04-desktop-amd64. iso локальной системе. Так как в локальной системе текущим рабочим выбран каталог ~/Desktop, файл будет загружен в него

Выходит из удаленной системы и завершает сеанс программы ftp. Так же можно исполь- зовать команды quit и exit

Если в приглашении ftp> ввести команду help, программа выведет список под- держиваемых команд. С помощью программы ftp можно выполнять множество обычных операций с файлами на сервере, правда, при наличии достаточных при- вилегий. Это не очень удобно, но выполнимо.

lftp — более удачная версия ftp

ftp — не единственный клиент FTP командной строки. В действительности та- ких клиентов множество. Одним из лучших (и более популярным) считается lftp Александра Лукьянова (Alexander Lukyanov). Этот клиент действует почти так же, как традиционная программа ftp, но имеет множество дополнительных функций, включая поддержку нескольких протоколов (в том числе и HTTP), возможность автоматического восстановления прервавшейся загрузки, вы- полнение операций в фоновом режиме, автодополнение путей по клавише Tab и многое другое.

wget

wget — еще одна популярная программа командной строки для загрузки файлов. Ее удобно использовать для загрузки содержимого веб- и FTP-сайтов. С помо- щью wget можно загрузить один файл, несколько файлов и даже целый сайт. На- пример, загрузить первую страницу сайта http://www.linuxcommand.org/ можно командой:

[me@linuxbox ~]$ wget http://linuxcommand.org/index.php –11:02:51– http://linuxcommand.org/index.php => `index.php’ Распознаётся linuxcommand.org… 66.35.250.210 Подключение к linuxcommand.org|66.35.250.210|:80… соединение установлено.

240

Глава 16. Сети

HTTP-запрос отправлен. Ожидание ответа… 200 OK Длина: 3808 (3.7K) [text/html] Сохранение в: “index.php”

[                                  ] 3,120 --.--K/s

11:02:51 (161.75 MB/s) - `index.php’ сохранён [3808/30808]

Большинство параметров, поддерживаемых программой wget, позволяет орга- низовать рекурсивную загрузку, загрузку файлов в фоновом режиме (позволяет выйти из системы без остановки загрузки) и догружать частично загруженные файлы. Эти возможности хорошо описаны на странице справочного руководства (man).

Безопасные взаимодействия с удаленными узлами

Уже много лет Unix-подобные операционные системы поддерживают возмож- ность удаленного администрирования по сети. На первом этапе, еще до повсемест- ного распространения Интернета, существовала пара популярных программ для входа в удаленные сетевые узлы: rlogin и telnet. Однако эти программы страдали тем же фатальным недостатком, что и программа ftp; все данные (включая имена пользователей и пароли) они передавали в виде открытого текста. Это совершен- но недопустимо в эпоху Интернета.

ssh

Для решения описанной проблемы был разработан протокол с названием SSH (Secure Shell — безопасная командная оболочка). SSH решает две основные про- блемы безопасного взаимодействия с удаленными сетевыми узлами:

 подтверждает, что удаленный узел является именно тем, за кого себя выдает (это предотвращает атаки вида «злоумышленник в середине» (man-in-the- middle));

 шифрует все данные, передаваемые между локальным и удаленным узлами.

В своей работе протокол SSH опирается на два компонента. На удаленном узле действует сервер SSH, принимающий соединения на порте 22, а в локальной си- стеме действует клиент SSH, осуществляющий обмен информацией с удаленным сервером.

Безопасные взаимодействия с удаленными узлами

241

Большинство дистрибутивов Linux включают реализацию SSH с названием OpenSSH из проекта BSD. Некоторые дистрибутивы (например, Red Hat) по умол- чанию содержат пакеты с обоими компонентами, сервером и клиентом, тогда как другие (например, Ubuntu) включают только клиента. Чтобы система могла при- нимать удаленные соединения, в ней должен быть установлен пакет с реализаци- ей сервера OpenSSH-server, этот сервер должен быть настроен и запущен, и если система находится за брандмауэром, последний должен пропускать входящие со- единения на порт TCP с номером 22.

СОВЕТ

Если у вас нет удаленной системы, с которой можно было бы устанавливать соединения, но вы желаете поработать с примерами, приведенными ниже, уста- новите пакет OpenSSH-server в своей системе и используйте имя localhost в каче- стве имени удаленного узла. В этом случае ваш компьютер будет устанавливать соединения с самим собой.

Программа клиента SSH, используемая для подключения к серверам SSH, имеет достаточно очевидное имя: ssh. Подключиться к удаленному сетевому узлу с име- нем remote-sys можно с помощью программы клиента ssh, как показано ниже:

[me@linuxbox ~]$ ssh remote-sys The authenticity of host ‘remote-sys (192.168.1.4)’ can’t be established. RSA key fingerprint is 41:ed:7a:df:23:19:bf:3c:a5:17:bc:61:b3:7f:d9:bb. Are you sure you want to continue connecting (yes/no)?1

При первой попытке подключения на экран выводится предупреждение, сообща- ющее, что аутентичность удаленного узла не может быть установлена. Это объ- ясняется тем, что программа-клиент прежде никогда не подключалась к данному удаленному узлу. Чтобы принять идентификационные данные удаленного узла, введите yes в ответ на приглашение. После установки соединения пользователю будет предложено ввести пароль:

Warning: Permanently added ‘remote-sys,192.168.1.4’ (RSA) to the list of known hosts. me@remote-sys’s password:2

1 Аутентичность узла ‘remote-sys (192.168.1.4)’ не может быть установлена. Идентифика- ционный ключ RSA: 41:ed:7a:df:23:19:bf:3c:a5:17:bc:61:b3:7f:d9:bb. Вы уверены, что желаете установить соединение (да/нет)? — Примеч. пер.

2 Внимание: узел ‘remote-sys,192.168.1.4’ (RSA) добавлен в хранимый список известных уз-

лов. — Примеч. пер.

242

Глава 16. Сети

После ввода действительного пароля в терминале появится приглашение команд- ной оболочки из удаленной системы:

Last login: Tue Aug 30 13:00:48 2018 [me@remote-sys ~]$

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

К удаленной системе можно также подключиться с другим именем пользователя. Например, если локальный пользователь me имеет в удаленной системе учетную запись с именем bob, он сможет войти в удаленную систему с именем bob, выпол- нив следующую команду:

[me@linuxbox ~]$ ssh bob@remote-sys bob@remote-sys’s password: Last login: Tue Aug 30 13:03:21 2018 [bob@remote-sys ~]$

Как отмечалось выше, ssh проверяет аутентичность удаленного узла. Если уда- ленный узел не пройдет аутентификацию, появится следующее предупреж- дение:

[me@linuxbox ~]$ ssh remote-sys @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that the RSA host key has just been changed. The fingerprint for the RSA key sent by the remote host is 41:ed:7a:df:23:19:bf:3c:a5:17:bc:61:b3:7f:d9:bb. Please contact your system administrator. Add correct host key in /home/me/.ssh/known_hosts to get rid of this message. Offending key in /home/me/.ssh/known_hosts:1 RSA host key for remote-sys has changed and you have requested strict checking. Host key verification failed.1

1 Перевод:

ВНИМАНИЕ: ИЗМЕНИЛСЯ ИДЕНТИФИКАТОР УДАЛЕННОГО УЗЛА! ЕСТЬ ВЕРОЯТНОСТЬ, ЧТО КТО-ТО ЗАМЫСЛИЛ ЧТО-ТО НЕДОБРОЕ! Кто-то может подслушивать вас прямо сейчас (атака “злоумышленник в середине”)! Возможно также, что просто изменился идентификационный ключ RSA узла.

Безопасные взаимодействия с удаленными узлами

243

Это сообщение появляется в двух возможных ситуациях. Первая: злоумышлен- ник мог попытаться провести атаку вида «злоумышленник в середине». Это слу- чается редко, потому что все знают, что ssh предупреждает пользователя об этом. Более вероятная причина связана с некими изменениями в удаленной системе: например, была выполнена переустановка операционной системы или сервера SSH. Однако в интересах безопасности не следует сбрасывать со счетов первую возможность. Всегда обращайтесь к системному администратору удаленной си- стемы, когда появится это сообщение.

Убедившись в безобидности причин, вызвавших это сообщение, можно исправить проблему на стороне клиента. Для этого с помощью текстового редактора (напри- мер, vim) удалите устаревший ключ из файла ~/.ssh/known_hosts. В примере со- общения выше присутствует строчка:

Offending key in /home/me/.ssh/known_hosts:11

Это означает, что подозреваемый ключ хранится в строке 1, в файле known_hosts. Удалите эту строку из файла и позвольте программе ssh принять новые идентифи- кационные данные от удаленной системы.

Помимо открытия сеанса командной оболочки в удаленной системе ssh позволяет также выполнить единственную команду. Например, в удаленной системе remote- sys можно выполнить команду free и получить результаты в локальной системе:

[me@linuxbox ~]$ ssh remote-sys free me@twin4’s password: total used free shared buffers cached Mem: 775536 507184 268352 0 110068 154596 -/+ buffers/cache: 242520 533016 Swap: 1572856 0 1572856 [me@linuxbox ~]$

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

Удаленный узел прислал идентификационный ключ RSA: 41:ed:7a:df:23:19:bf:3c:a5:17:bc:61:b3:7f:d9:bb. Пожалуйста, свяжитесь со своим системным администратором. Добавьте правильный ключ узла в /home/me/.ssh/known_hosts, чтобы избавиться от этого сообщения. Подозреваемый ключ хранится в файле /home/me/.ssh/known_hosts:1 Идентификационный ключ RSA узла remote-sys изменился, а вы запросили строгую проверку. Проверка ключа удаленного узла завершилась неудачей. — Примеч. пер. 1 Подозреваемый ключ хранится в файле /home/me/.ssh/known_hosts:1. — Примеч. пер.

244

Глава 16. Сети

[me@linuxbox ~]$ ssh remote-sys ’ls *’ > dirlist.txt me@twin4’s password: [me@linuxbox ~]$

Обратите внимание на одиночные кавычки. Они необходимы для предотвраще- ния подстановки пути в локальной системе; нам требуется, чтобы подстановка была выполнена в удаленной системе. Аналогично, если бы нам потребовалось перенаправить вывод в файл в удаленной системе, мы могли бы поместить опера- тор перенаправления и имя файла внутрь одиночных кавычек:

[me@linuxbox ~]$ ssh remote-sys ’ls * > dirlist.txt’

СОЗДАНИЕ ТУННЕЛЯ SSH

При установке SSH-соединения с удаленным узлом между локальной и удаленной системами создается шифрованный туннель. Обычно этот туннель используется для безопасной передачи команд из локальной системы в удаленную и безопас- ной передачи результатов обратно. Помимо этой основной задачи, протокол SSH позволяет также передавать через шифрованный туннель самые разные виды сетевого трафика, создавая своего рода виртуальную частную сеть (Virtual Private Network, VPN) между локальной и удаленной системами.

Чаще всего, пожалуй, эта возможность используется для передачи трафика X Window System. Из системы с действующим X-сервером (то есть отображающей графический интерфейс) можно запустить программу-клиента X (приложение с графическим интерфейсом) в удаленной системе и отображать ее интерфейс в локальной системе. Как это делается, показано в следующем примере. Пред- ставьте, что мы работаем в системе Linux с именем linuxbox, где запущен X-сервер, и нам понадобилось запустить программу xload в удаленной системе с именем remote-sys так, чтобы графический интерфейс программы отображался в локаль- ной системе. Добиться этого можно следующим способом:

[me@linuxbox ~]$ ssh -X remote-sys me@remote-sys’s password: Last login: Mon Sep 05 13:23:11 2018 [me@remote-sys ~]$ xload

После запуска программы xload в удаленной системе ее окно появится в локаль- ной системе. В некоторых системах может понадобиться использовать параметр -Y вместо -X.

Безопасные взаимодействия с удаленными узлами

245

scp и sftp

Пакет OpenSSH включает еще две программы, способные использовать шиф- рованный туннель SSH для копирования файлов по сети. Первая, scp (secure copy — безопасное копирование), используется для копирования файлов, как уже знакомая вам программа cp. Основное отличие заключается в необходи- мости предварять пути к исходному или конечному файлу именем удаленного узла и символом двоеточие за ним. Например, скопировать документ с именем document.txt из домашнего каталога в удаленной системе remote-sys в текущий рабочий каталог в локальной системе можно так:

[me@linuxbox ~]$ scp remote-sys:document.txt . me@remote-sys’s password: document.txt 100% 5581 5.5KB/s 00:00 [me@linuxbox ~]$

По аналогии с командой ssh перед именем удаленного узла можно указать имя пользователя, если имя учетной записи в удаленной системе не совпадает с име- нем учетной записи в локальной системе:

[me@linuxbox ~]$ scp bob@remote-sys:document.txt .

Вторая программа копирования файлов через SSH-соединение — sftp. Как сле- дует из ее имени — это безопасная замена для программы ftp. sftp действует практически так же, как оригинальная программа ftp, которую мы использовали выше, только передает данные не в открытом текстовом виде, а через шифрован- ный туннель SSH. sftp имеет важное преимущество перед обычной программой ftp — она не требует, чтобы на удаленном узле работал сервер FTP. Ей необходим только сервер SSH. Это означает, что любой компьютер, к которому можно под- ключиться с помощью клиента SSH, можно также использовать в качестве FTP- подобного сервера. Ниже приводится пример сеанса работы с программой sftp:

[me@linuxbox ~]$ sftp remote-sys Connecting to remote-sys… me@remote-sys’s password: sftp> ls ubuntu-8.04-desktop-i386.iso sftp> lcd Desktop sftp> get ubuntu-8.04-desktop-i386.iso Fetching /home/me/ubuntu-8.04-desktop-i386.iso to ubuntu-8.04-desktop-i386.iso /home/me/ubuntu-8.04-desktop-i386.iso 100% 699MB 7.4MB/s 01:35 sftp> bye

246

Глава 16. Сети

СОВЕТ

Протокол SFTP поддерживается многими диспетчерами файлов с графическим интерфейсом, входящими в состав дистрибутивов Linux. В GNOME или KDE можно ввести в адресную строку идентификатор URI, начинающийся с sftp://, и работать с файлами, хранящимися в удаленной системе с действующим сер- вером SSH.

СУЩЕСТВУЮТ ЛИ КЛИЕНТЫ SSH ДЛЯ WINDOWS?

Допустим, что вы работаете за компьютером с операционной системой Windows и вам нужно зайти на свой сервер с Linux, чтобы выполнить некую работу. Как быть? Нужно просто установить в Windows программу клиента SSH! Существует довольно много таких программ. Наиболее популярной, пожалуй, является про- грамма PuTTY Симона Тэтхэма (Simon Tatham) и его команды. Программа PuTTY отображает окно терминала и позволяет пользователю Windows открыть сеанс SSH (или telnet) с удаленным узлом. Программа также предоставляет аналоги программ scp и sftp.

Программа PuTTY доступна по адресу http://www.chiark.greenend.org.uk/~sgtatham/ putty/.

Заключение

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

17 Поиск файлов

Блуждая по системе Linux, мы совершенно ясно увидели, что типичная Linux- система содержит множество файлов. В связи с этим возникает вопрос: как ис- кать нужные файлы? Мы уже знаем, что файловая система в Linux организована в соответствии с определенными соглашениями, которые переходили из одного поколения Unix-подобных систем в другое, но огромное число файлов может по- рождать труднопреодолимую проблему.

В этой главе мы рассмотрим два инструмента для поиска файлов в системе:

locate — выполняет поиск файлов по именам;

find — выполняет поиск файлов в иерархии каталогов.

Мы также познакомимся с командой, часто используемой вместе с командами по- иска файлов для обработки списков с результатами:

xargs — конструирует команды на основе данных, полученных из стандартного ввода, и выполняет их.

Дополнительно в этой главе будет представлена пара команд, которые помогут нам в наших исследованиях:

touch — изменяет времена, ассоциированные с файлом;

stat — выводит статус файла или файловой системы.

locate — простой способ поиска файлов

Программа locate выполняет быстрый поиск в базе данных имен файлов и выво- дит все имена, соответствующие искомой строке. Допустим, к примеру, что нужно

248

Глава 17. Поиск файлов

найти все программы с именами, начинающимися с zip. Поскольку требуется най- ти программы, можно предположить, что имя каталога с программами оканчива- ется на bin/. Соответственно можно попробовать выполнить поиск с помощью locate, как показано ниже:

[me@linuxbox ~]$ locate bin/zip

locate выполнит поиск в базе данных имен файлов и выведет все имена, содержа- щие строку bin/zip:

/usr/bin/zip /usr/bin/zipcloak /usr/bin/zipgrep /usr/bin/zipinfo /usr/bin/zipnote /usr/bin/zipsplit

Если к результатам поиска предъявляются более строгие требования, команду locate можно объединить с другими инструментами, такими как grep, позволяю- щими осуществить более сложный поиск:

[me@linuxbox ~]$ locate zip | grep bin /bin/bunzip2 /bin/bzip2 /bin/bzip2recover /bin/gunzip /bin/gzip /usr/bin/funzip /usr/bin/gpg-zip /usr/bin/preunzip /usr/bin/prezip /usr/bin/prezip-bin /usr/bin/unzip /usr/bin/unzipsfx /usr/bin/zip /usr/bin/zipcloak /usr/bin/zipgrep /usr/bin/zipinfo /usr/bin/zipnote /usr/bin/zipsplit

Программа locate существует уже много лет, и за эти годы было создано несколько ее вариантов, получивших широкое распространение. Два из них, наиболее часто используемые в современных дистрибутивах Linux, — это slocate и mlocate, ко- торые, впрочем, являются символическими ссылками, указывающими на locate.

find — сложный способ поиска файлов

249

Разные версии locate имеют пересекающиеся множества параметров. Некоторые поддерживают поиск с использованием регулярных выражений (о которых рас- сказывается в главе 19) и групповые символы. Загляните на страницу справоч- ного руководства (man) для locate, чтобы определить, какая версия установлена у вас.

ОТКУДА БЕРЕТСЯ БАЗА ДАННЫХ ДЛЯ LOCATE?

В некоторых дистрибутивах при попытке запустить locate сразу после установки она потерпит неудачу, но если попытаться использовать ее на следующий день, все, как ни странно, будет работать как надо. Так в чем же проблема? База дан- ных для locate создается другой программой с именем updatedb. Обычно она периодически запускается как задание cron; то есть она запускается системным планировщиком cron через регулярные интервалы времени. В большинстве систем, в состав которых входит locate, программа updatedb запускается один раз в сут- ки. Поскольку база данных не обновляется непрерывно, скорее всего, locate не находит самые свежие файлы. Чтобы решить эту проблему, запустите программу updatedb вручную от имени суперпользователя.

find — сложный способ поиска файлов

В отличие от программы locate, выполняющей поиск файлов по именам, про- грамма find ищет файлы согласно заданным атрибутам в указанном каталоге (и во вложенных подкаталогах). Далее мы потратим время на исследование команды find, потому что она имеет множество интересных особенностей, с которыми мы неоднократно столкнемся, когда начнем знакомиться с приемами программиро- вания в последующих главах.

В простейшем случае программе find можно передать одно или несколько имен каталогов для поиска. Например, с ее помощью можно получить список содержи- мого домашнего каталога:

[me@linuxbox ~]$ find ~

Для большинства активных пользователей она выдаст длинный список. Так как список выводится в стандартный вывод, его можно передать по конвейеру другим программам. Воспользуемся программой wc, чтобы подсчитать число файлов:

[me@linuxbox ~]$ find ~ | wc -l 47068

250

Глава 17. Поиск файлов

Ух ты, вот это мы поработали! Вся прелесть команды find в том, что ее можно ис- пользовать для поиска файлов, соответствующих определенным критериям. Она делает это посредством (что немного странно) применения проверок, операций и параметров. Сначала рассмотрим проверки.

Проверки

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

[me@linuxbox ~]$ find ~ -type d | wc -l 1695

Добавив проверку -type d, мы ограничились поиском только каталогов. Но точно так же можно ограничить поиск только обычными файлами:

[me@linuxbox ~]$ find ~ -type f | wc -l 38737

В табл. 17.1 перечислены проверки типов файлов, наиболее часто используемые с командой find.

Таблица 17.1. Проверки типов файлов в find

Тип файлов

Описание

b

c

d

f

l

Специальный файл блочного устройства

Специальный файл символьного устройства

Каталог

Обычный файл

Символическая ссылка

Добавив дополнительные проверки, можно выполнять поиск файлов по размеру и имени. Давайте найдем все обычные файлы с именами, соответствующими ша- блону *.JPG, и имеющие размер больше 1 мегабайта:

[me@linuxbox ~]$ find ~ -type f -name “*.JPG” -size +1M | wc -l 840

В этом примере мы добавили проверку -name с шаблоном имени файла. Обрати- те внимание, что шаблон заключен в кавычки, чтобы предотвратить подстановку

find — сложный способ поиска файлов

251

имен файлов командной оболочкой. Далее мы добавили проверку -size со стро- кой +1M. Начальный символ «плюс» указывает, что требуется искать файлы, раз- мер которых превышает указанное число. Начальный символ «минус» изменил бы значение строки на противоположное: «меньше указанного числа». Число без знака означает: «в точности соответствует значению». Буква M в конце определяет единицы измерения — мегабайты (Megabytes). В табл. 17.2 перечислены символы, которые можно использовать для обозначения единиц измерения.

Таблица 17.2. Единицы измерения, поддерживаемые командой find

Символ

Единица измерения

b

c

w

k

M

G

Блоки размером по 512 байт (используется по умолчанию, если иное не указано явно)

Байты

2-байтные слова

Килобайты (Kilobytes, блоки по 1024 байт)

Мегабайты (Megabytes, блоки по 1 048 576 байт)

Гигабайты (Gigabytes, блоки по 1 073 741 824 байт)

Команда find поддерживает множество разнообразных проверок. В табл. 17.3 при- водится краткое описание наиболее часто используемых из них. Обратите внима- ние, что в случаях, когда требуется числовой аргумент, допустимо использование формы записи с символами + и -, обсуждавшейся выше.

Таблица 17.3. Проверки, поддерживаемые командой find

Проверка

Описание

-cmin n

-cnewer имя

-ctime n

Соответствует файлам или каталогам, содержимое или атрибуты которых последний раз изменялись точно n минут назад. Чтобы выра- зить условие «менее n минут назад», используйте -n; чтобы выразить условие «более n минут назад», используйте +n

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

Соответствует файлам или каталогам, содержимое или атрибуты (то есть разрешения) которых последний раз изменялись более чем n*24 часов назад

-empty

Соответствует пустым файлам и каталогам

252

Глава 17. Поиск файлов

Таблица 17.3 (окончание)

Проверка

Описание

-group группа

Соответствует файлам или каталогам, принадлежащим указанной группе. Группа может задаваться именем или числовым идентифика- тором группы

-iname шаблон

Действует так же, как проверка -name, но различает регистр симво- лов

-inum n

-mmin n

-mtime n

Соответствует файлам с номером индексного узла (inode) n. Эту проверку удобно использовать для поиска всех жестких ссылок на определенный индексный узел

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

Соответствует файлам или каталогам, содержимое которых послед- ний раз изменялось n*24 часов назад

-name шаблон

Соответствует файлам и каталогам, имена которых совпадают с ука- занным шаблоном

-newer имя

-nouser

-nogroup

-perm режим

Соответствует файлам и каталогам, содержимое которых последний раз изменялось позже, чем у файла с указанным именем. Эта провер- ка может пригодиться в сценариях, выполняющих резервное копи- рование файлов. Каждый раз, в процессе создания резервной копии, можно обновлять файл (например, файл журнала) и затем с помощью find определять, какие файлы изменились с момента последнего обновления

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

Соответствует файлам и каталогам, не принадлежащим какой-либо допустимой группе

Соответствует файлам или каталогам с указанным режимом доступа. Режим может задаваться восьмеричным числом или иметь символиче- скую форму

-samefile имя

Действует так же, как проверка -inum. Соответствует файлам с тем же номером индексного узла (inode), что и файл с указанным именем

-size n

-type c

-user имя

Соответствует файлам с размером n

Соответствует файлам с типом c

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

find — сложный способ поиска файлов

253

Это не полный список. Дополнительные детали ищите на странице справочного руководства (man) для команды find.

Операторы

Несмотря на большое число проверок, поддерживаемых командой find, мы все еще нуждаемся в способе определения логических отношений между проверками. На- пример, представьте, что в некотором каталоге мы хотим найти все файлы и под- каталоги с небезопасными разрешениями. Для этого можно было бы выполнить поиск всех файлов с разрешениями, отличающимися от 0600, и каталогов с раз- решениями, отличающимися от 0700. К счастью, find поддерживает возможность комбинирования проверок с помощью логических операторов с целью определить более сложные критерии отбора. Выразить вышеупомянутую проверку можно так:

[me@linuxbox ~]$ find ~ ( -type f -not -perm 0600 ) -or ( -type d -not –perm 0700 )

Ф-фу! Как неизящно! Что все это значит? На самом деле операторы перестанут казаться избыточно сложными, как только вы с ними познакомитесь поближе (табл. 17.4).

Таблица 17.4. Логические операторы, поддерживаемые командой find

Оператор

Описание

-and

-or

-not

( )

Соответствует, если выполняются условия в проверках с обеих сторон от оператора. Можно сократить до -a. Обратите внимание, что в отсутствие операторов по умолчанию подразумевается -and

Соответствует, если выполняется условие с одной из сторон от операто- ра. Можно сократить до -o

Соответствует, если условие в проверке, следующей за оператором, не выполняется. Можно сократить до -!

Группируют проверки и операторы для формирования крупных выра- жений. Используются для управления порядком проверок. По умолча- нию проверки выполняются слева направо. Часто используются для изменения порядка проверок по умолчанию, чтобы получить желаемый результат. Даже если скобки не нужны, иногда полезно включать их, чтобы сделать команды более наглядными. Не забывайте, что круглые скобки имеют специальное значение для командной оболочки, поэтому их нужно экранировать, чтобы они были переданы команде find как аргументы. Обычно экранирование выполняют с помощью символа «обратный слеш»

254

Глава 17. Поиск файлов

Имея список операторов под рукой, попробуем разобрать команду find. На самом верхнем уровне мы видим, что проверки объединены в две группы, разделенные оператором -or:

(выражение 1) -or (выражение 2)

В этом есть определенный смысл, потому что мы хотим найти файлы с одним на- бором разрешений и каталоги — с другим. Но если выполняется поиск и файлов, и каталогов, почему используется оператор -or вместо -and? Потому, что find, вы- полняя обход файлов и каталогов, оценивает их по одному, чтобы понять, соот- ветствует ли файл или каталог указанным проверкам. Команде требуется узнать, является ли очередной элемент файлом или каталогом с «плохими» разрешения- ми. Один и тот же элемент не может соответствовать сразу двум условиям. То есть если развернуть сгруппированные выражения, можно увидеть следующее:

(файл с плохими разрешениями) -or (каталог с плохими разрешениями)

Наша следующая задача — проверить «плохие разрешения». Как это сделать? Фактически никак. Но мы можем проверить «неудовлетворительные разреше- ния», зная, что такое «удовлетворительные разрешения». В случае с файлами удовлетворительными являются разрешения 0600, для каталогов — 0700. Выра- жение, проверяющее «неудовлетворительные» разрешения, выглядит так:

-type f -and -not -perms 0600

а для каталогов так:

-type d -and -not -perms 0700

Как отмечалось в табл. 17.4, оператор -and можно просто удалить, так как он под- разумевается по умолчанию. Теперь, объединив все вместе, мы получим оконча- тельную команду:

find ~ (-type f -not -perms 0600) -or (-type d -not -perms 0700)

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

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

выражение1 -оператор выражение2

find — сложный способ поиска файлов

255

Выражение1 будет вычислено в любом случае, а вот будет ли вычислено выражение2, зависит от оператора. В табл. 17.5 показано, как это работает.

Таблица 17.5. Действие логических операторов -and/-or команды find

Результат выражения1

Оператор

Выражение2

Истина

Ложь

Истина

Ложь

-and

-and

-or

-or

Всегда вычисляется

Никогда не вычисляется

Никогда не вычисляется

Всегда вычисляется

Почему так происходит? Это сделано для повышения производительности. Возь- мем для примера оператор -and. Мы знаем, что выражение выражение1 -and выраже- ние2 не может быть истинным, если выражение1 вернет ложный результат, поэтому нет смысла вычислять выражение2. Аналогично, если имеется выражение выражение1 -or выражение2 и выражение1 вернет истинный результат, нет смысла вычислять вы- ражение2, так как уже известно, что выражение1 -or выражение2 является истинным.

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

Предопределенные операции

Давайте попробуем выполнить определенные действия в процессе поиска! Иметь список с результатами работы команды find уже неплохо, но представьте, что нам нужно выполнить некие операции с элементами списка. К счастью, find позволяет выполнять наши операции, основываясь на результатах поиска. Существует мно- жество предопределенных операций и несколько способов применения операций, определяемых пользователем. Для начала взгляните на неполный список предо- пределенных операций в табл. 17.6.

Таблица 17.6. Предопределенные операции, поддерживаемые командой find

Операция

Описание

-delete

-ls

Удаляет текущий найденный файл

Действует эквивалентно команде ls -dils в отношении найденного файла. Результат выводится в стандартный вывод

256

Глава 17. Поиск файлов

Таблица 17.6 (окончание)

Операция

Описание

-print

-quit

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

Завершает выполнение команды после обнаружения первого совпадения

Поддерживаемых операций намного больше, чем показано здесь. Полный список можно найти на странице справочного руководства (man) для команды find.

В нашем первом примере мы выполнили команду:

find ~

Она выводит список всех файлов и подкаталогов, хранящихся в домашнем ката- логе. Список выводится просто потому, что в отсутствие других операций предпо- лагается операция -print. То есть эту команду можно было бы выразить так:

find ~ -print

Программу find можно использовать для удаления файлов, соответствующих определенным критериям. Например, следующая команда удалит все файлы с расширением .bak (которое часто используется для обозначений резервных ко- пий файлов):

find ~ -type f -name ‘*.bak’ -delete

Эта команда найдет в домашнем каталоге (и во вложенных подкаталогах) пользо- вателя все файлы с расширением .bak и удалит их.

ВНИМАНИЕ

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

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

find ~ -type f -name ‘*.bak’ -print

find — сложный способ поиска файлов

257

Как видите, эта команда ищет обычные файлы (-type f) с расширением .bak (-name ‘*.bak’) и выводит относительные пути к ним в стандартный вывод (-print). Однако такой порядок работы команды определяется логическими отношения- ми между всеми проверками и операциями. Как вы помните, между проверками и операциями по умолчанию подразумевается отношение -and. Ту же команду можно выразить, добавив логические операторы:

find ~ -type f -and -name ‘*.bak’ -and -print

Теперь, имея перед глазами это определение, взгляните на табл. 17.7, где показано, как логические операторы влияют на порядок выполнения.

Таблица 17.7. Влияние логических операторов

Проверка/операция

Выполняется, когда

-print

-type f and -name ‘*.BAK’ истинно

-name ‘*.bak’

-type f истинно

-type f

Всегда выполняется, потому что это первая проверка/опера- ция в отношении -and

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

find ~ -print -and -type f -and -name ‘*.bak’

Эта версия команды выведет каждый файл (операция -print всегда возвращает истинное значение), а затем проверит тип файла и его расширение.

Операции, определяемые пользователем

Помимо предопределенных операций можно также вызывать произвольные ко- манды. Традиционно с этой целью используется операция -exec, что показано ниже:

-exec команда {} ;

где команда — это имя команды, {} — символическое представление текущего пути к файлу и точка с запятой — обязательный разделитель, обозначающий конец

258

Глава 17. Поиск файлов

команды. Следующий пример демонстрирует использование -exec для получения эффекта, аналогичного операции -delete, обсуждавшейся выше:

-exec rm ‘{}’ ‘;’

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

Кроме того, существует возможность выполнять пользовательские операции ин- терактивно. Если заменить операцию -exec операцией -ok, перед выполнением каждой указанной команды будет выводиться запрос:

find ~ -type f -name ‘foo*’ -ok ls -l ‘{}’ ‘;’ ? y -rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo ? y -rw-r–r– 1 me me 0 2016-09-19 12:53 /home/me/foo.txt

Эта команда ищет файлы с именами, начинающимися со строки foo, и для каж- дого найденного файла выполняет команду ls -l. Операция -ok запрашивает под- тверждение у пользователя, прежде чем выполнить команду ls.

Увеличение эффективности

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

ls -l файл1 ls -l файл2

предпочтительнее было бы выполнить команду

ls -l файл1 файл2

Здесь команда выполняется только один раз, а не несколько. Существует два спо- соба добиться этого: традиционный, с использованием внешней команды xargs, и альтернативный, с использованием новой возможности в самой команде find. Обсудим сначала альтернативный способ.

find — сложный способ поиска файлов

259

Если заменить завершающий символ «точка с запятой» знаком «плюс», в команде find активируется функция объединения результатов в список аргументов для вы- зова единственного экземпляра требуемой команды. Вернемся к нашему примеру. Команда:

find ~ -type f -name ‘foo*’ -exec ls -l ‘{}’ ‘;’ -rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo -rw-r–r– 1 me me 0 2016-09-19 12:53 /home/me/foo.txt

будет вызывать ls для каждого найденного файла. Изменив команду, как показа- но ниже:

find ~ -type f -name ‘foo*’ -exec ls -l ‘{}’ + -rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo -rw-r–r– 1 me me 0 2016-09-19 12:53 /home/me/foo.txt

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

xargs

Команда xargs предлагает очень интересную возможность. Она принимает вход- ные данные со стандартного ввода и преобразует их в список аргументов для ука- занной команды. В данном примере ее можно было бы использовать так:

find ~ -type f -name ‘foo*’ -print | xargs ls -l -rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo -rw-r–r– 1 me me 0 2016-09-19 12:53 /home/me/foo.txt

Здесь вывод команды find передается по конвейеру команде xargs, которая, в свою очередь, конструирует список аргументов для команды ls и выполняет ее.

ПРИМЕЧАНИЕ

Несмотря на то что в командную строку можно включить большое число аргу- ментов, оно не бесконечно. Не исключено, что в результате получится команда, слишком длинная для командной оболочки. Когда длина командной строки пре- вышает максимально допустимый размер, xargs выполнит указанную команду с максимально возможным числом аргументов и затем повторит процесс, пока не исчерпает все, что получит со стандартного ввода. Чтобы увидеть максимально воз- можную длину командной строки, выполните xargs с параметром –show-limits.

260

Глава 17. Поиск файлов

ОБРАБОТКА ФАЙЛОВ С НЕОБЫЧНЫМИ ИМЕНАМИ

Unix-подобные системы позволяют встраивать в имена файлов пробелы (и даже символы перевода строки). Это порождает проблемы при выполнении программ, таких как xargs, конструирующих списки аргументов для других про- грамм. Внутренние пробелы интерпретируются как разделители, и получившаяся коман да будет интерпретировать слова, разделенные пробелами, как отдельные аргументы. Для решения этой проблемы find и xarg предлагают использовать в качестве разделителя аргументов пустой символ (null character). В кодировке ASCII пустой символ определен как символ с нулевым кодом (в противополож- ность пробелу, например, который в кодировке ASCII определен как символ с кодом 32). Команда find поддерживает операцию -print0, которая произво- дит вывод имен файлов, разделенных пустым символом, а команда xargs имеет параметр –null, позволяющий организовать прием значений, разделенных пустым символом. Например:

find ~ -iname ‘*.jpg’ -print0 | xargs –null ls -l

Этот прием гарантирует правильную обработку любых имен файлов, даже со- держащих пробелы.

Возвращаемся в песочницу

Пришло время применить find для решения некоторых практических (почти) за- дач. Сначала создадим песочницу с множеством файлов и каталогов:

[me@linuxbox ~]$ mkdir -p playground/dir-{001..100} [me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}

Какая мощь командной строки! Эти две строки создают каталог playground, со- держащий 100 подкаталогов и 26 пустых файлов в каждом. Попробуйте-ка то же самое сделать в графическом интерфейсе!

Это волшебство мы сотворили с помощью уже знакомой команды (mkdir), меха- низма подстановки в командной оболочке (фигурные скобки) и новой команды touch. Объединив команду mkdir с параметром -p (который вынуждает mkdir соз- дать родительские каталоги в указанном пути) с подстановкой фигурных скобок, мы смогли создать 100 каталогов.

Команда touch обычно используется для обновления времени последнего измене- ния файлов. Но если передать ей имя несуществующего файла, она создаст пустой файл.

find — сложный способ поиска файлов

261

В нашей песочнице мы создали 100 файлов с именем file-A. Давайте найдем их:

[me@linuxbox ~]$ find playground -type f -name ‘file-A’

Обратите внимание, что, в отличие от ls, find возвращает результаты в несортиро- ванном порядке. Порядок определяется организацией устройства хранения. Мы можем убедиться, что действительно имеем 100 файлов с именем file-A:

[me@linuxbox ~]$ find playground -type f -name ‘file-A’ | wc -l 100

А теперь выполним поиск файлов по времени их последнего изменения. Этот под- ход можно использовать для создания резервных копий или организации файлов в хронологическом порядке. Для этого сначала создадим эталонный файл, время последнего изменения которого будет использоваться для сравнения:

[me@linuxbox ~]$ touch playground/timestamp

Эта команда создаст пустой файл timestamp и установит время его последнего из- менения равным текущему времени. Мы можем убедиться в этом, использовав еще одну полезную команду, stat, которую можно рассматривать как своего рода форсированную версию ls. Команда stat выводит всю информацию о файле и его атрибутах, которой обладает система:

[me@linuxbox ~]$ stat playground/timestamp File: `playground/timestamp’ Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 803h/2051d Inode: 14265061 Links: 1 Access: (0644/-rw-r–r–) Uid: ( 1001/ me) Gid: ( 1001/ me) Access: 2018-10-08 15:15:39.000000000 -0400 Modify: 2018-10-08 15:15:39.000000000 -0400 Change: 2018-10-08 15:15:39.000000000 -0400

Если применить команду touch к файлу еще раз и затем исследовать его с по- мощью stat, мы увидим, что время последнего его изменения обновилось:

[me@linuxbox ~]$ touch playground/timestamp [me@linuxbox ~]$ stat playground/timestamp File: `playground/timestamp’ Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 803h/2051d Inode: 14265061 Links: 1 Access: (0644/-rw-r–r–) Uid: ( 1001/ me) Gid: ( 1001/ me) Access: 2018-10-08 15:23:33.000000000 -0400 Modify: 2018-10-08 15:23:33.000000000 -0400 Change: 2018-10-08 15:23:33.000000000 -0400

262

Глава 17. Поиск файлов

Далее воспользуемся командой find, чтобы обновить время последнего изменения некоторых файлов в нашей песочнице:

[me@linuxbox ~]$ find playground -type f -name ‘file-B’ -exec touch ‘{}’ ‘;’

Эта команда обновит время последнего изменения для всех файлов с именем file-B, имеющихся в песочнице. Теперь найдем с помощью find обновленные фай- лы, сравнив все файлы с эталонным файлом timestamp:

[me@linuxbox ~]$ find playground -type f -newer playground/timestamp

В результате мы получим все 100 файлов с именем file-B. Поскольку команда touch применялась ко всем файлам file-B в песочнице после обновления файла timestamp, они оказались «новее», чем timestamp, и потому были идентифицирова- ны проверкой -newer.

В заключение вернемся к проверке плохих разрешений, выполнявшейся выше, и применим ее к каталогу playground:

[me@linuxbox ~]$ find playground ( -type f -not -perm 0600 ) -or ( -type d -not -perm 0700 )

Эта команда выведет все 100 каталогов и 2600 файлов, хранящихся в playground (а также файл timestamp и сам каталог playground, всего 2702 элемента), потому что ни один из них не соответствует нашему определению «удовлетворительные разрешения». Вооружившись новыми знаниями об операторах и операциях, до- бавим в эту команду операции для применения новых разрешений к файлам и ка- талогам в песочнице:

[me@linuxbox ~]$ find playground ( -type f -not -perm 0600 -exec chmod 0600 ‘{}’ ‘;’ ) -or ( -type d -not -perm 0700 -exec chmod 0700 ‘{}’ ‘;’ )

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

Параметры

Наконец, мы добрались до параметров. Параметры помогают управлять областью поиска. Они могут включаться в выражения команды find наряду с другими про- верками и операциями. В табл. 17.8 перечислены наиболее часто используемые параметры.

Заключение

263

Таблица 17.8. Параметры команды find

Параметр

-depth

-maxdepth число_уровней

-mindepth число_уровней

-mount

-noleaf

Описание

Требует от find обработать сначала файлы в каталогах и только потом каталоги. Этот параметр автоматически применяется с операцией -delete

Устанавливает максимальное число уровней, на которое команда find может опускаться в дереве каталогов, вы- полняя проверки и операции

Устанавливает минимальное число уровней, на которое команда find должна опуститься в дереве каталогов перед выполнением проверок и операций

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

Требует от find не оптимизировать поиск, опираясь на предположение, что поиск ведется в Unix-подобной фай- ловой системе. Этот параметр необходимо использовать при обходе файловых систем DOS/Windows CD-ROM

Заключение

Вы видели сами, как легко определять местоположение файлов (locate) и как сложно искать их (find). Каждая из этих команд имеет свою область применения. Найдите время, чтобы исследовать богатые возможности find. При регулярном ис- пользовании эта команда поможет вам лучше понять операции с файловой систе- мой Linux.

18 Архивация и резервное

копирование

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

В этой главе мы рассмотрим несколько программ, часто используемых для управ- ления коллекциями файлов:

Программы сжатия:

gzip — сжимает и распаковывает файлы;

bzip2 — программа поблочного сжатия файлов.

Программы архивирования:

tar — утилита архивирования на ленту;

zip — упаковывает и сжимает файлы.

И программа синхронизации файлов:

rsync — выполняет синхронизацию файлов и каталогов с удаленной системой.

Сжатие файлов

На протяжении всей истории развития вычислительных технологий не прекра- щались попытки размещения большего числа данных в меньшем объеме, будь то

Сжатие файлов

265

память, устройства хранения или полоса пропускания сети. Многие устройства и технологии, прочно вошедшие в обиход, такие как переносные плееры, телеви- дение высокой четкости или широкополосный доступ в Интернет, обязаны своим существованием эффективным технологиям сжатия данных.

Сжатие данных — это процесс устранения избыточных данных. Давайте рассмо- трим воображаемый пример. Допустим, у нас есть файл, хранящий изображение абсолютно черного квадрата размером 100 на 100 пикселов. В терминах хране- ния данных (если предположить, что каждый пиксел представлен 24 битами, или 3 байтами) изображение занимает 30 000 байт:

100 × 100 × 3 = 30 000.

Изображение, состоящее из пикселов одного цвета, содержит массу избыточных данных. Будь мы умнее, мы могли бы закодировать данные в виде простого описа- ния того факта, что изображение представлено блоком из 30 000 пикселов черного цвета. То есть вместо хранения блока данных с 30 000 нулей (черный цвет в фай- лах изображений обычно представлен нулевым значением) мы могли бы сжать данные до числа 30 000 с последующим нулем, описывающим цвет. Такая схема сжатия, она называется кодированием длин серий (run-length encoding), является одной из простейших технологий сжатия. Современные технологии не в пример сложнее и эффективнее, но главная цель осталась прежней — избавиться от из- быточных данных. Алгоритмы сжатия (математические методики, применяемые для осуществления сжатия) делятся на две основные категории:

 Сжатие без потерь (lossless) и с потерями (lossy). Сжатие без потерь гаран- тирует сохранность всех данных, содержащихся в оригинале. То есть после восстановления файла из сжатой версии восстановленный файл будет иметь в точности то же содержимое, что и несжатый оригинал.

 Сжатие с потерями (lossy), с другой стороны, удаляет некоторые данные во время сжатия, чтобы обеспечить более высокую степень сжатия. Восстанов- ленный файл в этом случае не будет совпадать с оригинальной версией, скорее он будет близкой аппроксимацией оригинала. Примерами сжатия с потерями могут служить формат JPEG (для изображений) и MP3 (для музыкальных произведений).

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

gzip

Программа gzip используется для сжатия одного или нескольких файлов. Во время работы она замещает оригинальный файл его сжатой версией. Соответствующая

266

Глава 18. Архивация и резервное копирование

программа gunzip используется для восстановления сжатых файлов до исходного состояния. Например:

[me@linuxbox ~]$ ls -l /etc > foo.txt [me@linuxbox ~]$ ls -l foo.* -rw-r–r– 1 me me 15738 2018-10-14 07:15 foo.txt [me@linuxbox ~]$ gzip foo.txt [me@linuxbox ~]$ ls -l foo.* -rw-r–r– 1 me me 3230 2018-10-14 07:15 foo.txt.gz [me@linuxbox ~]$ gunzip foo.txt [me@linuxbox ~]$ ls -l foo.* -rw-r–r– 1 me me 15738 2018-10-14 07:15 foo.txt

В этом примере мы создали текстовый файл с именем foo.txt, записав в него список содержимого каталога /etc. Далее мы запустили программу gzip, которая замени- ла оригинальный файл сжатой версией с именем foo.txt.gz. В списке содержимого каталога, который был получен с использованием шаблона foo.*, можно видеть, что исходный файл действительно был замещен сжатой версией, и эта сжатая вер- сия получилась почти в пять раз меньше оригинала. Можно также заметить, что сжатый файл имеет такие же разрешения и время, что и оригинал.

Далее мы запустили программу gunzip, чтобы распаковать файл. После этого, как видите, сжатая версия была замещена оригиналом, и снова с теми же разрешени- ями и временем.

Программа gzip имеет множество параметров, часть которых описана в табл. 18.1.

Таблица 18.1. Параметры команды gzip

Пара- метр

Длинный параметр

Описание

-c

-d

-f

-h

-l

-r

-t

–stdout

–to-stdout

–decompress

–uncompress

–force

–help

–list

–recursive

Выводит результат на стандартный вывод и сохраняет ориги- нальные файлы

Распаковывает файл. С этим параметром gzip действует как gunzip

Принудительное (force) сжатие, даже если сжатая версия оригинального файла уже существует

Выводит информацию о порядке использования

Выводит список статистик для каждого сжатого файла

Если один или несколько аргументов команды являются каталогами, выполняет рекурсивное сжатие файлов, находя- щихся в них

–test

Проверяет целостность сжатого файла

Сжатие файлов

267

Пара- метр

Длинный параметр

-v

–verbose

-число

Описание

Выводит в процессе работы сообщения с информацией о ходе сжатия

Устанавливает степень сжатия. Числом может быть любое целочисленное значение в диапазоне от 1 (высокая ско- рость работы, низкая степень сжатия) до 9 (низкая скорость работы, высокая степень сжатия). Значения 1 и 9 можно так же заменить параметрами –fast и –best соответственно. По умолчанию используется значение 6

Вернемся к нашему примеру:

[me@linuxbox ~]$ gzip foo.txt [me@linuxbox ~]$ gzip -tv foo.txt.gz foo.txt.gz: OK [me@linuxbox ~]$ gzip -d foo.txt.gz

Здесь мы заменили файл foo.txt его сжатой версией с именем foo.txt.gz. Затем про- верили целостность сжатой версии, передав параметры -t и -v. В заключение мы распаковали файл, вернув его исходное состояние.

gzip можно также использовать несколько необычным способом, через стандарт- ные ввод и вывод:

[me@linuxbox ~]$ ls -l /etc | gzip > foo.txt.gz

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

Программа gunzip, которая распаковывает файлы, сжатые с помощью gzip, пред- полагает, что имена файлов оканчиваются расширением .gz, поэтому его можно не указывать при условии, что имя файла в команде не соответствует существующе- му несжатому файлу:

[me@linuxbox ~]$ gunzip foo.txt

Если цель только в том, чтобы просмотреть содержимое сжатого текстового фай- ла, сделать это можно так:

[me@linuxbox ~]$ gunzip -c foo.txt | less

ПРИМЕЧАНИЕ

Существует также программа zless. Она заменяет собой конвейер, представ- ленный выше.

268

Глава 18. Архивация и резервное копирование

bzip2

Программа bzip2 Джулиана Сюарда похожа на программу gzip, но использует иной алгоритм, который обеспечивает более высокую степень сжатия ценой сни- жения скорости работы. Во многих отношениях она действует точно так же, как gzip. Файл, сжатый с помощью bzip2, получает расширение .bz2:

[me@linuxbox ~]$ ls -l /etc > foo.txt [me@linuxbox ~]$ ls -l foo.txt -rw-r–r– 1 me me 15738 2018-10-17 13:51 foo.txt [me@linuxbox ~]$ bzip2 foo.txt [me@linuxbox ~]$ ls -l foo.txt.bz2 -rw-r–r– 1 me me 2792 2018-10-17 13:51 foo.txt.bz2 [me@linuxbox ~]$ bunzip2 foo.txt.bz2

Как видите, bzip2 можно использовать так же, как gzip. Все параметры програм- мы gzip (кроме -r), представленные выше, поддерживаются также программой bzip2. Но имейте в виду, что параметр степени сжатия (-число) имеет несколько иной смысл для bzip2. В паре с bzip2 поставляются программы bunzip2 и bzcat для распаковывания файлов.

Существует также программа bzip2recover для восстановления поврежденных файлов формата .bz2.

НЕ ПРЕВРАЩАЙТЕСЬ В ОДЕРЖИМЫХ МАНИЕЙ СЖАТИЯ

Мне иногда приходится видеть, как кто-то пытается сжать файл, уже сжатый с при- менением эффективного алгоритма сжатия, выполняя нечто подобное:

$ gzip picture.jpg

Это напрасная трата времени и дискового пространства! Если применить процедуру сжатия к уже сжатому файлу, зачастую получается файл большего размера. Это объясняется тем, что все методики сжатия добавляют в файл некоторую служебную информацию, описывающую сжатие. Если попытаться сжать файл, не содержащий избыточной информации, сжатие не приведет к экономии места, которая могла бы покрыть расходы на хранение служебной информации.

Архивирование файлов

Часто вместе со сжатием используется операция архивирования. Архивирова- ние — это процесс сбора множества файлов и упаковывание их в один большой файл. Архивирование часто применяется как один из этапов создания резервных

Архивирование файлов

269

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

tar

В мире программного обеспечения для Unix-подобных систем существует про- грамма tar — классический инструмент для архивирования файлов. Ее имя, ко- торое расшифровывается как tape archive (архив на магнитной ленте), указывает, что первоначально инструмент предназначался для создания архивов на магнит- ных лентах. Он до сих пор используется для решения этой традиционной задачи, но с неменьшим успехом поддерживает другие устройства хранения. Нам часто приходится видеть имена файлов с расширением .tar или .tgz, которые обознача- ют «простые» tar-архивы и архивы, сжатые с помощью gzip соответственно. Ар- хив может состоять из группы отдельных файлов, иерархий каталогов или и того и другого. Команда tar имеет следующий синтаксис:

tar режим[параметры] путь…

где под режимом подразумевается один из нескольких режимов работы, перечис- ленных в табл. 18.2 (здесь представлены не все параметры; полный список вы най- дете на странице справочного руководства (man) для tar).

Таблица 18.2. Режимы команды tar

Режим

Описание

c

x

r

t

Создать архив из списка файлов и/или каталогов

Извлечь файлы из архива

Добавить указанный файл и/или каталог в конец архива

Вывести список содержимого архива

В программе tar используется немного непривычный способ определения пара- метров, поэтому рассмотрим несколько примеров ее использования. Для начала воссоздадим нашу песочницу, как мы это делали в предыдущей главе:

[me@linuxbox ~]$ mkdir -p playground/dir-{001..100} [me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}

Далее создадим архив всей песочницы:

[me@linuxbox ~]$ tar cf playground.tar playground

270

Глава 18. Архивация и резервное копирование

Эта команда создаст tar-архив с именем playground.tar, включающий всю иерар- хию каталогов песочницы. Как видите, режим и параметр f, который использует- ся для определения имени tar-архива, можно объединять, и при этом не требуется использовать начальный дефис. Но имейте в виду, что режим всегда должен ука- зываться первым, перед любыми параметрами. Посмотреть содержимое архива можно с помощью следующей команды:

[me@linuxbox ~]$ tar tf playground.tar

Для получения более подробного списка добавим параметр v (verbose — подроб- ности):

[me@linuxbox ~]$ tar tvf playground.tar

Теперь извлечем содержимое архива в другой каталог. Для этого создадим новый каталог с именем foo, перейдем в него и извлечем содержимое tar-архива:

[me@linuxbox ~]$ mkdir foo [me@linuxbox ~]$ cd foo [me@linuxbox foo]$ tar xf ../playground.tar [me@linuxbox foo]$ ls playground

Если внимательно исследовать содержимое ~/foo/playground, можно заметить, что в результате распаковывания архива мы получили точные копии оригинальных файлов. Однако следует помнить, что если вы не действуете от имени суперпользо- вателя, файлы и каталоги, извлеченные из архива, будут принадлежать пользовате- лю, выполнившему восстановление, а не первоначальному их владельцу.

Другой интересной особенностью tar является способ обработки путей в архивах. По умолчанию используются относительные пути, а не абсолютные. Для этого программа tar просто удаляет начальный слеш во всех путях. Чтобы показать это, создадим снова наш архив, но на этот раз укажем абсолютный путь к архивируе- мому каталогу:

[me@linuxbox foo]$ cd [me@linuxbox ~]$ tar cf playground2.tar ~/playground

Как вы помните, командная оболочка заменит ~/playground полным путем /home/ me/playground после нажатия клавиши ENTER, благодаря этому мы получим пол- ный путь для нашей демонстрации. Далее извлечем архив, так же как прежде, и посмотрим, что из этого получилось:

[me@linuxbox ~]$ cd foo [me@linuxbox foo]$ tar xf ../playground2.tar

Архивирование файлов

271

[me@linuxbox foo]$ ls home playground [me@linuxbox foo]$ ls home me [me@linuxbox foo]$ ls home/me playground

Как видите, здесь при извлечении архива каталог home/me/playground был вос- создан не в корневом, а в текущем рабочем каталоге ~/foo, как было бы в случае с абсолютными путями. Это может показаться странным, но такое решение имеет свои преимущества: оно позволяет извлекать архивы в любое другое место, а не только в исходное. Повторив это упражнение с параметром, управляющим выво- дом дополнительных сообщений (v), можно получить более понятную картину происходящего.

Рассмотрим пусть и гипотетический, но все же имеющий практическую ценность пример использования tar. Представим, что нужно скопировать домашний ка- талог со всем его содержимым в другую систему и у нас имеется жесткий диск, подключаемый к порту USB, который можно использовать для переноса файлов. В современных системах Linux такие диски «как по волшебству» автоматически монтируются в каталог /media. Допустим также, что подключаемый жесткий диск имеет том с именем BigDisk. Чтобы создать требуемый архив, выполним следую- щую команду:

[me@linuxbox ~]$ sudo tar cf /media/BigDisk/home.tar /home

После записи файла следует отмонтировать диск и подключить его ко второму компьютеру. И снова он автоматически монтируется в каталог /media/BigDisk. Чтобы извлечь архив, выполните следующие команды:

[me@linuxbox2 ~]$ cd / [me@linuxbox2 /]$ sudo tar xf /media/BigDisk/home.tar

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

При распаковке архива можно ограничить количество извлекаемых данных. На- пример, можно извлечь из архива единственный файл:

tar xf archive.tar путь_к_файлу

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

272

Глава 18. Архивация и резервное копирование

нельзя использовать групповые символы; но GNU-версия tar (именно эта вер- сия входит в состав большинства дистрибутивов Linux) поддерживает параметр –wildcards. В следующем примере используется файл playground2.tar, создан- ный выше:

[me@linuxbox ~]$ cd foo [me@linuxbox foo]$ tar xf ../playground2.tar –wildcards ‘home/me/playground/dir-*/ file-A’

Эта команда извлечет только файлы, соответствующие указанному пути с груп- повым символом dir-*.

Программа tar часто используется в сочетании с find. В следующем примере коман да find используется для поиска файлов, подлежащих включению в архив:

[me@linuxbox ~]$ find playground -name ‘file-A’ -exec tar rf playground.tar ‘{}’ ‘+’

Здесь команда find отыскивает в каталоге playground все файлы с именем file-A и затем с помощью операции -exec вызывает tar в режиме добавления в конец (r), чтобы добавить найденные файлы в архив playground.tar.

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

Программа tar способна также использовать стандартный ввод и стандартный вывод. Например:

[me@linuxbox foo]$ cd [me@linuxbox ~]$ find playground -name ‘file-A’ | tar cf - –files-from=- | gzip > playground.tgz

Здесь программа find создает список файлов и передает его по конвейеру програм- ме tar. Когда программе tar передается имя файла - (дефис), под ним подразу- мевается стандартный ввод или стандартный вывод, в зависимости от контекста. (Кстати, соглашение об использовании дефиса (-) для представления стандарт- ного ввода/вывода используется также многими другими программами.) Пара- метр –files-from (который можно заменить эквивалентным параметром -T) за- ставляет tar читать список путей из файла, а не из командной строки. Наконец, архив, произведенный программой tar, передается по конвейеру программе gzip, чтобы в результате получить сжатый архив playground.tgz. Расширение .tgz по

Архивирование файлов

273

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

В примере, приведенном выше, для сжатия архива использовалась внешняя про- грамма gzip, однако современные GNU-версии tar поддерживают возможность gzip- и bzip2-сжатия своими встроенными средствами, для чего служат параме- тры z и j соответственно. Взяв за основу предыдущий пример, его можно упро- стить, как показано ниже:

[me@linuxbox ~]$ find playground -name ‘file-A’ | tar czf playground.tgz -T -

Если, напротив, понадобится создать архив, сжатый в формате bzip2, это можно сделать так:

[me@linuxbox ~]$ find playground -name ‘file-A’ | tar cjf playground.tbz -T -

Произведя простую замену параметра сжатия z на j (и изменив расширение вы- ходного файла на .tbz, указывающее, что для сжатия использовался алгоритм bzip2), мы задействовали bzip2-сжатие.

Другой интересный пример использования поддержки стандартного ввода и выво- да командой tar связан с передачей файлов между системами по сети. Представьте, что имеется две машины, действующие под управлением Unix-подобных систем и имеющие программы tar и ssh. В этом случае можно организовать передачу ка- талога из удаленной системы (с именем remote-sys в этом примере) в локальную:

[me@linuxbox ~]$ mkdir remote-stuff [me@linuxbox ~]$ cd remote-stuff [me@linuxbox remote-stuff]$ ssh remote-sys ’tar cf - Documents’ | tar xf - me@remote-sys’s password: [me@linuxbox remote-stuff]$ ls Documents

Здесь мы скопировали каталог Documents из удаленной системы remote-sys в ка- талог с именем remote-stuff в локальной системе. Как это получилось? Во-первых, мы запустили программу tar в удаленной системе с помощью команды ssh. Как вы наверняка помните, ssh позволяет выполнить программу на удаленном ком- пьютере в сети и «увидеть» результат в локальной системе — стандартный вывод, полученный в удаленной системе, пересылается в локальную систему для обзора. Мы воспользовались этой особенностью и заставили tar создать архив (режим c) и вывести его не в файл, а в стандартный вывод (параметр f с дефисом в качестве аргумента), вследствие чего архив передается через шифрованный туннель, соз- данный программой ssh, локальной системе. В локальной системе мы вызвали tar с целью распаковать архив (режим x), полученный со стандартного ввода (все тот же параметр f с дефисом в качестве аргумента).

274

Глава 18. Архивация и резервное копирование

zip

Программа zip одновременно является и инструментом сжатия, и архиватором. Формат файлов, используемый программой, знаком пользователям Windows — программа читает и создает файлы с расширением .zip. Однако в Linux чаще дру- гих используется программа сжатия gzip, а второе место занимает bzip2. Поль- зователи Linux используют zip в основном для обмена файлами с системами Windows, а не как основной инструмент сжатия и архивирования.

В простейшем случае программа zip имеет следующий синтаксис:

zip параметры сжатый_файл файл…

Например, ниже показано, как создать zip-архив нашей песочницы:

[me@linuxbox ~]$ zip -r playground.zip playground

Без параметра -r (отвечает за рекурсивный обход каталогов) в архив будет вклю- чен только каталог playground (без своего содержимого). Расширение .zip добав- ляется к имени выходного файла автоматически, а мы включили его в пример для наглядности.

В процессе создания zip-архива программа zip обычно выводит последователь- ность сообщений, как показано ниже:

adding: playground/dir-020/file-Z (stored 0%) adding: playground/dir-020/file-Y (stored 0%) adding: playground/dir-020/file-X (stored 0%) adding: playground/dir-087/ (stored 0%) adding: playground/dir-087/file-S (stored 0%)

Эти сообщения показывают состояние каждого файла, добавленного в архив. zip добавляет файлы в архив, используя один из двух методов: либо «store» (простое сохранение) — без сжатия, как в примере, приведенном выше, либо «deflate» — со сжатием. Числовое значение, следующее за названием метода добавления, указы- вает достигнутую степень сжатия. Поскольку в нашей песочнице хранятся только пустые файлы, сжатие их содержимого не производится.

Извлечение содержимого из zip-архива выполняется просто — с помощью про- граммы unzip:

[me@linuxbox ~]$ cd foo [me@linuxbox foo]$ unzip ../playground.zip

Архивирование файлов

275

Одно важное отличие zip (от tar) состоит в том, что если указанный архив суще- ствует, он дополняется, а не замещается. То есть существующий архив сохраняет- ся, новые файлы добавляются в него, а существующие — замещаются.

Программа unzip позволяет выводить информацию о файлах и выборочно извле- кать их, достаточно только передать ей имя интересующего нас файла:

[me@linuxbox ~]$ unzip -l playground.zip playground/dir-087/file-Z Archive: ./playground.zip Length Date Time Name


    0  10-05-18 09:25  playground/dir-087/file-Z

    0                  1 file

[me@linuxbox ~]$ cd foo [me@linuxbox foo]$ unzip ../playground.zip playground/dir-087/file-Z Archive: ../playground.zip replace playground/dir-087/file-Z? [y]es, [n]o, [A]ll, [N]one, [r]ename: y extracting: playground/dir-087/file-Z

При наличии параметра -l программа unzip просто выведет информацию о содер- жимом архива, не извлекая файл. Если имя файла (или файлов) не указано, unzip выведет список всех файлов в архиве. Для получения более подробной информа- ции следует добавить параметр -v. Обратите внимание, что когда при извлечении из архива обнаруживается конфликт с существующим файлом, перед его заменой у пользователя запрашивается разрешение.

Подобно программе tar, zip может использовать стандартный ввод и вывод, хотя реализация этой возможности имеет меньшую практическую ценность. С помо- щью параметра -@ программе zip по конвейеру передается список имен файлов:

[me@linuxbox foo]$ cd [me@linuxbox ~]$ find playground -name “file-A” | zip -@ file-A.zip

Здесь команда find генерирует список файлов, соответствующих проверке -name “file-A”, и передает его по конвейеру команде zip, которая затем создает архив file-A.zip с выбранными файлами.

zip также поддерживает запись результатов своей работы в стандартный вывод, но эта особенность имеет ограниченное применение, потому что очень немногие программы способны работать с форматом zip. К сожалению, программа unzip не принимает входные данные со стандартного ввода. Это препятствует совместному использованию zip и unzip для копирования файлов по сети, как это возможно с программой tar.

276

Глава 18. Архивация и резервное копирование

zip, в свою очередь, способна принимать данные со стандартного ввода, поэтому ее можно использовать для сжатия вывода других программ:

[me@linuxbox ~]$ ls -l /etc/ | zip ls-etc.zip - adding: - (deflated 80%)

В этом примере вывод команды ls передается по конвейеру программе zip. Так же как tar, zip интерпретирует завершающий дефис как требование «использовать стандартный ввод вместо файла».

Программа unzip позволяет направить ее результаты в стандартный вывод, для чего следует передать параметр -p (pipe — конвейер):

[me@linuxbox ~]$ unzip -p ls-etc.zip | less

Мы затронули лишь самые основные возможности программ zip и unzip. Обе они имеют множество параметров, придающих им большую гибкость, хотя некоторые из них допустимы только для определенных платформ. Для обеих программ, zip и unzip, имеются подробные страницы справочного руководства (man) с множе- ством полезных примеров; однако главное назначение этих программ — обмен файлами с системами Windows, а не сжатие и архивирование файлов в Linux, где большей популярностью пользуются tar и gzip.

Синхронизация файлов и каталогов

В задачах резервного копирования систем широко используется стратегия син- хронизации одного или нескольких каталогов с другими каталогами, находящи- мися в локальной системе (обычно на некотором извлекаемом устройстве) или в удаленной. Можно, к примеру, создать локальную копию веб-сайта, находяще- гося в разработке, и синхронизировать ее время от времени с «рабочей» копией на удаленном веб-сервере.

В мире Unix-подобных систем для решения этой задачи широко используется ин- струмент rsync. Эта программа синхронизует локальные и удаленные каталоги, используя протокол rsync remote-update (протокол удаленного обновления rsync), который позволяет rsync быстро обнаруживать различия между двумя каталога- ми и копировать минимальный объем данных, необходимый для синхронизации. Это делает программу rsync быстрой и экономичной по сравнению с другими про- граммами копирования.

Программа rsync имеет следующий синтаксис:

rsync параметры источник приемник

Синхронизация файлов и каталогов

277

где роль источника и приемника могут играть:

 локальный файл или каталог;

 удаленный файл или каталог в форме [пользователь@]хост:путь;

 удаленный сервер rsync, определяемый идентификатором URI rsync://[поль-

зователь@]хост[:порт]/путь.

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

Давайте попробуем синхронизировать несколько локальных файлов. Сначала очистим наш каталог foo:

[me@linuxbox ~]$ rm -rf foo/*

Далее синхронизируем каталог playground с соответствующей копией в foo:

[me@linuxbox ~]$ rsync -av playground foo

Мы добавили два параметра: -a (для архивирования — обеспечивает рекурсивный обход и сохранение атрибутов файлов) и -v (подробный вывод), чтобы отразить каталог playground в каталог foo. В процессе выполнения команды можно просма- тривать список копируемых файлов и каталогов. В конце программа выведет ито- говое сообщение, как показано ниже, включающее общий объем скопированных данных:

sent 135759 bytes received 57870 bytes 387258.00 bytes/sec total size is 3230 speedup is 0.02

Если теперь запустить команду еще раз, результат будет другой:

[me@linuxbox ~]$ rsync -av playgound foo building file list … done

sent 22635 bytes received 20 bytes 45310.00 bytes/sec total size is 3230 speedup is 0.14

Обратите внимание на отсутствие списка файлов. Это объясняется тем, что про- грамма rsync не обнаружила различий между ~/playground и ~/foo/playground и поэтому ничего не скопировала. Если теперь изменить файл в playground и запу- стить rsync еще раз, она обнаружит изменившийся файл и скопирует только его.

[me@linuxbox ~]$ touch playground/dir-099/file-Z [me@linuxbox ~]$ rsync -av playground foo building file list … done

278

Глава 18. Архивация и резервное копирование

242 Глава 18. Архивация и резервное копирование playground/dir-099/file-Z sent 22685 bytes received 42 bytes 45454.00 bytes/sec total size is 3230 speedup is 0.14

Как видите, rsync обнаружила изменения и скопировала только изменившийся файл.

Есть одна маленькая, но важная деталь, связанная с определением источника в коман де rsync. Пусть у нас есть два каталога:

[me@linuxbox ~]$ ls source destination

Каталог source содержит один файл с именем file1, а каталог destination пуст. Если выполнить копирование source в destination так:

[me@linuxbox ~]$ rsync source destination

rsync скопирует каталог source в destination:

[me@linuxbox ~]$ ls destination source

Но если в имя каталога source добавить завершающий слеш /, rsync скопирует только содержимое каталога source, но не сам каталог:

[me@linuxbox ~]$ rsync source/ destination [me@linuxbox ~]$ ls destination file1

Это удобно, когда требуется скопировать только содержимое каталога, без созда- ния еще одного уровня в дереве каталогов приемника. Этот трюк действует подоб- но source/*, но, в отличие от последнего, обеспечит копирование всего содержимо- го каталога-источника, включая скрытые файлы.

В качестве практического примера представьте воображаемый внешний жесткий диск, использовавшийся выше с командой tar. Если после подключения такого диска к системе он снова будет смонтирован в каталог /media/BigDisk, мы смо- жем выполнить первое резервное копирование системы, cначала создав каталог /backup на внешнем устройстве, а затем вызвав rsync для копирования наиболее важных компонентов системы на внешнее устройство:

[me@linuxbox ~]$ mkdir /media/BigDisk/backup [me@linuxbox ~]$ sudo rsync -av –delete /etc /home /usr/local /media/BigDisk/ backup

Синхронизация файлов и каталогов

279

В этом примере мы скопировали каталоги /etc, /home и /usr/local из нашей си- стемы на воображаемый внешний диск. Мы добавили параметр –delete, чтобы удалить файлы, которые могут присутствовать на устройстве с резервной копией, но отсутствовать на устройстве-источнике (этот параметр не нужен при создании резервной копии в первый раз, но является полезным дополнением в последую- щих операциях копирования). Периодическое повторение процедуры подключе- ния внешнего диска и запуск этой команды rsync является неплохим (хотя и не идеальным) способом сохранения резервной копии небольшой системы. Конечно, здесь также могло бы пригодиться создание псевдонима. Определим псевдоним и добавим его в свой файл .bashrc, чтобы обеспечить возможность быстрого ре- зервного копирования:

alias backup=‘sudo rsync -av –delete /etc /home /usr/local /media/BigDisk/backup’

Теперь, чтобы выполнить всю работу, достаточно просто подключить внешний диск и ввести команду backup.

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

Одно из самых больших достоинств rsync — возможность копирования файлов по сети, об этом нам «говорит» буква r в названии rsync, что означает remote (уда- ленная). Удаленную синхронизацию можно выполнить одним из двух способов. Первый можно использовать с удаленными системами, где установлена rsync и программа удаленной командной оболочки, такая как ssh. Допустим, что в ло- кальной сети имеется другая система с огромным объемом дискового простран- ства, и мы хотели бы использовать эту систему для хранения резервной копии вместо внешнего диска. Если допустить, что в этой системе уже имеется каталог /backup, куда можно было бы сохранить наши файлы, мы могли бы выполнить резервное копирование так:

[me@linuxbox ~]$ sudo rsync -av –delete –rsh=ssh /etc /home /usr/local remotesys:/backup

Мы внесли два изменения в команду, чтобы обеспечить копирование по сети. Во- первых, добавили параметр –rsh=ssh, который требует от rsync использовать в качестве удаленной командной оболочки программу ssh. Благодаря этому для передачи данных из локальной системы в удаленную мы можем использовать шифрованный туннель SSH. Во-вторых, мы добавили имя удаленного узла (в дан- ном примере remote-sys) перед именем удаленного каталога.

Второй способ использования rsync для синхронизации файлов по сети заклю- чается в использовании сервера rysnc. rsync можно настроить на работу в режи- ме демона, принимающего входящие запросы на синхронизацию. Этот прием

280

Глава 18. Архивация и резервное копирование

часто используется для зеркалирования удаленных систем. Например, компания Red Hat Software поддерживает огромный репозиторий программных пакетов, разрабатываемых для ее дистрибутива Fedora. Для специалистов, занимающих- ся тестированием программного обеспечения, очень удобно иметь зеркало этой коллекции в ходе этапа тестирования, предшествующего этапу выпуска дистрибу- тива. Поскольку файлы в репозитории обновляются достаточно часто (порой по нескольку раз в день), неплохо было бы организовать периодическую синхрониза- цию локального зеркала вместо копирования всего объема репозитория. Один из таких репозиториев хранится в университете Georgia Tech; мы могли бы создать его зеркало с помощью локальной программы rsync и сервера rsync в Georgia Tech:

[me@linuxbox ~]$ mkdir fedora-devel [me@linuxbox ~]$ rsync -av –delete rsync://archive.linux.duke.edu/fedora/ linux/development/rawhide/Everything/x86_64/os/ fedora-devel

В этом примере мы использовали идентификатор URI удаленного сервера rsync, включающий протокол (rsync://), имя удаленного узла (archive.linux.duke.edu) и путь к репозиторию.

Заключение

Мы рассмотрели распространенные программы сжатия и архивирования, исполь- зуемые в Linux и в других Unix-подобных операционных системах. Для архивиро- вания файлов в Unix-подобных системах чаще других используется комбинация tar/gzip, тогда как команды zip/unzip в основном используются для организации обмена данными с системами Windows. Наконец, мы познакомились с програм- мой rsync (моя любимица) — очень эффективным инструментом синхронизации файлов и каталогов между системами.

19 Регулярные

выражения

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

Знакомясь со свойствами и особенностями командной строки, мы уже встреча- ли некоторые по-настоящему таинственные свойства и команды, такие как меха- низмы подстановки и экранирования, короткие комбинации клавиш и история команд, не говоря уже о редакторе vi. Регулярные выражения продолжают этот список и являются (пожалуй) самым загадочным из всех инструментов. Это не означает, что время на их изучение будет потрачено впустую. Как раз наоборот. Хорошее понимание регулярных выражений позволит вам творить настоящие чу- деса, хотя истинная их ценность поначалу может быть и не очевидна.

Что такое регулярные выражения?

Регулярные выражения — это всего лишь символическая форма записи, исполь- зуемая для идентификации шаблонов в тексте. Они, до определенной степени, напоминают групповые символы, используемые командной оболочкой для выбо- ра соответствующих файлов и путей, но в более широком масштабе. Регулярные выражения поддерживаются многими инструментами командной строки и боль- шинством языков программирования, чтобы упростить решение задач, связанных с обработкой текста. Однако проблема в том, что не все регулярные выражения одинаковы; разные инструменты и языки программирования используют соб- ственные «диалекты» регулярных выражений. Для целей нашего обсуждения мы

282

Глава 19. Регулярные выражения

ограничимся регулярными выражениями, как они определены в стандарте POSIX (и поддерживаются большинством инструментов командной строки) в противо- положность многим языкам программирования (особенно это относится к Perl), где используются более широкие и богатые формы записи.

grep

При работе с регулярными выражениями мы в основном будем использовать нашу старую добрую приятельницу — программу grep. Название grep в действи- тельности произошло от фразы «global regular expression print» (глобальный по- иск с помощью регулярного выражения и вывод), то есть, как видите, grep имеет некоторое отношение к регулярным выражениям. В сущности, grep просматрива- ет текстовые файлы в поисках совпадений с указанным регулярным выражением и выводит в стандартный вывод все строки с такими совпадениями.

До сих пор мы передавали программе grep фиксированные строки, например:

[me@linuxbox ~]$ ls /usr/bin | grep zip

Эта команда выведет список всех файлов из каталога /usr/bin, имена которых со- держат подстроку zip.

Программа grep имеет следующий синтаксис:

grep [параметры] регулярное_выражение [файл…]

В табл. 19.1 перечислены наиболее часто используемые параметры grep.

Таблица 19.1. Параметры команды grep

Параметр

Длинный параметр

Описание

-i

-v

-c

–ignore-case

–invert-match

–count

Игнорировать регистр символов. Требует не различать символы верхнего и нижнего реги- стров

Инвертировать критерий. Обычно grep вы- водит строки с совпадениями. Этот параметр заставляет grep выводить строки, не содержа- щие совпадений

Вывести число совпадений (или «несовпа- дений») в присутствии параметра -v вместо самих текстовых строк

grep

283

Параметр

Длинный параметр

Описание

-l

-L

-n

-h

–files-with-matches

Вместо строк с совпадениями выводить только имена файлов с найденными строками

–files-without-match

–line-number

–no-filename

Действует подобно параметру -l, но выво- дит только имена файлов, где не найдено ни одного совпадения

В начале каждой строки с совпадением выве- сти ее номер в файле

Подавить вывод имен файлов при поиске по нескольким файлам

Давайте создадим несколько текстовых файлов, чтобы наше исследование grep стало более предметным:

[me@linuxbox ~]$ ls /bin > dirlist-bin.txt [me@linuxbox ~]$ ls /usr/bin > dirlist-usr-bin.txt [me@linuxbox ~]$ ls /sbin > dirlist-sbin.txt [me@linuxbox ~]$ ls /usr/sbin > dirlist-usr-sbin.txt [me@linuxbox ~]$ ls dirlist*.txt dirlist-bin.txt dirlist-sbin.txt dirlist-usr-sbin.txt dirlist-usr-bin.txt

Ниже показано, как выполнить простой поиск в нашем списке файлов:

[me@linuxbox ~]$ grep bzip dirlist*.txt dirlist-bin.txt:bzip2 dirlist-bin.txt:bzip2recover

В этом примере grep просматривает все перечисленные файлы в поисках строки bzip и находит два совпадения, оба в файле dirlist-bin.txt. Если бы нам достаточно было получить только имена файлов с совпадениями, а не сами совпадения, мы могли бы добавить параметр -l:

[me@linuxbox ~]$ grep -l bzip dirlist*.txt dirlist-bin.txt

Напротив, получить список файлов, не содержащих совпадений, можно так:

[me@linuxbox ~]$ grep -L bzip dirlist*.txt dirlist-sbin.txt dirlist-usr-bin.txt dirlist-usr-sbin.txt

284

Глава 19. Регулярные выражения

Метасимволы и литералы

Несмотря на то что пока это не очевидно, во всех своих попытках поиска с помо- щью grep мы использовали регулярные выражения, хотя и очень простые. Регу- лярное выражение bzip, к примеру, означает, что ему соответствуют только строки в файлах, содержащие не менее четырех символов, и среди этих символов присут- ствуют символы b, z, i и p, следующие именно в таком порядке, и между ними от- сутствуют какие-либо другие символы. Символы в строке bzip — это литеральные символы, то есть они соответствуют сами себе. Помимо литералов регулярные вы- ражения могут содержать метасимволы, они используются для определения бо- лее сложных критериев сопоставления. К метасимволам регулярных выражений относятся следующие символы:

^ $ . [ ] { } - ? * + ( ) | \

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

ПРИМЕЧАНИЕ

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

Любой символ

Первый метасимвол, который мы рассмотрим, — это символ «точка», соответству- ющий любому символу. Если включить его в регулярное выражение, он будет со- ответствовать любому символу в данной позиции. Например:

[me@linuxbox ~]$ grep -h ‘.zip’ dirlist*.txt bunzip2 bzip2 bzip2recover gunzip gzip

Якоря

285

funzip gpg-zip preunzip prezip prezip-bin unzip unzipsfx

Здесь выполнен поиск в наших файлах совпадений с регулярным выражением .zip. В полученных результатах имеется пара важных моментов, которые необ- ходимо отметить. Обратите внимание, что программа zip не была найдена. Это объясняется включением в регулярное выражение метасимвола «точка», увели- чившим длину обязательного совпадения до четырех символов; так как в имени программы zip всего три символа, оно не было найдено. Кроме того, если бы в на- ших списках имелись имена файлов с расширением .zip, они также были бы най- дены, потому что символ «точка» в расширении файла интерпретировался бы как «любой символ».

Якоря

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

[me@linuxbox ~]$ grep -h ‘^zip’ dirlist*.txt zip zipcloak zipgrep zipinfo zipnote zipsplit [me@linuxbox ~]$ grep -h ‘zip$’ dirlist*.txt gunzip gzip funzip gpg-zip preunzip prezip unzip zip [me@linuxbox ~]$ grep -h ‘^zip$’ dirlist*.txt zip

286

Глава 19. Регулярные выражения

Здесь выполняется поиск в списке файлов строки zip, находящейся в начале стро- ки, в конце строки и занимающей всю строку, от начала до конца. Обратите вни- мание, что регулярное выражение ^$ (начало и конец без каких-либо символов между ними) будет соответствовать пустым строкам.

В ПОМОЩЬ ЛЮБИТЕЛЯМ КРОССВОРДОВ

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

Моя супруга обожает разгадывать кроссворды и иногда просит меня помочь с ответом на какой-нибудь вопрос. Например: «Слово из пяти букв, третья j, по- следняя r, которое означает…» Подобные вопросы навели меня на размышления.

Знаете ли вы, что в вашей системе Linux имеется словарь? Загляните в каталог /usr/ share/dict, и вы обнаружите там один или несколько словарей. Файлы словарей, находящиеся в каталоге, — это обычные длинные списки слов, по одному в стро- ке, упорядоченные по алфавиту. В моей системе файл words содержит больше 98 500 слов. Найти возможные ответы на вопрос в кроссворде можно с помощью следующей команды:

[me@linuxbox ~]$ grep -i ‘^..j.r$’ /usr/share/dict/words Major major

Это регулярное выражение помогает найти в файле словаря все слова длиной в пять букв, где третья буква — j и последняя — r.

Выражения в квадратных скобках и классы символов

В дополнение к возможности описать в регулярном выражении совпадение с лю- бым символом в заданной позиции, используя выражения в квадратных скобках, можно также описать совпадение с одним символом из определенного множе- ства. Выражение в квадратных скобках помогает определить множество символов (включая символы, которые иначе интерпретировались бы как метасимволы), ко- торые находятся в данной позиции. В следующем примере используется множе- ство из двух символов, благодаря которому обнаруживаются соответствия с по- следовательностями bzip и gzip:

[me@linuxbox ~]$ grep -h ‘[bg]zip’ dirlist*.txt bzip2

Выражения в квадратных скобках и классы символов

287

bzip2recover gzip

Множество может содержать любое число символов. Метасимволы, заключенные в квадратные скобки, теряют свое специальное значение. Лишь два метасимво- ла интерпретируются особым образом, но при этом они имеют иной смысл. Пер- вый — символ крышки (^), который используется для обозначения отрицания; второй — дефис (-), который используется для обозначения диапазона символов.

Отрицание

Если сразу после открывающей квадратной скобки стоит символ крышки (^), остальные символы множества интерпретируются как недопустимые в данной позиции. Проверим это, изменив предыдущий пример:

[me@linuxbox ~]$ grep -h ‘[^bg]zip’ dirlist*.txt bunzip2 gunzip funzip gpg-zip preunzip prezip prezip-bin unzip unzipsfx

Включив отрицание, мы получили список файлов, имена которых содержат по- следовательность zip, которой предшествует любой символ, кроме b или g. Обра- тите внимание, что файл zip не был найден. Символ отрицания не отменяет не- обходимости присутствия символа в заданной позиции, он лишь требует, чтобы символ в этой позиции не принадлежал указанному множеству.

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

Традиционные диапазоны символов

Если необходимо сконструировать регулярное выражение, которое находило бы в наших списках все файлы с именами, начинающимися с заглавной буквы, это можно выполнить следующим образом:

[me@linuxbox ~]$ grep -h ‘^[ABCDEFGHIJKLMNOPQRSTUVWXZY]’ dirlist*.txt

288

Глава 19. Регулярные выражения

Достаточно просто поместить 26 букв в верхнем регистре в выражение в квадрат- ных скобках. Но необходимость ввода всех этих символов вызывает некоторое беспокойство, поэтому предусмотрен другой способ:

[me@linuxbox ~]$ grep -h ‘^[A-Z]’ dirlist*.txt MAKEDEV ControlPanel GET HEAD POST X X11 Xorg MAKEFLOPPIES NetworkManager NetworkManagerDispatcher

Мы сократили множество с 26 буквами до 3-символьного диапазона. Так можно выразить любой диапазон символов и даже несколько диапазонов, например, для поиска имен файлов, начинающихся с буквы или цифры:

[me@linuxbox ~]$ grep -h ‘^[A-Za-z0-9]’ dirlist*.txt

Как следует из примеров, символ «дефис» получает в диапазонах специальное значение, поэтому возникает вопрос: как включить дефис в выражение в квадрат- ных скобках, чтобы он интерпретировался как обычный символ? Для этого доста- точно поставить его в начало выражения. Например:

[me@linuxbox ~]$ grep -h ‘[A-Z]’ dirlist*.txt

Эта команда найдет все имена файлов, содержащие буквы верхнего регистра. С другой стороны, следующее выражение:

[me@linuxbox ~]$ grep -h ‘[-AZ]’ dirlist*.txt

найдет все имена файлов, содержащие дефис, букву A или букву Z.

Классы символов POSIX

Традиционные диапазоны символов — простой и эффективный способ опреде- ления наборов символов. К сожалению, они могут использоваться не со всеми программами. Мы не испытывали никаких проблем с диапазонами, используя программу grep, но могли бы столкнуться с ними при использовании других про- грамм.

Классы символов POSIX

289

Вернемся к главе 4, где демонстрировалось использование групповых символов для подстановки имен файлов. Там говорилось, что можно использовать диапазо- ны символов почти так же, как они используются в регулярных выражениях, но есть одна проблема:

[me@linuxbox ~]$ ls /usr/sbin/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]* /usr/sbin/MAKEFLOPPIES /usr/sbin/NetworkManagerDispatcher /usr/sbin/NetworkManager

(В разных дистрибутивах будут получены разные списки файлов, а в некоторых даже пустой список. Эти результаты получены в Ubuntu.) Эта команда вернула ожидаемый результат — список имен файлов, начинающихся с заглавной буквы. Но следующая команда даст совершенно другой результат (здесь приведена толь- ко часть результатов):

[me@linuxbox ~]$ ls /usr/sbin/[A-Z]* /usr/sbin/biosdecode /usr/sbin/chat /usr/sbin/chgpasswd /usr/sbin/chpasswd /usr/sbin/chroot /usr/sbin/cleanup-info /usr/sbin/complain /usr/sbin/console-kit-daemon

В чем же причина? Для этой длинной истории имеется короткая версия.

Во времена, когда операционная система Unix только появилась на свет, был известен только один набор символов — ASCII, и этот факт нашел свое отра- жение в данной особенности. В ASCII первые 32 символа (с номерами 0–31) — это управляющие символы (такие, как табуляция, забой и возврат каретки). Следующие 32 (32–63) представляют печатаемые символы, включая большин- ство знаков пунктуации и цифры с нуля до девяти. Следующие 32 (с номера- ми 64–95) представляют буквы верхнего регистра и несколько знаков пунктуа- ции. Последние 31 (с номерами 96–127) представляют буквы нижнего регистра и еще несколько знаков пунктуации. Опираясь на эту классификацию, систе- мы, использующие набор ASCII, придерживались следующего порядка сопо- ставления:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Этот порядок отличается от лексикографического, который выглядит так:

aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ

290

Глава 19. Регулярные выражения

С ростом популярности Unix за пределами США возникла необходимость в под- держке символов, не входящих в алфавит американского английского. Таблица ASCII была расширена до использования 8-битных символов, и в нее добави- лись символы с номерами 128–255, используемые во многих других языках. Для поддержки этой возможности в стандарт POSIX было введено понятие региона (locale), определяющее выбор набора символов для конкретного географиче- ского региона. Узнать, какой язык настроен в вашей системе, можно с помощью команды:

[me@linuxbox ~]$ echo $LANG en_US.UTF-8

При проверке этой настройки POSIX-совместимые приложения используют лек- сикографический порядок, а не порядок следования символов в наборе ASCII. Это объясняет поведение команд, рассмотренное выше. Когда диапазон символов [A-Z] интерпретируется в лексикографическом порядке, он включает все алфа- витные символы, кроме символа a в нижнем регистре, — именно это объясняет полученный результат.

Для частичного решения этой проблемы стандарт POSIX предусматривает не- сколько классов символов, описывающих диапазоны символов. Они перечислены в табл. 19.2.

Таблица 19.2. Классы символов POSIX

Класс символов

[:alnum:]

[:word:]

Описание

Алфавитно-цифровые символы; эквивалент диапазона [A-Za-z0-9] в ASCII

То же, что и [:alnum:], с дополнительным символом подчеркива- ния (_)

[:alpha:]

Алфавитные символы; эквивалент диапазона [A-Za-z] в ASCII

[:blank:]

Включает символы пробела и табуляции

[:cntrl:]

Управляющие символы ASCII; включает символы ASCII с кодами от 0 до 31 и 127

[:digit:]

Цифры от 0 до 9

[:graph:]

Отображаемые символы; включает символы ASCII с кодами от 33 до 126

[:lower:]

Символы нижнего регистра

Простые и расширенные регулярные выражения POSIX

291

Класс символов

[:punct:]

Описание

Знаки пунктуации; эквивалент класса [-!"#$%&’()*+,./:;?@ [\]_`{|}~] в ASCII

[:print:]

Печатаемые символы; Все символы из класса [:graph:] и пробел

[:space:]

Пробельные символы, включая пробел, табуляцию, возврат каретки, перевод строки, вертикальную табуляцию и перевод формата; эквива- лент класса [ \t\r\n\v\f] в ASCII

[:upper:]

Символы верхнего регистра

[:xdigit:]

Символы, используемые для представления шестнадцатеричных цифр; эквивалент класса [0-9A-Fa-f] в ASCII

Но даже наличие классов символов не дает удобного способа выражения частич- ных диапазонов, таких как [A-M].

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

[me@linuxbox ~]$ ls /usr/sbin/[[:upper:]]* /usr/sbin/MAKEFLOPPIES /usr/sbin/NetworkManagerDispatcher /usr/sbin/NetworkManager

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

Простые и расширенные регулярные выражения POSIX

Как раз когда, казалось бы, проблема путаницы с диалектами регулярных вы- ражений решена, обнаруживается, что стандарт POSIX также делит реализа- ции регулярных выражений на два вида: простые регулярные выражения (Basic Regular Expressions, BRE) и расширенные регулярные выражения (Extended Regular Expressions, ERE). Особенности, рассматривавшиеся до сих пор, поддерживаются всеми POSIX-совместимыми приложениями и приложениями, реализующими BRE. Программа grep — одна из них.

292

Глава 19. Регулярные выражения

ВОЗВРАТ К ТРАДИЦИОННОМУ ПОРЯДКУ СОРТИРОВКИ

Есть возможность вернуть систему к традиционному (ASCII) порядку сортировки, изменив значение переменной окружения LANG. Как было показано в предыдущем разделе, переменная LANG хранит название языка и набора символов, заданных в региональных настройках. Значение этой переменной первоначально определя- ется в момент, когда выбирается язык установки дистрибутива Linux.

Увидеть региональные настройки можно, выполнив команду locale:

[me@linuxbox ~]$ locale LANG=en_US.UTF-8 LC_CTYPE=“en_US.UTF-8” LC_NUMERIC=“en_US.UTF-8” LC_TIME=“en_US.UTF-8” LC_COLLATE=“en_US.UTF-8” LC_MONETARY=“en_US.UTF-8” LC_MESSAGES=“en_US.UTF-8” LC_PAPER=“en_US.UTF-8” LC_NAME=“en_US.UTF-8” LC_ADDRESS=“en_US.UTF-8” LC_TELEPHONE=“en_US.UTF-8” LC_MEASUREMENT=“en_US.UTF-8” LC_IDENTIFICATION=“en_US.UTF-8” LC_ALL=

Чтобы установить региональные настройки, обеспечивающие традиционное по- ведение системы Unix, присвойте переменной LANG значение POSIX:

[me@linuxbox ~]$ export LANG=POSIX

Имейте в виду, что в результате наших действий система будет использовать набор символов американского английского (точнее, ASCII), поэтому подумайте, действительно ли это то, что вам нужно.

Эти изменения можно сделать постоянными, добавив следующую строку в файл .bashrc:

export LANG=POSIX

Чем различаются BRE и ERE? Различия касаются наборов метасимволов. В диа- лекте BRE распознаются следующие метасимволы:

^ $ . [ ] *

Все остальные считаются литералами. В ERE во множество метасимволов (с соот- ветствующими им функциями) добавляются:

( ) { } ? + |

Чередование

293

POSIX

На протяжении 1980-х система Unix обрела популярность как коммерческая опе- рационная система, но до 1988-го в мире Unix царила полная анархия. Многие производители компьютеров лицензировали исходный код Unix у ее создателя — компании AT&T и поставляли разные версии операционной системы вместе со своими машинами. Однако в стремлении к дифференциации продуктов каждый про- изводитель добавлял свои, патентованные изменения и расширения. В результате значительно ухудшилась совместимость программного обеспечения. Как обычно, производители пытались играть в игру, победой в которой было «замыкание» клиентов на конкретном производителе. Этот период истории Unix ныне известен как Балканизация (Balkanization).

В середине 1980-х институт инженеров электроники и электротехники (Institute of Electrical and Electronics Engineers, IEEE) начал разработку единого пакета стандартов, которые должны были определить особенности работы системы Unix (и Unix-подобных). Эти стандарты, формально известные как IEEE 1003, определя- ют прикладные программные интерфейсы (Application Programming Interface, API), командную оболочку и утилиты, которые должны присутствовать в стандартной Unix-подобной системе. Название POSIX, сокращенное от «Portable Operating System Interface» (интерфейс переносимой операционной системы, где буква X добавлена для лучшего звучания), было предложено Ричардом Столлманом (да, тем самым Ричардом Столлманом) и принято IEEE.

Однако (что самое интересное) символы ( ) { } интерпретируются в BRE как ме- тасимволы, если они экранированы символом «обратный слеш», тогда как в ERE присутствие обратного слеша перед этими же метасимволами превращает их в ли- тералы.

Поскольку далее в этой главе мы рассмотрим особенности, являющиеся частью ERE, необходимо использовать другую версию grep. Традиционно диалект ERE поддерживался программой egrep, но GNU-версия grep также поддерживает рас- ширенные регулярные выражения при вызове ее с параметром -E.

Чередование

Первой особенностью расширенных регулярных выражений, которую мы об- судим, будет чередование (alternation, или выражение выбора) — оно позволяет выбирать совпадение с одним из нескольких выражений. Так же как выражения в квадратных скобках позволяют одному символу соответствовать множеству

294

Глава 19. Регулярные выражения

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

Для демонстрации воспользуемся комбинацией команд grep и echo. Сначала по- пробуем выполнить простое сопоставление строк:

[me@linuxbox ~]$ echo “AAA” | grep AAA AAA [me@linuxbox ~]$ echo “BBB” | grep AAA [me@linuxbox ~]$

Достаточно простой пример, в котором мы передаем по конвейеру вывод команды echo на ввод grep и видим результат. Если обнаруживается совпадение, мы видим вывод; если совпадение отсутствует, ничего не выводится.

Теперь добавим чередование, обозначаемое метасимволом вертикальной черты:

[me@linuxbox ~]$ echo “AAA” | grep -E ‘AAA|BBB’ AAA [me@linuxbox ~]$ echo “BBB” | grep -E ‘AAA|BBB’ BBB [me@linuxbox ~]$ echo “CCC” | grep -E ‘AAA|BBB’ [me@linuxbox ~]$

Здесь мы видим регулярное выражение ‘AAA|BBB’, которое означает «совпадение со строкой AAA или со строкой BBB». Так как это расширенная особенность, мы до- бавили в команду grep параметр -E (вместо этого можно было бы использовать программу egrep) и заключили регулярное выражение в кавычки, чтобы предот- вратить интерпретацию командной оболочкой символа вертикальной черты как оператора конвейера. В чередовании может быть более двух вариантов:

[me@linuxbox ~]$ echo “AAA” | grep -E ‘AAA|BBB|CCC’ AAA

Для объединения с другими элементами регулярного выражения чередование можно заключать в круглые скобки ():

[me@linuxbox ~]$ grep -Eh ‘^(bz|gz|zip)’ dirlist*.txt

Этому выражению будут соответствовать имена файлов из наших списков, начи- нающиеся с bz, gz или zip. Если отбросить круглые скобки, смысл регулярного вы- ражения изменится, и ему будут соответствовать имена, начинающиеся с bz или содержащие gz или zip:

[me@linuxbox ~]$ grep -Eh ‘^bz|gz|zip’ dirlist*.txt

Квантификаторы

295

Квантификаторы

Расширенные регулярные выражения поддерживают несколько способов опреде- ления числа совпадений с элементом.

? — совпадение с элементом ноль или один раз

Этот квантификатор фактически означает: «совпадение с предыдущим элементом не обязательно». Представьте, что нужно проверить допустимость номера телефо- на, и предполагается, что номер допустим, если представлен в одной из двух форм:

 (nnn) nnn-nnnn;

 nnn nnn-nnnn.

Для проверки можно было бы использовать следующее регулярное выражение:

^(?[0-9][0-9][0-9])? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$

В этом выражении за круглыми скобками следуют знаки вопроса, указывающие, что скобки могут либо отсутствовать, либо присутствовать один раз. И снова, по- скольку круглые скобки считаются метасимволами (в ERE), мы экранировали их обратными слешами, чтобы они интерпретировались как литералы.

Попробуем применить это выражение:

[me@linuxbox ~]$ echo “(555) 123-4567” | grep -E ‘^(?[0-9][0-9][0-9])? [0-9] [0-9][0-9]$’ (555) 123-4567 [me@linuxbox ~]$ echo “555 123-4567” | grep -E ‘^(?[0-9][0-9][0-9])? [0-9][0-9] [0-9]-[0-9][0-9][0-9][0-9]$’ 555 123-4567 [me@linuxbox ~]$ echo “AAA 123-4567” | grep -E ‘^(?[0-9][0-9][0-9])? [0-9][0-9] [0-9]-[0-9][0-9][0-9][0-9]$’ [me@linuxbox ~]$

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

  • — совпадение с элементом ноль или более раз

Подобно метасимволу ?, звездочка (*) обозначает необязательный элемент; одна- ко, в отличие от знака вопроса (?), этот элемент может встречаться любое число раз, а не только единожды. Представьте, что нам нужно проверить, является ли строка предложением. Чтобы удовлетворять нашим требованиям, строка должна

296

Глава 19. Регулярные выражения

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

[[:upper:]][[:upper:][:lower:] ]*.

Выражение состоит из трех элементов: выражение в квадратных скобках с клас- сом символов [:upper:], выражение в квадратных скобках с двумя классами символов, [:upper:] и [:lower:], и пробелом, и точка, экранированная обратным слешем. Второй элемент сопровождается метасимволом *, поэтому в нашем пред- ложении ему может соответствовать любое число букв верхнего и нижнего реги- стра и пробелов, следующих за первой буквой верхнего регистра:

[me@linuxbox ~]$ echo “This works.” | grep -E ‘[[:upper:]][[:upper:][:lower:] ].’ This works. [me@linuxbox ~]$ echo “This Works.” | grep -E ‘[[:upper:]][[:upper:][:lower:] ].’ This Works. [me@linuxbox ~]$ echo “this does not” | grep -E ‘[[:upper:]][[:upper:][:lower:] ]*.’ [me@linuxbox ~]$

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

  • — совпадение с элементом один или более раз

Метасимвол + действует почти так же, как *, но требует совпадения с предыдущим элементом не менее одного раза. Следующему регулярному выражению будут со- ответствовать только строки, состоящие из групп, насчитывающих один или не- сколько алфавитных символов и разделенных одиночными пробелами:

^([[:alpha:]]+ ?)+$

Опробуем его:

[me@linuxbox ~]$ echo “This that” | grep -E ‘^([[:alpha:]]+ ?)+$’ This that [me@linuxbox ~]$ echo “a b c” | grep -E ‘^([[:alpha:]]+ ?)+$’ a b c [me@linuxbox ~]$ echo “a b 9” | grep -E ‘^([[:alpha:]]+ ?)+$’ [me@linuxbox ~]$ echo “abc d” | grep -E ‘^([[:alpha:]]+ ?)+$’ [me@linuxbox ~]$

Квантификаторы

297

Как видите, этому выражению не соответствует строка “a b 9”, потому что она содержит неалфавитный символ; точно так же ему не соответствует строка “abc d”, потому что между символами c и d в ней присутствует больше одного пробела.

{ } — совпадение с элементом определенное число раз

Метасимволы { и } используются, чтобы выразить минимальное и максимальное число обязательных совпадений. Эти числа можно представить четырьмя воз- можными способами, как показано в табл. 19.3.

Таблица 19.3. Определение числа совпадений

Спецификатор

Значение

{n}

{n,m}

{n,}

{,m}

Предыдущий элемент соответствует, если встречается точно n раз

Предыдущий элемент соответствует, если встречается не менее n и не более m раз

Предыдущий элемент соответствует, если встречается n или более раз

Предыдущий элемент соответствует, если встречается не более m раз

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

^(?[0-9][0-9][0-9])? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$

до

^(?[0-9]{3})? [0-9]{3}-[0-9]{4}$

Опробуем его:

[me@linuxbox ~]$ echo “(555) 123-4567” | grep -E ‘^(?[0-9]{3})? [0-9]{3}- [0-9]{4}$’ (555) 123-4567 [me@linuxbox ~]$ echo “555 123-4567” | grep -E ‘^(?[0-9]{3})? [0-9]{3}-[0-9]{4}$’ 555 123-4567 [me@linuxbox ~]$ echo “5555 123-4567” | grep -E ‘^(?[0-9]{3})? [0-9]{3}- [0-9]{4}$’ [me@linuxbox ~]$

298

Глава 19. Регулярные выражения

Как видите, измененная версия регулярного выражения успешно справляется с проверкой номеров, с круглыми скобками и без них, и отвергает неправильно оформленные номера.

Практические примеры применения регулярных выражений

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

Проверка списка телефонов с помощью grep

В предыдущем примере мы брали телефонные номера по одному и проверяли правильность их оформления. На практике же часто приходится проверять списки телефонов, поэтому давайте создадим такой список. Для этого восполь- зуемся волшебной магией командной строки. Магией, потому что мы еще не знакомы с большинством команд, привлеченных для решения поставленной задачи, но не волнуйтесь — мы рассмотрим их в последующих главах. Вот это волшебство:

[me@linuxbox ~]$ for i in {1..10}; do echo “(${RANDOM:0:3}) ${RANDOM:0:3}- ${RANDOM:0:4}” » phonelist.txt; done

Эта команда создаст файл с именем phonelist.txt, содержащий 10 телефонных но- меров. Если повторить команду, она добавит в список еще 10 номеров. Также мож- но изменить число 10 ближе к началу команды, чтобы создать больше или меньше номеров. Однако если заглянуть в файл, можно заметить проблему:

[me@linuxbox ~]$ cat phonelist.txt (232) 298-2265 (624) 381-1078 (540) 126-1980 (874) 163-2885 (286) 254-2860 (292) 108-518 (129) 44-1379 (458) 273-1642 (686) 299-8268 (198) 307-2440

Некоторые номера оформлены неправильно, что очень хорошо для целей демон- страции их проверки с помощью grep.

Практические примеры применения регулярных выражений

299

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

[me@linuxbox ~]$ grep -Ev ‘^([0-9]{3}) [0-9]{3}-[0-9]{4}$’ phonelist.txt (292) 108-518 (129) 44-1379 [me@linuxbox ~]$

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

Поиск необычных имен файлов с помощью find

Команда find поддерживает проверку, основанную на регулярном выражении. Су- ществует одно важное обстоятельство, которое следует помнить, используя регу- лярные выражения в командах find и grep. Если grep выводит строку, содержащую совпадение с регулярным выражением, то find требует точного совпадения пути с регулярным выражением. В следующем примере команда find использует регу- лярное выражение для поиска путей к файлам, содержащим любые символы, не входящие в следующее множество:

[-_./0-9a-zA-Z]

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

[me@linuxbox ~]$ find . -regex ‘.[^-_./0-9a-zA-Z].

Из-за требования точного совпадения всего пути мы добавили элемент .* с обоих концов выражения, замещающий любое количество любых символов (в том чис- ле и отсутствие символов). В середине выражения находится инвертированное выражение в квадратных скобках, содержащее множество символов, допустимых в именах файлов и каталогов.

Поиск файлов с помощью locate

Программа locate поддерживает простые (параметр –regexp) и расширенные (параметр –regex) регулярные выражения. Благодаря этому можно выполнять те же операции, что производились выше с файлами dirlist:

300

Глава 19. Регулярные выражения

[me@linuxbox ~]$ locate –regex ‚bin/(bz|gz|zip)’ /bin/bzcat /bin/bzcmp /bin/bzdiff /bin/bzegrep /bin/bzexe /bin/bzfgrep /bin/bzgrep /bin/bzip2 /bin/bzip2recover /bin/bzless /bin/bzmore /bin/gzexe /bin/gzip /usr/bin/zip /usr/bin/zipcloak /usr/bin/zipgrep /usr/bin/zipinfo /usr/bin/zipnote /usr/bin/zipsplit

Использовав чередование, мы нашли пути, содержащие bin/bz, bin/gz или /bin/zip.

Поиск текста в less и vim

less и vim поддерживают одинаковые способы поиска в тексте. Чтобы выполнить поиск, нажмите клавишу / и введите регулярное выражение. Воспользуемся про- граммой less, чтобы просмотреть содержимое файла phonelist.txt:

[me@linuxbox ~]$ less phonelist.txt

Затем выполним поиск с применением выражения для проверки:

(232) 298-2265 (624) 381-1078 (540) 126-1980 (874) 163-2885 (286) 254-2860 (292) 108-518 (129) 44-1379 (458) 273-1642 (686) 299-8268 (198) 307-2440 ~ ~ ~ /^([0-9]{3}) [0-9]{3}-[0-9]{4}$

less выделит строки с совпадениями, что позволит сразу увидеть недопустимые номера:

Заключение

301

(232) 298-2265 (624) 381-1078 (540) 126-1980 (874) 163-2885 (286) 254-2860 (292) 108-518 (129) 44-1379 (458) 273-1642 (686) 299-8268 (198) 307-2440 ~ ~ ~ (END)

Редактор vim поддерживает только простые регулярные выражения, поэтому вы- ражение для поиска должно выглядеть следующим образом:

/([0-9]{3}) [0-9]{3}-[0-9]{4}

Как видите, выражение практически то же самое; различия обусловлены лишь тем, что многие символы, которые в расширенной версии выражений считаются метасимволами, в простой версии интерпретируются как литералы. Они действу- ют как метасимволы, только если экранировать их символом «обратный слеш». В зависимости от конкретных настроек vim совпадения могут быть выделены или нет. Если совпадения не выделяются, попробуйте в командном режиме выполнить команду :hlsearch, чтобы активировать выделение результатов поиска.

ПРИМЕЧАНИЕ

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

Заключение

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

302

Глава 19. Регулярные выражения

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

[me@linuxbox ~]$ cd /usr/share/man/man1 [me@linuxbox man1]$ zgrep -El ‘regex|regular expression’ *.gz

Программа zgrep реализует интерфейс к grep, позволяя читать сжатые файлы. В данном примере выполняется поиск в сжатых файлах первого раздела справоч- ного руководства. Результатом этой команды будет список файлов, содержащих строку regex или regular expression. Как видите, регулярные выражения поддержи- ваются множеством программ.

Простые регулярные выражения обладают одной интересной особенностью, ко- торую мы пропустили, — обратными ссылками (back references). Они будут рас- сматриваться в следующей главе.

20 Обработка текста

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

В этой главе мы повторно рассмотрим уже знакомые программы и познакомимся с новыми:

cat — объединяет файлы и выводит их в стандартный вывод;

sort — сортирует строки из текстовых файлов;

uniq — сообщает о повторяющихся строках или удаляет их;

cut — удаляет фрагменты из каждой строки в файлах;

paste — выполняет слияние строк из файлов;

join — объединяет строки из двух файлов по общему полю;

comm — выполняет построчное сравнение двух сортированных файлов;

diff — выполняет построчное сравнение файлов;

patch — применяет diff-файл (файл с результатами сравнения командой diff) к оригиналу;

tr — перекодирует или удаляет символы;

sed — потоковый редактор для фильтрации и преобразования текста;

aspell — интерактивная программа проверки орфографии.

304

Глава 20. Обработка текста

Области применения текста

К настоящему моменту мы познакомились с парой текстовых редакторов (nano и vim), рассмотрели несколько конфигурационных файлов и увидели вывод не- скольких десятков команд, и все это в текстовом виде. А для чего еще использует- ся текст? Как оказывается, много для чего.

Документы

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

Веб-страницы

Самым популярным в мире форматом электронных документов является, по- жалуй, формат веб-страниц. Веб-страницы — это текстовые документы с размет- кой HTML (Hypertext Markup Language — язык разметки гипертекста) или XML (Extensible Markup Language — расширяемый язык разметки), описывающей ви- зуальный формат документа.

Электронная почта

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

Вывод на принтер

В Unix-подобных системах данные выводятся на печать в простом текстовом виде, или, если страница содержит графику, она преобразуется в описание страницы

cat

305

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

Исходный код программ

Многие программы командной строки, имеющиеся в Unix-подобных системах, были созданы для поддержки системного администрирования и разработки про- граммного обеспечения, и программы обработки текста не исключение. Многие из них предназначались для решения задач, связанных с разработкой программного обеспечения. Важность обработки текста для программистов объясняется тем, что любое программное обеспечение начинает свое существование как текст. Исход- ный код, часть программы, которую пишет программист, всегда имеет текстовый формат.

А вот и наши старые знакомые!

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

cat

Программа cat содержит множество интересных параметров. Многие из них ис- пользуются для улучшенного отображения текстового содержимого. Примером может служить параметр -A, используемый для отображения непечатаемых сим- волов в тексте. Иногда необходимо знать, имеются ли управляющие символы в просматриваемом тексте. Наиболее распространенными из них являются сим- волы табуляции (в противоположность пробелам) и символы возврата каретки, часто представляющие концы строк в текстовых файлах, оформленных в стиле MS-DOS. Другим распространенным вариантом является файл, содержащий строки с завершающими пробелами.

Давайте создадим файл для экспериментов, используя cat как примитивный тек- стовый процессор. Для этого введем команду cat (указав файл для перенаправ- ления вывода), а следом — наш текст, завершив строки нажатием клавиши ENTER и закончив все комбинацией CTRL+D — она сообщит программе cat, что достигнут конец файла. В этом примере мы ввели символ табуляции и добавили в конец строки несколько пробелов:

306

Глава 20. Обработка текста

[me@linuxbox ~]$ cat > foo.txt The quick brown fox jumped over the lazy dog1. [me@linuxbox ~]$

Далее, вызовем cat с параметром -A, чтобы показать текст:

[me@linuxbox ~]$ cat -A foo.txt ^IThe quick brown fox jumped over the lazy dog. $ [me@linuxbox ~]$

Как видите, символ табуляции в тексте представлен парой символов ^I. Эта обыч- ная форма записи означает «CTRL+I», то есть, как оказывается, — символ табуля- ции. Здесь также видно, что символ $ отмечает истинный конец строки, помогая увидеть дополнительные пробелы в конце строки.

ТЕКСТ В MS-DOS И UNIX

Одна из причин, почему может появиться желание использовать cat для ото- бражения непечатаемых символов в тексте, — необходимость определить при- сутствие символов возврата каретки. Откуда берутся скрытые возвраты каретки? Из DOS и Windows! В Unix и DOS концы строк в текстовых файлах оформляются по-разному. В Unix строки заканчиваются символом перевода строки (ASCII 10), тогда как в MS-DOS и ее наследницах для этой цели используется последователь- ность из возврата каретки (ASCII 13) и перевода строки.

Существует несколько способов преобразовать файлы из формата DOS в формат Unix. Во многих системах Linux имеются программы unix2dos и dos2unix для пре- образования текстовых файлов в формат DOS и обратно. Однако если в вашей системе нет программы dos2unix, не волнуйтесь. Процесс преобразования текста из формата DOS в формат Unix очень прост — достаточно лишь удалить ненужные возвраты каретки. Это можно сделать с помощью пары программ, с которыми мы познакомимся позже в этой главе.

Программа cat имеет также параметры, используемые для изменения текста. Наиболее известными являются -n, добавляющий номера строк, и -s, подавля- ющий вывод множества пустых строк, идущих подряд. Давайте продемонстри- руем их:

1 Английский аналог фразы: «Съешь же ещё этих мягких французских булок, да выпей чаю», содержащей все буквы алфавита. В этой главе, чтобы избежать полного переписы- вания всех авторских примеров, мы будем работать с ней. — Примеч. ред.

cat

307

[me@linuxbox ~]$ cat > foo.txt The quick brown fox

jumped over the lazy dog. [me@linuxbox ~]$ cat -ns foo.txt 1 The quick brown fox 2 3 jumped over the lazy dog. [me@linuxbox ~]$

В этом примере мы создали новую версию тестового файла foo.txt, содержащую две строки, разделенные двумя пустыми строками. После обработки текста ко- мандой cat с параметрами -ns одна пустая строка была удалена, а остальные стро- ки пронумерованы. Хотя это не самая сложная обработка текста, все же это об- работка.

sort

Программа sort сортирует содержимое стандартного ввода или одного или не- скольких файлов, указанных в командной строке, и записывает результаты в стан- дартный вывод. Применив тот же прием, который мы использовали совместно с командой cat, можно продемонстрировать обработку стандартного ввода:

[me@linuxbox ~]$ sort > foo.txt c b a [me@linuxbox ~]$ cat foo.txt a b c

После запуска команды мы ввели буквы c, b и a, а затем признак конца файла с по- мощью комбинации CTRL+D. Затем просмотрели получившийся файл и увидели, что строки в нем отсортированы.

Поскольку sort может принимать несколько файлов в аргументах командной строки, существует возможность объединить множество файлов в один отсорти- рованный файл. Например, если у вас имеется три файла и вам нужно объединить их в один отсортированный файл, это можно выполнить следующим образом:

sort file1.txt file2.txt file3.txt > final_sorted_list.txt

Программа sort имеет несколько интересных параметров. Их неполный список приводится в табл. 20.1.

308

Глава 20. Обработка текста

Таблица 20.1. Часто используемые параметры команды sort

Параметр

Длинный параметр

Описание

-b

–ignore-leading-blanks

-f

-n

-r

-k

-m

-o

-t

–ignore-case

–numeric-sort

–reverse

–key=поле1[,поле2]

–merge

–output=файл

–field-separator=символ

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

Выполнять сортировку без учета регистра символов

Выполнять сортировку, опираясь на числовые значения строк. Этот параметр позволяет осуществлять сортировку в соот- ветствии с числовыми значениями, а не по алфавиту

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

Сортировать по ключевым полям, начиная с поля1 и заканчивая полем2, а не по всей строке (поясняется ниже)

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

Записать результат сортировки не в стан- дартный вывод, а в указанный файл

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

Имена большинства параметров из представленных выше говорят сами за себя, однако некоторые требуют дополнительных пояснений. Прежде всего рассмо- трим параметр -n, используемый для сортировки по числовым значениям. Этот параметр позволяет сортировать строки по их числовым значениям. Продемон- стрировать действие этого параметра можно на примере сортировки результатов команды du, чтобы определить каталог, занимающий больший объем дискового пространства. Обычно команда du выводит результаты, отсортированные по име- нам каталогов:

cat

309

[me@linuxbox ~]$ du -s /usr/share/* | head 252 /usr/share/aclocal 96 /usr/share/acpi-support 8 /usr/share/adduser 196 /usr/share/alacarte 344 /usr/share/alsa 8 /usr/share/alsa-base 12488 /usr/share/anthy 8 /usr/share/apmd 21440 /usr/share/app-install 48 /usr/share/application-registry

В этом примере мы передали результаты по конвейеру программе head, чтобы ограничить число результатов первыми 10 строками. Мы можем изменить эту команду, добавив сортировку по числовым значениям, чтобы получить 10 самых объемных каталогов:

[me@linuxbox ~]$ du -s /usr/share/* | sort -nr | head 509940 /usr/share/locale-langpack 242660 /usr/share/doc 197560 /usr/share/fonts 179144 /usr/share/gnome 146764 /usr/share/myspell 144304 /usr/share/gimp 135880 /usr/share/dict 76508 /usr/share/icons 68072 /usr/share/apps 62844 /usr/share/foomatic

С помощью параметров n и r мы получили сортировку по числовым значениям в обратном порядке, в результате наибольшие значения оказались в начале спи- ска. Такой способ сортировки стал возможен, потому что числовые значения на- ходятся в начале каждой строки. Но как быть, если потребуется отсортировать строки по числовым значениям, находящимся в середине строки, как, например, в результатах команды ls -l?

[me@linuxbox ~]$ ls -l /usr/bin | head total 152948 -rwxr-xr-x 1 root root 34824 2016-04-04 02:42 [ -rwxr-xr-x 1 root root 101556 2007-11-27 06:08 a2p -rwxr-xr-x 1 root root 13036 2016-02-27 08:22 aconnect -rwxr-xr-x 1 root root 10552 2007-08-15 10:34 acpi -rwxr-xr-x 1 root root 3800 2016-04-14 03:51 acpi_fakekey -rwxr-xr-x 1 root root 7536 2016-04-19 00:19 acpi_listen -rwxr-xr-x 1 root root 3576 2016-04-29 07:57 addpart -rwxr-xr-x 1 root root 20808 2016-01-03 18:02 addr2line -rwxr-xr-x 1 root root 489704 2016-10-09 17:02 adept_batch

310

Глава 20. Обработка текста

Забудем на время, что ls может сортировать свои результаты по размеру, и выпол- ним сортировку с помощью sort:

[me@linuxbox ~]$ ls -l /usr/bin | sort -nr -k 5 | head -rwxr-xr-x 1 root root 8234216 2016-04-07 17:42 inkscape -rwxr-xr-x 1 root root 8222692 2016-04-07 17:42 inkview -rwxr-xr-x 1 root root 3746508 2016-03-07 23:45 gimp-2.4 -rwxr-xr-x 1 root root 3654020 2016-08-26 16:16 quanta -rwxr-xr-x 1 root root 2928760 2016-09-10 14:31 gdbtui -rwxr-xr-x 1 root root 2928756 2016-09-10 14:31 gdb -rwxr-xr-x 1 root root 2602236 2016-10-10 12:56 net -rwxr-xr-x 1 root root 2304684 2016-10-10 12:56 rpcclient -rwxr-xr-x 1 root root 2241832 2016-04-04 05:56 aptitude -rwxr-xr-x 1 root root 2202476 2016-10-10 12:56 smbcacls

Программа sort часто вовлекается в обработку табличных данных, таких как ре- зультат команды ls выше. Если воспользоваться терминологией баз данных, об этой таблице можно сказать, что каждая строка — это запись и каждая запись со- стоит из множества полей, таких как атрибуты файла, счетчик ссылок, имя файла, размер файла и т. д. Программа sort способна обрабатывать поля по отдельно- сти. Согласно той же терминологии баз данных, мы можем указать одно или не- сколько ключевых полей, которые должны использоваться как ключи сортировки. В примере, приведенном выше, мы добавили параметры n и r, чтобы выполнить сортировку по числовым значениям в порядке убывания, а также параметр k с ар- гументом 5, чтобы указать, что сортировка должна выполняться по пятому полю.

Параметр k очень интересен и обладает множеством любопытных свойств, но пре- жде чем приступить к их изучению, поговорим о том, как sort определяет поля. Рассмотрим очень простой текстовый файл, содержащий единственную строку с именем автора этой книги:

William Shotts

По умолчанию sort «видит» в этой строке два поля. Первое поле содержит после- довательность символов William, второе — последовательность символов Shotts, то есть пробельные символы (пробелы и символы табуляции) интерпретируются как разделители полей, и эти разделители включаются в поле при выполнении сортировки.

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

-rwxr-xr-x 1 root root 8234216 2016-04-07 17:42 inkscape

Для следующей серии экспериментов возьмем файл с историей выпуска новых версий трех популярных дистрибутивов Linux в период с 2006 по 2008 год. Каждая

строка в файле содержит три поля: название дистрибутива, номер версии и дата выпуска в формате ММ/ДД/ГГГГ:

cat

311

SUSE 10.2 12/07/2006 Fedora 10 11/25/2008 SUSE 11.0 06/19/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 SUSE 10.3 10/04/2007 Ubuntu 6.10 10/26/2006 Fedora 7 05/31/2007 Ubuntu 7.10 10/18/2007 Ubuntu 7.04 04/19/2007 SUSE 10.1 05/11/2006 Fedora 6 10/24/2006 Fedora 9 05/13/2008 Ubuntu 6.06 06/01/2006 Ubuntu 8.10 10/30/2008 Fedora 5 03/20/2006

С помощью текстового редактора (например, vim) введите эти данные и сохраните в файле с именем distros.txt.

Далее попробуем отсортировать файл и посмотрим, что из этого получится:

[me@linuxbox ~]$ sort distros.txt Fedora 10 11/25/2008 Fedora 5 03/20/2006 Fedora 6 10/24/2006 Fedora 7 05/31/2007 Fedora 8 11/08/2007 Fedora 9 05/13/2008 SUSE 10.1 05/11/2006 SUSE 10.2 12/07/2006 SUSE 10.3 10/04/2007 SUSE 11.0 06/19/2008 Ubuntu 6.06 06/01/2006 Ubuntu 6.10 10/26/2006 Ubuntu 7.04 04/19/2007 Ubuntu 7.10 10/18/2007 Ubuntu 8.04 04/24/2008 Ubuntu 8.10 10/30/2008

У нас это почти получилось. Единственная проблема возникла с сортировкой номеров версий Fedora. Так как в лексикографическом смысле 1 предшествует 5, версия 10 оказалась вверху, тогда как версия 9 — внизу.

312

Глава 20. Обработка текста

Чтобы исправить эту ошибку, выполним сортировку по нескольким ключам. Итак, нам нужно выполнить сортировку по первому полю в алфавитном порядке, а затем по второму полю в числовом порядке. Программа sort позволяет указать в командной строке несколько параметров -k, чтобы можно было определить не- сколько ключей сортировки. В действительности в ключ можно включать диа- пазон полей. Если диапазон не определен (как в примерах, приведенных выше), sort использует в качестве ключа часть строки, начинающуюся с указанного поля и простирающуюся до конца строки.

Вот как выглядит синтаксис сортировки по нескольким ключам:

[me@linuxbox ~]$ sort –key=1,1 –key=2n distros.txt Fedora 5 03/20/2006 Fedora 6 10/24/2006 Fedora 7 05/31/2007 Fedora 8 11/08/2007 Fedora 9 05/13/2008 Fedora 10 11/25/2008 SUSE 10.1 05/11/2006 SUSE 10.2 12/07/2006 SUSE 10.3 10/04/2007 SUSE 11.0 06/19/2008 Ubuntu 6.06 06/01/2006 Ubuntu 6.10 10/26/2006 Ubuntu 7.04 04/19/2007 Ubuntu 7.10 10/18/2007 Ubuntu 8.04 04/24/2008 Ubuntu 8.10 10/30/2008

Здесь для ясности использовались имена параметров в длинной форме, однако с тем же успехом можно было бы передать параметры -k 1,1 -k 2n. В аргументе для первого экземпляра параметра ключа мы указали диапазон полей, входящих в первый ключ. Так как сортировка должна выполняться только по первому полю, мы указали диапазон 1,1, что означает: «начиная с поля 1 и заканчивая полем 1». Второму экземпляру мы передали аргумент 2n, который означает: «ключом со- ртировки является второе поле, и сортировка выполняется в порядке числовых значений». В конце определения ключа допускаются однобуквенные имена па- раметров, они указывают на тип сортировки. Имена этих однобуквенных параме- тров совпадают с именами глобальных параметров программы sort: b (пропустить начальные пробелы), n (числовая сортировка), r (сортировка в обратном порядке) и т. д.

Третье поле в списке содержит дату в формате, неудобном для сортировки. В ком- пьютере даты обычно приводятся к виду ГГГГ-ММ-ДД, что упрощает сортировку

cat

313

в хронологическом порядке, но здесь используется американский формат ММ/ ДД/ГГГГ. Как же тогда отсортировать этот список в хронологическом порядке?

К счастью, sort предоставляет такую возможность. Параметр –key позволяет определять смещения внутри полей, чтобы в качестве ключей можно было исполь- зовать части полей:

[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt Fedora 10 11/25/2008 Ubuntu 8.10 10/30/2008 SUSE 11.0 06/19/2008 Fedora 9 05/13/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 Ubuntu 7.10 10/18/2007 SUSE 10.3 10/04/2007 Fedora 7 05/31/2007 Ubuntu 7.04 04/19/2007 SUSE 10.2 12/07/2006 Ubuntu 6.10 10/26/2006 Fedora 6 10/24/2006 Ubuntu 6.06 06/01/2006 SUSE 10.1 05/11/2006 Fedora 5 03/20/2006

Добавив параметр -k 3.7, мы сообщили программе sort, что она должна исполь- зовать для сортировки ключ, начинающийся с седьмого символа в третьем поле, который соответствует началу года. Аналогично, параметры -k 3.1 и -k 3.4 опре- деляют ключи сортировки по месяцу и дню месяца. Мы также добавили параме- тры n и r, чтобы выполнить числовую сортировку в обратном порядке. Параметр b добавлен для исключения начальных пробелов из поля с датой (число которых в разных строках отличается и тем самым влияет на результат сортировки).

В некоторых файлах в качестве разделителей используются символы, отличные от пробелов и символов табуляции; возьмем, к примеру, файл /etc/passwd:

[me@linuxbox ~]$ head /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh

314

Глава 20. Обработка текста

Поля в этом файле отделяются друг от друга двоеточием (:). Можно ли отсорти- ровать содержимое этого файла с использованием ключевых полей? Программа sort поддерживает параметр -t для определения символа — разделителя полей. Чтобы отсортировать содержимое файла passwd по седьмому полю (командная оболочка по умолчанию), используем такую команду:

[me@linuxbox ~]$ sort -t ‘:’ -k 7 /etc/passwd | head me:x:1001:1001:Myself,,,:/home/me:/bin/bash root:x:0:0:root:/root:/bin/bash dhcp:x:101:102::/nonexistent:/bin/false gdm:x:106:114:Gnome Display Manager:/var/lib/gdm:/bin/false hplip:x:104:7:HPLIP system user,,,:/var/run/hplip:/bin/false klog:x:103:104::/home/klog:/bin/false messagebus:x:108:119::/var/run/dbus:/bin/false polkituser:x:110:122:PolicyKit,,,:/var/run/PolicyKit:/bin/false pulse:x:107:116:PulseAudio daemon,,,:/var/run/pulse:/bin/false

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

uniq — выявление или удаление повторяющихся строк

В сравнении с sort программа uniq более легковесна. Она решает, казалось бы, тривиальную задачу. Когда ей передается сортированный файл (в том числе и стандартный ввод), она удаляет повторяющиеся строки и выводит результат в стандартный вывод. Она часто используется в сочетании с sort для удаления повторяющихся строк.

СОВЕТ

Даже притом, что uniq — инструмент, традиционный для Unix, который часто используется вместе с sort, GNU-версия sort поддерживает параметр -u, уда- ляющий повторяющиеся строки из сортированных результатов.

Давайте создадим текстовый файл для последующих экспериментов:

[me@linuxbox ~]$ cat > foo.txt a b c a b c

Не забудьте ввести CTRL+D, чтобы завершить ввод с клавиатуры. Если теперь при- менить uniq к нашему текстовому файлу, результат ничем не будет отличаться от оригинала; повторяющиеся записи никуда не исчезли:

cat

315

[me@linuxbox ~]$ uniq foo.txt a b c a b c

Чтобы uniq действительно выполнила свою работу, исходные данные нужно сна- чала отсортировать:

[me@linuxbox ~]$ sort foo.txt | uniq a b c

Это объясняется тем, что uniq удаляет повторяющиеся записи, только если они следуют друг за другом.

uniq имеет несколько параметров. Наиболее часто используемые из них перечис- лены в табл. 20.2.

Таблица 20.2. Часто используемые параметры команды uniq

Параметр

-c

-d

Длинный параметр

–count

Описание

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

–repeated

Вывести только повторяющиеся, не уникальные строки

-f n

–skip-fields=n

Пропустить n начальных полей в каждой строке. Деле- ние на поля производится по пробельным символам, как в программе sort; однако, в отличие от sort, про- грамма uniq не имеет параметра для настройки альтер- нативного разделителя полей

-i

-s n

-u

–ignore-case

Сравнивать строки без учета регистра символов

–skip-chars=n

Пропустить n начальных символов в каждой строке

–unique

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

316

Глава 20. Обработка текста

В следующем примере используется параметр -c программы uniq для определе- ния числа повторяющихся строк в исходном текстовом файле:

[me@linuxbox ~]$ sort foo.txt | uniq -c 2 a 2 b 2 c

Нарезка и перетасовка текста

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

cut — удаление фрагментов из всех строк в файлах

Программа cut используется для извлечения фрагментов текста из строк и выво- да их в стандартный вывод. Она может принимать имена файлов в аргументах или данные со стандартного ввода.

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

Таблица 20.3. Параметры команды cut для выбора фрагментов

Параметр

Длинный параметр

Описание

-c список_сим- волов

–characters= спи- сок_символов

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

-f список_полей

–fields= список_полей Извлекает одно или несколько полей из строки, как определено аргументом спи- сок_символов. Список может включать одно или несколько полей или диапазо- нов полей, разделенных запятыми

-d символ_разде- литель

–delimiter= символ_разделитель

–complement

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

Извлекает строку текста целиком, кроме фрагментов, определяемых параметром -c и/или -f

Нарезка и перетасовка текста

317

Как видите, программа cut не обладает особенной гибкостью. Она лучше всего подходит для извлечения фрагментов из текста, произведенного другими про- граммами, а не человеком. Давайте вернемся к нашему файлу distros.txt и по- смотрим, достаточно ли он «хорош» для программы cut. Если воспользоваться программой cat с параметром -A, можно увидеть, отвечает ли файл требованию в отношении использования символа табуляции в качестве разделителя полей.

[me@linuxbox ~]$ cat -A distros.txt SUSE^I10.2^I12/07/2006$ Fedora^I10^I11/25/2008$ SUSE^I11.0^I06/19/2008$ Ubuntu^I8.04^I04/24/2008$ Fedora^I8^I11/08/2007$ SUSE^I10.3^I10/04/2007$ Ubuntu^I6.10^I10/26/2006$ Fedora^I7^I05/31/2007$ Ubuntu^I7.10^I10/18/2007$ Ubuntu^I7.04^I04/19/2007$ SUSE^I10.1^I05/11/2006$ Fedora^I6^I10/24/2006$ Fedora^I9^I05/13/2008$ Ubuntu^I6.06^I06/01/2006$ Ubuntu^I8.10^I10/30/2008$ Fedora^I5^I03/20/2006$

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

[me@linuxbox ~]$ cut -f 3 distros.txt 12/07/2006 11/25/2008 06/19/2008 04/24/2008 11/08/2007 10/04/2007 10/26/2006 05/31/2007 10/18/2007 04/19/2007 05/11/2006 10/24/2006 05/13/2008 06/01/2006 10/30/2008 03/20/2006

318

Глава 20. Обработка текста

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

[me@linuxbox ~]$ cut -f 3 distros.txt | cut -c 7-10 2006 2008 2008 2008 2007 2007 2006 2007 2007 2007 2006 2006 2008 2006 2008 2006

Применив cut второй раз к нашему списку, мы смогли извлечь символы в пози- циях с 7-й по 10-ю, которые соответствуют году в поле с датой. Форма записи 7-10 — это пример определения диапазона. Полное описание особенностей опре- деления диапазонов находится на странице справочного руководства (man) для команды cut.

При работе с полями определим разделитель, отличающийся от символа табуля- ции. Следующий пример извлекает первое поле из файла /etc/passwd:

[me@linuxbox ~]$ cut -d ‘:’ -f 1 /etc/passwd | head root daemon bin sys sync games man lp mail news

Нарезка и перетасовка текста

319

С помощью параметра -d мы определили, что роль разделителя полей будет играть двоеточие.

ЗАМЕНА СИМВОЛОВ ТАБУЛЯЦИИ

Наш файл distros.txt идеально отформатирован для извлечения полей с использо- ванием cut. Но что, если нам понадобится обработать файл, вырезая фрагменты по символам, а не по полям? Для этого нам нужно заменить символы табуляции в файле соответствующим числом пробелов. К счастью, в GNU-пакете Coreutils имеется инструмент для этого — программа expand. Она может принимать имена файлов в аргументах или данные со стандартного ввода и выводить измененный текст в стандартный вывод.

Если обработать наш файл distros.txt программой expand, мы сможем использовать cut -c для извлечения любых диапазонов символов из файла. Например, с помо- щью следующей команды можно извлечь год выпуска из нашего файла со списком, применив cut для извлечения всех символов с 23-й позиции до конца строки:

[me@linuxbox ~]$ expand distros.txt | cut -c 23-

В состав пакета Coreutils входит также программа unexpand, замещающая пробелы символами табуляции.

paste — слияние строк из файлов

Команда paste выполняет операцию, обратную команде cut. Вместо извлечения колонок текста из файла она добавляет одну или несколько колонок текста в файл. Для этого она читает содержимое нескольких файлов, комбинирует поля, найден- ные в них, и выводит результат в стандартный вывод. Подобно программе cut, paste принимает несколько файлов в аргументах и/или данные со стандартного ввода. Для демонстрации возможностей программы paste выполним небольшую хирургическую операцию с файлом distros.txt, чтобы получить список выпусков в хронологическом порядке.

Сначала применим команду sort, чтобы получить список дистрибутивов, отсо- ртированный по дате выпуска, и сохраним результат в файле distros-by-date.txt:

[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt > distros-by-date.txt

Затем с помощью cut извлечем два первых поля (с именами дистрибутивов и но- мерами версий) и сохраним результат в файле distro-versions.txt:

320

Глава 20. Обработка текста

[me@linuxbox ~]$ cut -f 1,2 distros-by-date.txt > distros-versions.txt [me@linuxbox ~]$ head distros-versions.txt Fedora 10 Ubuntu 8.10 SUSE 11.0 Fedora 9 Ubuntu 8.04 Fedora 8 Ubuntu 7.10 SUSE 10.3 Fedora 7 Ubuntu 7.04

Завершая этап подготовки, извлечем даты выпусков и сохраним их в файле distro- dates.txt:

[me@linuxbox ~]$ cut -f 3 distros-by-date.txt > distros-dates.txt [me@linuxbox ~]$ head distros-dates.txt 11/25/2008 10/30/2008 06/19/2008 05/13/2008 04/24/2008 11/08/2007 10/18/2007 10/04/2007 05/31/2007 04/19/2007

Теперь у нас есть все необходимое. Чтобы завершить процедуру, с помощью paste добавим колонку с датами перед названиями и номерами версий дистрибутивов, создав хронологический список. Для этого достаточно просто вызвать paste и пе- редать ей файлы в требуемом порядке.

[me@linuxbox ~]$ paste distros-dates.txt distros-versions.txt 11/25/2008 Fedora 10 10/30/2008 Ubuntu 8.10 06/19/2008 SUSE 11.0 05/13/2008 Fedora 9 04/24/2008 Ubuntu 8.04 11/08/2007 Fedora 8 10/18/2007 Ubuntu 7.10 10/04/2007 SUSE 10.3 05/31/2007 Fedora 7 04/19/2007 Ubuntu 7.04 12/07/2006 SUSE 10.2 10/26/2006 Ubuntu 6.10

Нарезка и перетасовка текста

321

10/24/2006 Fedora 6 06/01/2006 Ubuntu 6.06 05/11/2006 SUSE 10.1 03/20/2006 Fedora 5

join — объединение строк из двух файлов по общему полю

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

Чтобы понять, как действует операция join в реляционной базе данных, пред- ставьте очень маленькую базу данных с двумя таблицами, по одной записи в каж- дой. Первая таблица, с именем CUSTOMERS, имеет три поля: номер клиента (CUSTNUM), имя клиента (FNAME) и фамилия клиента (LNAME):

CUSTNUM FNAME LNAME ========= ====== ====== 4681934 John Smith

Вторая таблица, с именем ORDERS, имеет четыре поля: номер заказа (ORDERNUM), номер клиента (CUSTNUM), количество (QUAN) и пункт зака- за (ITEM):

ORDERNUM CUSTNUM QUAN ITEM ========== ========= ===== ==== 3014953305 4681934 1 Blue Widget

Обратите внимание, что обе таблицы имеют общее поле CUSTNUM. Это важно, так как оно устанавливает отношение между таблицами.

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

FNAME LNAME QUAN ITEM ====== ====== ===== ==== John Smith 1 Blue Widget

Для демонстрации программы join нам понадобится пара файлов с общим ключом. Возьмем в качестве отправной точки файл distros-by-date.txt и из него

322

Глава 20. Обработка текста

сконструируем два дополнительных файла. Первый будет содержать даты выпу- сков (которые в этом примере будут играть роль общего ключа) и названия дис- трибутивов:

[me@linuxbox ~]$ cut -f 1,1 distros-by-date.txt > distros-names.txt [me@linuxbox ~]$ paste distros-dates.txt distros-names.txt > distros-key-names.txt [me@linuxbox ~]$ head distros-key-names.txt 11/25/2008 Fedora 10/30/2008 Ubuntu 06/19/2008 SUSE 05/13/2008 Fedora 04/24/2008 Ubuntu 11/08/2007 Fedora 10/18/2007 Ubuntu 10/04/2007 SUSE 05/31/2007 Fedora 04/19/2007 Ubuntu

И второй — даты выпусков и номера версий:

[me@linuxbox ~]$ cut -f 2,2 distros-by-date.txt > distros-vernums.txt [me@linuxbox ~]$ paste distros-dates.txt distros-vernums.txt > distros-keyvernums.txt [me@linuxbox ~]$ head distros-key-vernums.txt 11/25/2008 10 10/30/2008 8.10 06/19/2008 11.0 05/13/2008 9 04/24/2008 8.04 11/08/2007 8 10/18/2007 7.10 10/04/2007 10.3 05/31/2007 7 04/19/2007 7.04

Теперь у нас есть два файла с общим ключом (поле «дата выпуска»). Здесь важ- но отметить, что файлы должны быть отсортированы по ключевому полю, чтобы программа join выдала правильный результат.

[me@linuxbox ~]$ join distros-key-names.txt distros-key-vernums.txt | head 11/25/2008 Fedora 10 10/30/2008 Ubuntu 8.10 06/19/2008 SUSE 11.0 05/13/2008 Fedora 9 04/24/2008 Ubuntu 8.04 11/08/2007 Fedora 8 10/18/2007 Ubuntu 7.10

Сравнение текста

323

10/04/2007 SUSE 10.3 05/31/2007 Fedora 7 04/19/2007 Ubuntu 7.04

Отметьте также, что по умолчанию в качестве разделителя полей во входных дан- ных join использует символы табуляции, а в выводе — пробел. Такое поведение можно изменить с помощью параметров. За дополнительными подробностями об- ращайтесь к странице справочного руководства (man) для join.

Сравнение текста

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

comm — построчное сравнение двух сортированных файлов

Программа comm сравнивает два текстовых файла, показывая, какие строки в них уникальные, а какие — одинаковые. Для демонстрации создадим с помощью cat два почти идентичных файла:

[me@linuxbox ~]$ cat > file1.txt a b c d [me@linuxbox ~]$ cat > file2.txt b c d e

Затем сравним эти два файла с помощью comm:

[me@linuxbox ~]$ comm file1.txt file2.txt a b c d e

324

Глава 20. Обработка текста

Как видите, comm произвела вывод в три колонки. Первая колонка содержит уни- кальные строки из первого файла, вторая — уникальные строки из второго фай- ла, третья — строки, одинаковые в обоих файлах. Программа comm поддерживает параметры в формате -n, где n может быть числом 1, 2 или 3. При использовании эти параметры определяют номера колонок, вывод которых следует подавить. Например, чтобы вывести только одинаковые строки, нужно подавить вывод колонок 1 и 2:

[me@linuxbox ~]$ comm -12 file1.txt file2.txt b c d

diff — построчное сравнение файлов

Подобно программе comm, diff используется для выявления различий между файлами. Однако diff намного более сложный инструмент, поддерживаю- щий вывод во множестве форматов и способный обрабатывать сразу огромные коллекции файлов. Программа diff часто используется разработчиками про- граммного обеспечения для исследования различий между разными версиями исходного программного кода, потому что позволяет рекурсивно обходить ка- талоги, которые часто называют деревьями исходного кода (source trees). Часто программа diff применяется для создания diff-файлов, или заплат (patches), которые могут использоваться другими программами, такими как patch (о ко- торой рассказывается чуть ниже), для преобразования файлов из одной версии в другую.

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

[me@linuxbox ~]$ diff file1.txt file2.txt 1d0 e

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

В этом формате любой диапазон представлен списком через запятую номеров начальной и конечной строки. Хотя этот формат используется по умолчанию

Сравнение текста

325

(главным образом для совместимости со стандартом POSIX и обратной совме- стимости с традиционными версиями diff для Unix), он не так широко исполь- зуется, как другие, дополнительные форматы. Два других формата, получив- ших большую популярность, — это контекстный формат и унифицированный формат.

Таблица 20.4. Команды изменения, генерируемые программой diff

Команда

Описание

r1ar2

r1cr2

r1dr2

Добавить строки, находящиеся в диапазоне r2 во втором файле, после строк в позиции r1 в первом файле

Изменить (заменить) строки в диапазоне r1 в первом файле строками в диапазоне r2 во втором файле

Удалить строки в диапазоне r1 в первом файле, которые находились бы в диапазоне r2 во втором файле

При использовании контекстного формата (параметр -c) вывод выглядит так:

[me@linuxbox ~]$ diff -c file1.txt file2.txt *** file1.txt 2008-12-23 06:40:13.000000000 -0500 — file2.txt 2008-12-23 06:40:34.000000000 -0500


*** 1,4 ****

  • a b c d — 1,4 —- b

    c d

  • e

Вывод начинается с имен двух файлов и времени последнего их изменения. Пер- вый файл отмечается звездочками, а второй — дефисами. На протяжении всей оставшейся части листинга эти маркеры обозначают соответствующие им файлы. Далее следуют группы изменений, включая заданное по умолчанию число окру- жающих строк, определяющих контекст. Первая группа начинается со строки:

*** 1,4 ****

326

Глава 20. Обработка текста

указывающей на строки с номерами с 1-го по 4-й в первом файле. Далее следует строка:

— 1,4 —-

указывающая на строки с номерами с 1-го по 4-й во втором файле. Внутри группы изменений присутствуют строки, начинающиеся с одного из четырех индикато- ров, перечисленных в табл. 20.5.

Таблица 20.5. Индикаторы изменений, генерируемые программой diff при использовании контекстного формата

Индикатор

Значение

(нет)

Строка показана для контекста. В ней отсутствуют различия между файлами

!

Строка удалена. Эта строка присутствует в первом файле и отсутствует во втором

Строка добавлена. Эта строка присутствует во втором файле и отсут- ствует в первом

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

Унифицированный формат напоминает контекстный, но более компактный. За- дается параметром -u:

[me@linuxbox ~]$ diff -u file1.txt file2.txt — file1.txt 2008-12-23 06:40:13.000000000 -0500 +++ file2.txt 2008-12-23 06:40:34.000000000 -0500 @@ -1,4 +1,4 @@ -a b c d +e

Самое большое отличие между контекстным и унифицированным форматами — отсутствие повторяющихся контекстных строк, благодаря чему обеспечивается большая компактность унифицированного формата в сравнении с контекстным. В примере, приведенном выше, видны те же времена последнего изменения фай- лов, что и в контекстном формате, за которыми следует строка @@ -1,4 +1,4 @@. Она указывает номера строк в первом и во втором файлах, описываемых группой

Сравнение текста

327

изменений. Далее следуют сами строки с тремя (по умолчанию) строками контек- ста. Каждая строка начинается с одного из трех возможных символов, значение которых описывается в табл. 20.6.

Таблица 20.6. Индикаторы изменений, генерируемые программой diff при использовании унифицированного формата

Индикатор

Значение

(нет)

Строка, одинаковая в обоих файлах

Строка отсутствует в первом файле

Строка присутствует только в первом файле

patch — применение diff-файла к оригиналу

Программа patch используется для применения изменений к текстовым файлам. Она принимает вывод программы diff и обычно используется для преобразо- вания старых версий файлов в более новые. Рассмотрим один известный при- мер. Ядро Linux разрабатывается большой, свободно организованной группой разработчиков, от которых неиссякаемым потоком идут небольшие изменения в исходном коде. Ядро Linux включает миллионы строк программного кода, но изменения, присылаемые одним разработчиком за один раз, очень невелики. Раз- работчикам нет смысла пересылать все дерево исходных текстов ядра всякий раз, когда вносится небольшое изменение. Вместо этого они присылают diff-файлы. Эти файлы описывают различия между предыдущей версией ядра и новой, вклю- чающей изменения разработчика. Другой разработчик, получивший такое изме- нение, использует программу patch, позволяющую применить предложенное из- менение к своему дереву исходных текстов. Использование пары программ diff/ patch дает два важных преимущества:

 diff-файл очень невелик в сравнении с полным деревом исходных текстов;

 diff-файл наглядно показывает произведенные изменения, что позволяет экс-

пертам быстро применить эти изменения и оценить их.

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

Чтобы подготовить diff-файл для последующего его применения программой patch, документация GNU предлагает использовать diff, как показано ниже:

diff -Naur старый_файл новый_файл > diff_файл

328

Глава 20. Обработка текста

где старый_файл и новый_файл могут быть одиночными файлами или каталогами. Параметр r поддерживает рекурсивный обход вложенных подкаталогов.

Получив diff-файл, его можно применить к старому файлу, чтобы получить новый:

patch patchfile.txt [me@linuxbox ~]$ patch файл_unix

где файл_dos — это файл, подлежащий преобразованию, а файл_unix — результат. В этой форме команды используется экранированная последовательность \r, представляющая символ возврата каретки. Чтобы увидеть полный список после- довательностей и классов символов, поддерживаемых программой tr, попробуйте:

[me@linuxbox ~]$ tr –help

330

Глава 20. Обработка текста

tr также позволяет выполнять и другие трюки. При вызове с параметром -s команда tr «сжимает» (squeeze), или удаляет, повторяющиеся экземпляры сим- волов:

[me@linuxbox ~]$ echo “aaabbbccc” | tr -s ab abccc

Здесь у нас имеется строка с повторяющимися символами. Передав множество ab команде tr, мы удалили повторяющиеся экземпляры символов, входящие в мно- жество, при этом остальные символы (c), отсутствующие в множестве, остались нетронутыми. Обратите внимание, что повторяющиеся символы должны следо- вать подряд. В противном случае сжатие не даст никакого эффекта:

[me@linuxbox ~]$ echo “abcabcabc” | tr -s ab abcabcabc

ROT13: НЕ САМЫЙ НАДЕЖНЫЙ СПОСОБ ШИФРОВАНИЯ

Одно забавное применение команды tr — шифрование текста по алгоритму ROT13. ROT13 — тривиальный тип шифрования, основанный на простом подстановочном шифре. Шифрованием назвать этот алгоритм можно только с большой натяжкой, скорее это алгоритм обфускации (запутывания) текста. Он используется иногда для запутывания потенциально уязвимого содержимого. Метод заключается в простом смещении каждого символа на 13 позиций далее по алфавиту. Так как число 13 соответствует середине набора из 26 символов, повторное применение алгоритма к тексту приводит к его восстановлению в исходное состояние. Шифрование с по- мощью tr выполняется, как показано ниже:

echo “secret text” | tr a-zA-Z n-za-mN-ZA-M frperg grkg

Повторное применение той же процедуры приводит к обратному преобразованию:

echo “frperg grkg” | tr a-zA-Z n-za-mN-ZA-M secret text

Многие программы для работы с электронной почтой и чтения новостей Usenet поддерживают шифрование ROT13. В Википедии можно найти замечательную статью по этой теме: http://ru.wikipedia.org/wiki/ROT13.

Редактирование на лету

331

sed — потоковый редактор для фильтрации и преобразования текста

Имя sed — это сокращенное словосочетание stream editor (потоковый редактор). Данная команда осуществляет редактирование потока текста, получаемого из множества файлов или подаваемого на стандартный ввод команды. sed — мощная и достаточно сложная программа (ей посвящены целые книги), поэтому здесь мы не будем рассматривать ее во всех подробностях.

В общем случае sed используется следующим образом: ей передается единствен- ная команда редактирования (в командной строке) или имя файла сценария с множеством команд, и она применяет эти команды к каждой строке в потоке текста. Ниже приводится очень простой пример sed в действии:

[me@linuxbox ~]$ echo “front” | sed ’s/front/back/’ back

В этом примере с помощью echo создается поток текста с единственным словом, который по конвейеру передается программе sed. sed, в свою очередь, применяет инструкцию s/front/back/ к тексту в потоке и выводит результат. Эта команда напоминает команду подстановки (поиск с заменой) в редакторе vi.

Команды sed начинаются с единственной буквы. В примере, рассмотренном выше, буква s представляет команду подстановки (substitution). За ней следуют иско- мая строка и строка замены, разделенные слешем. В качестве разделителя мож- но использовать любые символы. По общепринятому соглашению, чаще других используется символ «слеш», но sed будет использовать в качестве разделителя любой символ, следующий сразу за командой. Ту же самую команду можно было бы записать иначе:

[me@linuxbox ~]$ echo “front” | sed ’s_front_back_’ back

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

Большинству команд в sed может предшествовать адрес, который определяет, какие строки во входном потоке должны редактироваться. Если адрес отсутству- ет, команда редактирования применяется ко всем строкам во входном потоке.

332

Глава 20. Обработка текста

В простейшем случае адрес — это номер строки. Мы могли бы добавить единицу в наш пример:

[me@linuxbox ~]$ echo “front” | sed ‘1s/front/back/’ back

Добавление адреса 1 в команду гарантирует применение операции подстановки только к первой строке в нашем однострочном потоке. Можно указать другое число:

[me@linuxbox ~]$ echo “front” | sed ‘2s/front/back/’ front

Теперь, как видите, редактирование не было выполнено, потому что во входном потоке отсутствует строка с номером 2.

Адреса можно выражать множеством способов. В табл. 20.7 перечислены адреса, чаще других используемые на практике.

Таблица 20.7. Форма записи адресов в команде sed

Адрес

Описание

n

$

Номер строки, где n — положительное число

Последняя строка

/регулярное_выражение/

адр1,адр2

первая~шаг

адр1,+n

адр!

Строки, соответствующие простому регулярному выраже- нию POSIX. Обратите внимание, что регулярное выражение должно ограничиваться символ «слеш» с обеих сторон. При желании можно использовать другие ограничитель- ные символы, определив регулярное выражение в форме \cрегулярное_выражениеc, где c — альтернативный символ- ограничитель

Диапазон строк с номерами от адр1 по адр2 включительно. Каждый адрес может иметь любую форму из перечисленных выше

Соответствует строке с номером первая и каждой последую- щей с указанным шагом. Например, адрес 12 соответствует всем строкам с нечетными номерами, а адрес 55 соответ- ствует пятой строке и каждой пятой последующей

Соответствует строке с адресом адр1 и следующим за ней n строкам

Соответствует всем строкам, кроме строки с адресом адр, где адрес может иметь любую форму из перечисленных выше

Редактирование на лету

333

Рассмотрим разные способы адресации строк на примере файла distros.txt, создан- ного выше в этой главе. Сначала попробуем диапазоны номеров строк:

[me@linuxbox ~]$ sed -n ‘1,5p’ distros.txt SUSE 10.2 12/07/2006 Fedora 10 11/25/2008 SUSE 11.0 06/19/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007

В нашем примере мы вывели строки с 1-й по 5-ю. Для этого использовалась коман- да p, которая просто выводит строки, соответствующие адресам. Однако здесь нам пришлось добавить параметр -n (параметр подавления автоматического вывода), чтобы программа sed не выводила все строки, что она делает по умолчанию.

Далее попробуем задействовать регулярное выражение:

[me@linuxbox ~]$ sed -n ‘/SUSE/p’ distros.txt SUSE 10.2 12/07/2006 SUSE 11.0 06/19/2008 SUSE 10.3 10/04/2007 SUSE 10.1 05/11/2006

Добавив регулярное выражение /SUSE/, заключенное в слеши, мы смогли выде- лить строки подобно тому, как это делает программа grep.

Наконец, попробуем применить оператор отрицания, добавив в адрес восклица- тельный знак (!):

[me@linuxbox ~]$ sed -n ‘/SUSE/!p’ distros.txt Fedora 10 11/25/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 Ubuntu 6.10 10/26/2006 Fedora 7 05/31/2007 Ubuntu 7.10 10/18/2007 Ubuntu 7.04 04/19/2007 Fedora 6 10/24/2006 Fedora 9 05/13/2008 Ubuntu 6.06 06/01/2006 Ubuntu 8.10 10/30/2008 Fedora 5 03/20/2006

Здесь мы видим ожидаемый результат: все строки из файла, кроме совпавших с регулярным выражением.

334

Глава 20. Обработка текста

Пока что мы познакомились лишь с двумя командами редактирования, поддер- живаемыми программой sed: s и p. В табл. 20.8 приводится более полный список основных команд редактирования.

Таблица 20.8. Основные команды редактирования sed

Команда

Описание

=

a

d

i

p

q

Q

s/регулярное_выражение/ строка_замены/

y/множество1/множество2

Выводит номер текущей строки

Добавляет текст в конец текущей строки

Удаляет текущую строку

Вставляет текст в начало текущей строки

Выводит текущую строку. По умолчанию sed выводит все строки, но редактирует только соответствующие указан- ному адресу. Поведение по умолчанию можно отменить, передав параметр -n

Завершает sed без обработки остальных строк. Если пара- метр -n не указан, выводит текущую строку

Завершает sed без обработки остальных строк

Замещает совпадение с регулярным выражением строкой замены. Строка замены может включать специальный символ &, обозначающий совпадение с регулярным вы- ражением. Кроме того, строка замены может включать последовательности, с \1 по \9, обозначающие совпаде- ния с соответствующими подвыражениями в регулярном выражении. Дополнительную информацию по этой теме можно найти в обзоре обратных ссылок ниже. За симво- лом «слеш», закрывающим строку замены, может следо- вать необязательный флаг, определяющий дополнитель- ные особенности поведения команды

Выполняет перекодирование, преобразуя символы из первого множества в символы второго множества. Имейте в виду, что в отличие от программы tr, sed требует, что- бы оба множества были одинаковой длины

Команда s, вне всяких сомнений, используется намного чаще других команд ре- дактирования. Далее мы рассмотрим только часть ее возможностей, выполняя ре- дактирование нашего файла distros.txt. Мы уже говорили, что поле даты в distros. txt хранит информацию не в самом «дружественном» для компьютеров виде. Здесь даты записаны в формате ММ/ДД/ГГГГ, однако гораздо удобнее (для со- ртировки) было бы, если бы даты были записаны в формате ГГГГ-ММ-ДД. Замена

Редактирование на лету

335

представления дат вручную — довольно утомительное занятие и чревато ошибка- ми, но с помощью sed ту же замену можно выполнить в одно действие:

[me@linuxbox ~]$ sed ’s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-
2/’ distros.txt SUSE 10.2 2006-12-07 Fedora 10 2008-11-25 SUSE 11.0 2008-06-19 Ubuntu 8.04 2008-04-24 Fedora 8 2007-11-08 SUSE 10.3 2007-10-04 Ubuntu 6.10 2006-10-26 Fedora 7 2007-05-31 Ubuntu 7.10 2007-10-18 Ubuntu 7.04 2007-04-19 SUSE 10.1 2006-05-11 Fedora 6 2006-10-24 Fedora 9 2008-05-13 Ubuntu 6.06 2006-06-01 Ubuntu 8.10 2008-10-30 Fedora 5 2006-03-20

Прекрасный результат! Правда, команда выглядит устрашающе, но она работает. За один шаг мы изменили представление дат во всем файле. Этот пример также наглядно показывает, почему про регулярные выражения иногда в шутку говорят «только для записи». Мы можем писать их, но прочитать их порой никак не полу- чается. Прежде чем сбежать от этой устрашающей команды, давайте посмотрим, как она была сконструирована. Во-первых, как мы уже знаем, эта команда имеет следующую структуру:

sed ’s/регулярное_выражение/строка_замены/’ distros.txt

Теперь разберем регулярное выражение, отыскивающее даты. Так как даты имеют формат ММ/ДД/ГГГГ и находятся в конце строки, найти их можно с помощью следующего выражения:

[0-9]{2}/[0-9]{2}/[0-9]{4}$

которому соответствуют две цифры, слеш, две цифры, слеш, четыре цифры и ко- нец строки. Так, с регулярным выражением разобрались, а что со строкой замены? Чтобы описать ее, нам необходимо познакомиться с новой для нас особенностью регулярных выражений, которую можно использовать в некоторых приложениях, поддерживающих BRE. Эта особенность называется обратные ссылки, и действу- ет она так: если в строке замены присутствует последовательность \n, где n — чис- ло от одного до девяти, эта последовательность будет ссылаться на совпадение

336

Глава 20. Обработка текста

с соответствующим подвыражением в предшествующем регулярном выражении. Чтобы создать подвыражение, достаточно просто заключить часть регулярного выражения в круглые скобки, например:

([0-9]{2})/([0-9]{2})/([0-9]{4})$

Теперь у нас есть три подвыражения. Первому соответствует месяц, второму — число месяца и третьему — год. Соответственно строку замены можно выразить так:

\3-\1-\2

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

Теперь наша команда приобрела следующий вид:

sed ’s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/’ distros.txt

Но остались две проблемы. Первая: дополнительные слеши в регулярном выра- жении запутают программу sed, когда она попытается интерпретировать коман- ду s. Вторая: так как по умолчанию sed принимает только простые регулярные выражения, некоторые символы в нашем регулярном выражении будут интерпре- тироваться как литералы, а не как метасимволы. Мы решим обе проблемы, при- менив обратные слеши для экранирования нужных символов:

sed ’s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/’ distros.txt

И дело в шляпе!

Другая особенность команды s — возможность использования дополнительных флагов вслед за строкой замены. Наиболее примечательным из них является флаг g, который требует от sed применить поиск с заменой к строке глобально (globally), а не только к первому найденному совпадению, как это делается по умолчанию. Например:

[me@linuxbox ~]$ echo “aaabbbccc” | sed ’s/b/B/’ aaaBbbccc

Как видите, замена была выполнена только для первого вхождения буквы b, а остальные остались нетронутыми. Добавив флаг g, можно изменить все вхож- дения:

[me@linuxbox ~]$ echo “aaabbbccc” | sed ’s/b/B/g’ aaaBBBccc

Редактирование на лету

337

До сих пор мы передавали команды программе sed только по одной и только в командной строке. Однако существует возможность создавать более сложные коман ды в файлах сценариев и передавать эти сценарии с помощью параметра -f. Для демонстрации создадим с помощью sed отчет на основе нашего файла distros. txt. Отчет будет содержать заголовок вверху, измененные даты и названия дис- трибутивов будут преобразованы в верхний регистр. Для этого нам понадобится написать сценарий, поэтому запустите текстовый редактор и введите следующие строки:

Сценарий для sed, создающий отчет о дистрибутивах Linux #

1 i

Linux Distributions Report
s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/ y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

Сохраните сценарий в файл с именем distros.sed и запустите его:

[me@linuxbox ~]$ sed -f distros.sed distros.txt

Linux Distributions Report

SUSE 10.2 2006-12-07 FEDORA 10 2008-11-25 SUSE 11.0 2008-06-19 UBUNTU 8.04 2008-04-24 FEDORA 8 2007-11-08 SUSE 10.3 2007-10-04 UBUNTU 6.10 2006-10-26 FEDORA 7 2007-05-31 UBUNTU 7.10 2007-10-18 UBUNTU 7.04 2007-04-19 SUSE 10.1 2006-05-11 FEDORA 6 2006-10-24 FEDORA 9 2008-05-13 UBUNTU 6.06 2006-06-01 UBUNTU 8.10 2008-10-30 FEDORA 5 2006-03-20

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

[me@linuxbox ~]$ cat -n distros.sed 1 # Сценарий для sed, создающий отчет о дистрибутивах Linux

338

Глава 20. Обработка текста

2 3 1 i
4
5 Linux Distributions Report
6 7 s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/ 8 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

Строка с номером 1 — это комментарий. Так же как во многих конфигурационных файлах и языках программирования, широко используемых в Linux, коммента- рии начинаются с символа #, за которым следует пояснительный текст. Коммента- рии можно помещать в сценарии в любое место (только не в сами команды), и они, безусловно, полезны для всех, кто хочет выяснить особенности работы сценария или сопровождать его.

Строка 2 — это пустая строка. Так же как комментарии, мы можем добавлять пу- стые строки для удобочитаемости.

Многие команды sed поддерживают адресацию строк. Адреса используются, что- бы определить, к каким строкам во входном потоке применяется операция. Адре- са могут выражаться в форме простых номеров строк, диапазонов номеров и спе- циального номера $, соответствующего последней входной строке.

В строках с 3-й по 6-ю содержится текст, который должен быть вставлен по адресу 1, в первую входную строку. Команда i, сопровождаемая последовательностью из обратного слеша и перевода строки, производит экранированный символ перево- да, или то, что мы называем символом продолжения строки. Эта последователь- ность используется во многих случаях, в том числе и в сценариях на языке ко- мандной оболочки, позволяя встраивать символ перевода строки в поток текста так, чтобы он не воспринимался интерпретатором (в данном случае программой sed) как конец строки. Команда i, а также команда a (добавления текста в конец) и команда c (замены текста) могут располагаться в нескольких строках текста, при условии, что каждая из них, кроме последней, будет завершаться символом продолжения строки. Шестая строка в нашем сценарии фактически завершает вставляемый текст и заканчивается уже не символом продолжения строки, а про- стым переводом строки, сообщая о завершении команды i.

ПРИМЕЧАНИЕ

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

Редактирование на лету

339

Строка 7 — наша команда поиска с заменой. Так как ей не предшествует никакой конкретный адрес, она будет выполнена для каждой строки во входном потоке.

Строка 8 выполняет перекодировку букв нижнего регистра в буквы верхнего ре- гистра. Обратите внимание, что, в отличие от программы tr, команда y в sed не поддерживает ни диапазоны символов (например, [a-z]), ни классы символов POSIX. И снова, так как команде y не предшествует никакой конкретный адрес, она будет выполнена для каждой строки во входном потоке.

ИСПОЛЬЗУЮЩИЕ SED ТАКЖЕ ЧАСТО ВЫБИРАЮТ…

Программа sed обладает очень широкими возможностями. С ее помощью можно решать весьма сложные задачи, связанные с редактированием потока текста. Но чаще она используется для выполнения простеньких операций, определе- ние которых укладывается в одну строку. Для решения объемных задач многие предпочитают использовать другие инструменты. Наиболее популярными из них являются awk и perl. Они не относятся к разряду простых инструментов, как программы, рассматриваемые здесь, а являются полноценными языками про- граммирования. perl, например, часто применяется взамен языка командной оболочки для решения многих задач системного администрирования, а также пользуется большой популярностью как средство разработки веб-приложений. awk имеет более узкую область применения. Основное его достоинство заклю- чается в возможности управления табличными данными. Он напоминает sed в том смысле, что программы на awk обычно занимаются построчной обработкой текстовых файлов, используя схему, похожую на адреса в sed со следующими за ними операциями. Даже при том, что рассмотрение awk и perl выходит за рамки этой книги, они являются отличными инструментами для пользователей командной строки в Linux.

aspell — интерактивная проверка орфографии

Последний инструмент, который мы рассмотрим в этой главе, — программа aspell, интерактивное средство проверки орфографии. Программа aspell является пре- емницей программы ispell, существовавшей прежде, и может использоваться как ее замена. Чаще всего программа aspell используется другими программами в тех случаях, когда необходима функция проверки орфографии, однако aspell может также весьма эффективно использоваться как самостоятельный инстру- мент командной строки. Она способна проверять текстовые файлы разных типов, включая документы HTML, программы на C/C++, электронные письма и другие специальные виды текста.

340

Глава 20. Обработка текста

Чтобы проверить орфографию в файле с простым текстом, можно вызвать aspell, как показано ниже:

aspell check текстовый_файл

где текстовый_файл — это имя файла для проверки. В качестве практического при- мера создадим простой текстовый файл с именем foo.txt, содержащий несколько произвольных орфографических ошибок:

[me@linuxbox ~]$ cat > foo.txt The quick brown fox jimped over the laxy dog.

Затем проверим файл с помощью aspell:

[me@linuxbox ~]$ aspell check foo.txt

Поскольку в режиме проверки (check) программа aspell действует интерактивно, вы увидите следующий экран:

The quick brown fox jimped over the laxy dog.


  1. jumped 6) wimped
  2. gimped 7) camped
  3. comped 8) humped
  4. limped 9) impede
  5. pimped 0) umped i) Ignore I) Ignore all r) Replace R) Replace all a) Add l) Add Lower b) Abort x) Exit

?

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

Если ввести 1, aspell заменит подозрительное слово jimped словом jumped и перей- дет к следующему подозрительному слову, laxy. Если выбрать вариант замены lazy, aspell выполнит подстановку и завершится (так как ошибок во введенной фразе больше нет). После того как aspell завершится, заглянем в файл и увидим, что все ошибки исправлены:

[me@linuxbox ~]$ cat foo.txt The quick brown fox jumped over the lazy dog.

Редактирование на лету

341

Если вызвать программу aspell без параметра –dont-backup, она создаст резерв- ную копию файла с исходным текстом, добавив к имени файла расширение .bak.

А теперь похвастаемся умением пользоваться программой sed и вернем наши ошибки на место, чтобы продолжить эксперименты с нашим файлом:

[me@linuxbox ~]$ sed -i ’s/lazy/laxy/; s/jumped/jimped/’ foo.txt

Параметр -i сообщает программе sed, что требуется отредактировать файл «на месте», в том смысле, что изменения нужно произвести в самом файле, а не пере- слать их в стандартный вывод. Здесь также показана возможность передать более одной команды редактирования, разделив их точкой с запятой.

Далее мы посмотрим, как aspell справляется с текстовыми файлами разных ви- дов. С помощью текстового редактора, например vim (наиболее смелые могут по- пробовать использовать sed), добавим в файл немного разметки HTML:

    Mispelled HTML file


    <p>The quick brown fox jimped over the laxy dog.

Если теперь попытаться проверить орфографию в измененном файле, мы стол- кнемся с проблемой. Вызвав команду

[me@linuxbox ~]$ aspell check foo.txt

мы получим следующее:

    Mispelled HTML file


    <p>The quick brown fox jimped over the laxy dog.

  1. HTML 4) Hamel
  2. ht ml 5) Hamil
  3. ht-ml 6) hotel i) Ignore I) Ignore all r) Replace R) Replace all

342

Глава 20. Обработка текста

a) Add l) Add Lower b) Abort x) Exit


?

aspell посчитала, что HTML-теги записаны с ошибками. Эту проблему можно преодолеть, передав параметр -H (HTML) режима проверки:

[me@linuxbox ~]$ aspell -H check foo.txt

Теперь результат будет выглядеть так:

    Mispelled HTML file


    <p>The quick brown fox jimped over the laxy dog.

  1. Mi spelled 6) Misapplied
  2. Mi-spelled 7) Miscalled
  3. Misspelled 8) Respelled
  4. Dispelled 9) Misspell
  5. Spelled 0) Misled i) Ignore I) Ignore all r) Replace R) Replace all a) Add l) Add Lower b) Abort x) Exit

?

Теперь теги HTML игнорируются, и проверке подвергаются только фрагменты файла, не являющиеся частью разметки. В этом режиме содержимое HTML-тегов игнорируется и не проверяется, исключение составляет содержимое тегов ALT (точнее, атрибутов alt), которое будет проверяться в этом режиме проверки.

ПРИМЕЧАНИЕ

По умолчанию aspell игнорирует адреса URL и электронной почты в тексте. Эту ситуацию можно изменить с помощью параметров командной строки. Также можно указать, какие теги разметки должны проверяться, а какие пропускаться. За подробностями обращайтесь к странице справочного руководства (man) для aspell.

Дополнительное задание

343

Заключение

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

Дополнительное задание

Существует несколько интересных команд обработки текста, на которые стоит об- ратить внимание. Среди них split (разбивает файлы на фрагменты), csplit (раз- бивает файлы на фрагменты, опираясь на контекст) и sdiff (выводит различия между файлами, что называется, «бок о бок»).

21 Форматирование

вывода

В этой главе мы продолжим знакомство с инструментами, имеющими отноше- ние к тексту, сосредоточившись на программах для форматирования выводимого текста, а не его изменения. Эти инструменты часто используются для подготовки текста к печати, о которой мы поговорим в следующей главе. В этой главе мы рас- смотрим следующие программы:

nl — нумерует строки;

fold — выполняет перенос строк, ограничивая их указанной длиной;

fmt — выполняет простое форматирование текста;

pr — форматирует текст для печати;

printf — форматирует и выводит данные;

groff — система форматирования документов.

Инструменты простого форматирования

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

Инструменты простого форматирования

345

nl — нумерация строк

Программа nl предназначена для решения простой задачи: она выполняет нуме- рацию строк. В простейшем случае использования nl напоминает команду cat -n:

[me@linuxbox ~]$ nl distros.txt | head 1 SUSE 10.2 12/07/2006 2 Fedora 10 11/25/2008 3 SUSE 11.0 06/19/2008 4 Ubuntu 8.04 04/24/2008 5 Fedora 8 11/08/2007 6 SUSE 10.3 10/04/2007 7 Ubuntu 6.10 10/26/2006 8 Fedora 7 05/31/2007 9 Ubuntu 7.10 10/18/2007 10 Ubuntu 7.04 04/19/2007

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

nl поддерживает идею логических страниц. Это дает возможность начинать ну- мерацию на каждой странице заново. С помощью параметров можно определить номер первой строки и протяженность нумерации, а также формат номеров. Логи- ческую страницу можно разбить на заголовок, тело и нижний колонтитул. В каж- дом разделе нумерация может начинаться с начала и/или может использоваться разный формат нумерации. Если программе nl передать несколько файлов, она будет интерпретировать их как один поток текста. Разделы в потоке выделяются добавлением в поток немного странной разметки, как показано в табл. 21.1.

Таблица 21.1. Разметка nl

Разметка

Значение

:::

::

:

Начало заголовка логической страницы

Начало тела логической страницы

Начало нижнего колонтитула логической страницы

Каждый элемент разметки из перечисленных в табл. 21.1 должен находиться в отдельной строке. После обработки элемента программа nl удалит его из по- тока текста.

346

Глава 21. Форматирование вывода

В табл. 21.2 перечислены наиболее часто используемые параметры nl.

Таблица 21.2. Наиболее часто используемые параметры nl

Параметр

Значение

-b стиль

Стиль нумерации тела, где аргумент стиль может иметь следующие значе- ния:

• a — нумеровать все строки; • t — нумеровать только непустые строки. Этот стиль применяется по

умолчанию;

• n — не нумеровать; • регулярное выражение — нумеровать только строки, соответствующие

простому регулярному выражению

-f стиль

-h стиль

Стиль нумерации нижнего колонтитула. По умолчанию имеет значение n (нет нумерации)

Стиль нумерации заголовка. По умолчанию имеет значение n (нет нумера- ции)

-i число

Шаг приращения номеров на странице. По умолчанию имеет значение 1

-n формат

Формат номеров, где аргумент формат может иметь следующие значения: • ln — с выравниванием по левому краю, без ведущих нулей; • rn — с выравниванием по правому краю, без ведущих нулей. Использу-

ется по умолчанию;

• rz — с выравниванием по правому краю и с ведущими нулями

-p

Не сбрасывать нумерацию в начале каждой логической страницы

-s строка

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

-v число

Номер первой строки на каждой логической странице. По умолчанию имеет значение 1

-w ширина

Ширина поля номера строки. По умолчанию имеет значение 6

Следует отметить, что на практике нумеровать строки приходится довольно ред- ко, но мы можем использовать nl, чтобы посмотреть, как объединить несколько инструментов для решения более сложных задач. Возьмем за основу наши на- работки, созданные в предыдущей главе для получения отчета о дистрибутивах

Инструменты простого форматирования

347

Linux. Поскольку далее мы будем использовать программу nl, включим в текст разметку, отделяющую заголовок/тело/нижний колонтитул. Для этого откройте в текстовом редакторе сценарий для sed из предыдущей главы, добавьте в него строки с разметкой, как показано ниже, и сохраните сценарий в файле с именем distros-nl.sed:

Сценарий для sed, создающий отчет о дистрибутивах Linux #

1 i
\:\:\:

Linux Distributions Report

Name Ver. Released
—- —- ——–
\:\: s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/ $ a
\:

End Of Report

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

Теперь выведем улучшенный отчет, объединив sort, sed и nl:

[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-nl.sed | nl

   Linux Distributions Report

    Name    Ver.    Released
    ----    ----    --------
 1  Fedora  5       2006-03-20
 2  Fedora  6       2006-10-24
 3  Fedora  7       2007-05-31
 4  Fedora  8       2007-11-08
 5  Fedora  9       2008-05-13
 6  Fedora  10      2008-11-25
 7  SUSE    10.1    2006-05-11
 8  SUSE    10.2    2006-12-07
 9  SUSE    10.3    2007-10-04

348

Глава 21. Форматирование вывода

10  SUSE    11.0    2008-06-19
11  Ubuntu  6.06    2006-06-01
12  Ubuntu  6.10    2006-10-26
13  Ubuntu  7.04    2007-04-19
14  Ubuntu  7.10    2007-10-18
15  Ubuntu  8.04    2008-04-24
16  Ubuntu  8.10    2008-10-30

  End Of Report

Наш отчет является результатом объединения в конвейер нескольких команд. Сначала мы отсортировали список по названиям дистрибутивов и номерам вер- сий (поля 1 и 2), затем обработали результат программой sed, добавив заголовок отчета (включая разметку логических страниц для nl) и нижний колонтитул. В заключение мы обработали результат с помощью программы nl, которая по умолчанию нумерует только строки в потоке текста, принадлежащие разделу с те- лом логической страницы.

Попробуйте повторить команду и поэкспериментировать с разными параметрами команды nl. Интересный результат, например, можно получить с помощью

nl -n rz

и

nl -w 3 -s ’ '

fold — перенос строк после указанной длины

Перенос строк заключается в разрыве текстовых строк по указанной ширине. По- добно другим рассматриваемым командам, fold может принимать одно или не- сколько имен файлов или данные со стандартного ввода. Если передать команде fold простой поток текста, можно увидеть, как она действует:

[me@linuxbox ~]$ echo “The quick brown fox jumped over the lazy dog.” | fold -w 12 The quick br own fox jump ed over the lazy dog.

Здесь мы видим, как действует программа fold. Текст, посланный командой echo, был разбит на сегменты указанной в параметре -w ширины. В этом при- мере мы ограничили ширину строк 12 символами. Если ширина не указана, по умолчанию она принимается равной 80 символам. Обратите внимание, что стро- ки были разбиты без учета границ слов. Добавив параметр -s, можно заставить

Инструменты простого форматирования

349

fold разбивать строки по последнему доступному пробелу перед достижением указанной ширины:

[me@linuxbox ~]$ echo “The quick brown fox jumped over the lazy dog.” | fold -w 12 -s The quick brown fox jumped over the lazy dog.

fmt — простое форматирование текста

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

Для демонстрации нам понадобится некий текст. Возьмем фрагмент из Info- страницы для fmt:

`fmt’ читает текст из файла, заданного аргументами FILE (или со стандартного ввода, если аргументы отсутствуют), и выводит результат в стандартный вывод.

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

fmt' старается разбивать строки по концам предложений и стремится не разрывать строки после первого слова или перед последним словом в предложении. "Конец предложения" определяется либо по концу абзаца, либо по слову, завершающемуся любым из символов .?!’, за которым следуют два пробела или символ перевода строки, любые скобки или кавычки при этом игнорируются. Подобно TЕX, fmt' читает "абзацы" целиком, прежде чем выполнить перенос строк; программа использует вариант алгоритма, предложенного Дональдом Э. Кнутом (Donald E. Knuth) и Михаэлем Ф. Плассом (Michael F. Plass) в статье "Breaking Paragraphs Into Lines", Software–Practice & Experience’ 11, 11 (November 1981), 1119-1184.

Скопируйте этот текст в текстовый редактор и сохраните в файле с именем fmt- info.txt. Теперь представьте, что нам нужно переформатировать этот текст, уме- стив его в колонку шириной 50 символов. Решить эту задачу можно с помощью команды fmt и ее параметра -w:

350

Глава 21. Форматирование вывода

[me@linuxbox ~]$ fmt -w 50 fmt-info.txt | head `fmt’ читает текст из файла, заданного аргументами FILE (или со стандартного ввода, если аргументы отсутствуют), и выводит результат в стандартный вывод. По умолчанию пустые строки, пробелы между словами и отступы сохраняются в выводе; последующие строки с разными отступами не объединяются; символы

Результат не особенно впечатляет. Может быть, стоит прочитать этот текст, так как он объясняет происходящее:

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

Итак, fmt сохраняет отступ в первой строке. К счастью, fmt имеет параметр, ис- правляющий это:

[me@linuxbox ~]$ fmt -cw 50 fmt-info.txt `fmt’ читает текст из файла, заданного аргументами FILE (или со стандартного ввода, если аргументы отсутствуют), и выводит результат в стандартный вывод.

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

fmt' старается разбивать строки по концам предложений и стремится не разрывать строки после первого слова или перед последним словом в предложении. "Конец предложения" определяется либо по концу абзаца, либо по слову, завершающемуся любым из символов .?!’, за которым следует два пробела или символ перевода строки, любые скобки или кавычки при этом игнорируются. Подобно TeX, `fmt’ читает “абзацы” целиком, прежде чем выполнить перенос строк; программа использует вариант алгоритма, предложенного Дональдом Э. Кнутом (Donald

Инструменты простого форматирования

351

E. Knuth) и Михаэлем Ф. Плассом (Michael F. Plass) в статье “Breaking Paragraphs Into Lines”, `Software–Practice & Experience’ 11, 11 (November 1981), 1119-1184.

Намного лучше. Добавив параметр -c, мы получили желаемый результат.

Программа fmt содержит несколько интересных параметров, которые перечисле- ны в табл. 21.3.

Таблица 21.3. Параметры fmt

Параметр

Значение

-c

-p строка

-s

-u

Включить режим обработки края (crown margin). В этом режиме сохраня- ется отступ первых строк абзаца. Последующие строки выравниваются по второй строке

Форматировать только строки, начинающиеся со строки, указанной в аргументе. После форматирования содержимое аргумента добавляется в начало каждой переформатированной строки. Этот параметр можно использовать для форматирования текста комментариев в исходном коде. Например, любой сценарий или конфигурационный файл, где комментарии начинаются с символа #, можно обработать командой fmt с параметром -p ‘# ‘, чтобы отформатировать только комментарии. Пример приводится ниже

Режим простой разбивки. В этом режиме выполняется только разбивка строк по указанной ширине. Короткие строки не объединяются. Этот режим можно использовать для форматирования исходного кода, когда объединение коротких строк нежелательно

Нормировать пробелы. Этот параметр применяется для форматирования в стандартном «машинописном стиле», то есть когда слова отделяются од- ним пробелом, а предложения — двумя. Этот режим удобно использовать для удаления выравнивающих пробелов, то есть пробелов, добавленных с целью выравнивания текста по обоим краям

-w ширина

Форматировать текст по указанной ширине. По умолчанию используется ширина 75 символов. Обратите внимание: в действительности fmt форма- тирует строки немного короче, чем указано в аргументе, чтобы обеспечить сбалансированность ширины текста

Особый интерес представляет параметр -p. С его помощью можно форматировать выбранные фрагменты файла, гарантировав, что все отформатированные строки будут начинаться с одной и той же последовательности символов. Многие языки программирования поддерживают комментарии, начинающиеся с символа решет- ки (#), и такие комментарии можно форматировать с помощью этого параметра.

352

Глава 21. Форматирование вывода

Давайте создадим файл, имитирующий исходный текст программы с коммента- риями:

[me@linuxbox ~]$ cat > fmt-code.txt

Этот файл содержит код с комментариями. #

Эта строка - комментарий. #

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

И еще одна. #

Это не комментарий, а строка с кодом. Еще одна строка с кодом. И еще.

Файл примера содержит комментарии, начинающиеся со строки # (символ # и пробел), и строки «кода». Теперь воспользуемся командой fmt, чтобы отформа- тировать комментарии и при этом не затронуть код:

[me@linuxbox ~]$ fmt -w 50 -p ‘# ’ fmt-code.txt

Этот файл содержит код с комментариями. #

Эта строка - комментарий. За ней следует другая #

строка с комментарием. И еще одна. #

Это не комментарий, а строка с кодом. Еще одна строка с кодом. И еще.

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

pr — форматирование текста для печати

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

Продемонстрируем работу pr, форматируя наш файл distros.txt в последователь- ность очень коротких страниц (ниже показаны только первые две страницы):

Инструменты простого форматирования

353

[me@linuxbox ~]$ pr -l 15 -w 65 distros.txt

2016-12-11 18:27 distros.txt Page 1

SUSE 10.2 12/07/2006 Fedora 10 11/25/2008 SUSE 11.0 06/19/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007

2012-16-11 18:27 distros.txt Page 2

SUSE 10.3 10/04/2007 Ubuntu 6.10 10/26/2006 Fedora 7 05/31/2007 Ubuntu 7.10 10/18/2007 Ubuntu 7.04 04/19/2007

В этом примере использовались параметры -l (длина (length) страницы) и -w (ширина (width) страницы), определяющие размеры «страницы» — 65 символов в ширину и 15 строк в длину. pr разбила содержимое файла distros.txt на отдельные страницы, добавив несколько пустых строк сверху и снизу, и создала заголовок по умолчанию, содержащий время последнего изменения файла, имя файла и номер страницы. Программа pr поддерживает множество параметров для управления форматированием страницы, но подробнее о них мы поговорим в главе 22.

printf — форматирование и вывод данных

В отличие от других команд в этой главе, команда printf не используется в кон- вейерной обработке (она не принимает данные со стандартного ввода) и редко применяется непосредственно в командной строке (чаще она используется в сце- нариях). Почему это так важно? Потому что она используется очень широко.

Команда printf (ее название происходит от print formatted (форматированный вы- вод)) первоначально была создана как функция для языка программирования C

354

Глава 21. Форматирование вывода

и впоследствии была реализована во многих других языках, включая язык команд- ной оболочки. Фактически в bash команда printf реализована как встроенная коман да. Она имеет следующий синтаксис:

printf “формат” аргументы

Команда принимает строку с описанием формата, которая затем применяется к списку аргументов. Отформатированный результат передается в стандартный вывод. Ниже приводится простой пример:

[me@linuxbox ~]$ printf “I formatted the string: %s\n” foo I formatted the string: foo

Строка формата может содержать литеральный текст (такой, как I formatted the string:), экранированные последовательности (такие, как \n, символ перевода строки) и последовательности, начинающиеся с символа %, которые называют спецификаторами преобразований (conversion specifications). В примере выше спецификатор преобразования %s используется для форматирования строки foo и включения ее в вывод команды. Еще один пример:

[me@linuxbox ~]$ printf “I formatted ‘%s’ as a string.\n” foo I formatted ‘foo’ as a string.

Как видите, в выводе команды спецификатор преобразования %s замещается стро- кой foo. Преобразование s используется для форматирования строковых данных. Существуют также другие спецификаторы для других видов данных. В табл. 21.4 перечислены наиболее часто используемые типы данных.

Таблица 21.4. Спецификаторы типов данных, наиболее часто используемых в команде printf

Спецификатор

Описание

d

f

o

s

x

X

%

Форматирует число как десятичное целое со знаком

Форматирует и выводит вещественное число

Форматирует целочисленное значение как восьмеричное число

Форматирует строку

Форматирует целочисленное значение как шестнадцатеричное чис- ло, с использованием букв a–f нижнего регистра, где это необходимо

То же, что и x, но использует буквы верхнего регистра

Выводит литеральный символ % (то есть сам спецификатор имеет вид: %%)

Инструменты простого форматирования

355

Продемонстрируем действие каждого спецификатора преобразования на примере строки 380:

[me@linuxbox ~]$ printf “%d, %f, %o, %s, %x, %X\n” 380 380 380 380 380 380 380, 380.000000, 574, 380, 17c, 17C

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

Для настройки вывода спецификатору формата можно передать несколько до- полнительных компонентов. Полный синтаксис спецификатора преобразования выглядит так:

%[флаги][ширина][.точность]спецификатор_преобразования

Для правильной интерпретации дополнительные компоненты, если их не- сколько, должны передаваться в указанном порядке. Все компоненты описаны в табл. 21.5.

Таблица 21.5. Компоненты спецификаторов преобразований в команде printf

Спецификатор

Описание

флаги

Существует пять разных флагов:

• # — использовать альтернативный формат вывода. Действует по-

разному, в зависимости от типа данных. Преобразование o (восьме- ричное число) добавляет в вывод префикс 0 (ноль). Преобразова- ния x и X (шестнадцатеричное число) добавляют в вывод префикс 0x или 0X соответственно;

• 0 (ноль) — добавляет нули в начало вывода. Это означает, что поле будет дополнено ведущими нулями, например: 000380; • - (дефис) — выравнивание по левому краю. По умолчанию

printf выполняет выравнивание по правому краю;

• ’ ’ (пробел) — добавляет ведущий пробел перед положительны-

ми числами;

• + (знак «плюс») — выводит знак перед положительными числа-

ми. По умолчанию printf выводит знаки только перед отрицатель- ными числами

ширина

.точность

Число, определяющее минимальную ширину поля вывода в символах

Определяет число знаков после десятичной запятой при выводе веще- ственных чисел. Для строковых значений точность определяет число выводимых символов

356

Глава 21. Форматирование вывода

В табл. 21.6 перечислены некоторые примеры применения разных форматов.

Таблица 21.6. Примеры применения спецификаторов преобразований команды printf

Аргумент

Формат

Результат

Примечание

380

380

“%d”

“%#x”

380

0x17c

380

“%05d”

00380

380

“%05.5f”

380.00000

380

380

380

“%010.5f”

0380.00000

“%+d”

+380

“%-d”

380

abcdefghijk

“%5s”

abcdefghijk

abcdefghijk

“%.5s”

abcde

Простое форматирование целых чисел

Форматирование целочисленных значений в шестнадцатеричное пред- ставление с использованием альтерна- тивного форматирования

Форматирование целочисленных значений с ведущими нулями и мини- мальным размером поля, равным пяти символам

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

Ширина поля вывода увеличена до 10, вследствие чего появился ведущий нуль

Флаг + требует выводить знак у по- ложительных чисел

Флаг - обеспечивает форматирование с выравниванием по левому краю

Форматирование строки в поле с ука- занной минимальной шириной

Применение компонента точности к строке привело к ее усечению

И еще раз: команда printf в основном используется в сценариях, где применяется для форматирования табличных данных, а не как самостоятельный инструмент командной строки. Тем не менее мы можем использовать ее для решения различ- ных задач форматирования. Во-первых, попробуем вывести несколько полей, раз- делив их символами табуляции:

[me@linuxbox ~]$ printf “%s\t%s\t%s\n” str1 str2 str3 str1 str2 str3

Системы форматирования документов

357

Добавив \t (экранированную последовательность, соответствующую символу та- буляции), мы достигли желаемого эффекта. Затем попробуем вывести несколько чисел в форматированном виде:

[me@linuxbox ~]$ printf “Line: %05d %15.3f Result: %+15d\n” 1071 3.14156295 32589 Line: 01071 3.142 Result: +32589

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

[me@linuxbox ~]$ printf “\n\t\n\t\t%s\n\t \n\t\n\t\t

%s\n\t\n\n” “Page Title” “Page Content”

            Page Title
    
    
            <p>Page Content

Системы форматирования документов

До сих пор мы исследовали простые инструменты форматирования текста. Они хорошо подходят для решения небольших и простых задач, но как быть с более сложными заданиями? Одна из причин большой популярности операционной системы Unix среди технических специалистов и научных работников (кроме мощной поддержки многозадачности и многопользовательского окружения для выполнения любых работ, связанных с разработкой программного обеспечения) состоит в наличии инструментов, которые можно использовать для создания са- мых разных документов, таких как научные и академические публикации. Фак- тически, как описывается в документации GNU, средства подготовки документов положительно сказались на разработке Unix:

Первая версия Unix была создана на машине PDP-7, простаивавшей в Bell Labs. В 1971-м разработчики захотели заполучить PDP-11 для дальнейшей работы над операционной системой. Чтобы оправдать затраты на эту систему, они внесли предложение о реализации системы форматирования документов для патентного бюро в AT&T. Эта первая программа форматирования являлась переделкой про- граммы roff Макиллроя (McIllroy), которую написал Д. Ф. Оссанна (J. F. Ossanna).

В области систем форматирования документов доминируют два основных се- мейства программ: уходящие корнями в оригинальную программу roff, включая

358

Глава 21. Форматирование вывода

nroff и troff, и основанные на системе верстки Дональда Кнута TEX (произно- сится как «тек»). И да, буква «E» в середине имени действительно смещена вниз.

Имя roff произошло от словосочетания «run off» (напечатать), как во фразе: «Я напечатал копию для вас». Программа nroff используется для форматиро- вания документов перед выводом на устройства, использующие моноширинные шрифты, такие как алфавитно-цифровые терминалы и принтеры, действующие подобно пишущим машинкам. На момент появления программы такие устрой- ства составляли подавляющее большинство устройств вывода, подключаемых к компьютерам. Позднее появилась программа troff, форматирующая доку- менты для вывода на наборные устройства, используемые для производства «готовых к тиражированию» макетов. Большинство современных принтеров способны имитировать вывод таких наборных устройств. Семейство roff так- же включает ряд других программ для подготовки фрагментов документов. К их числу относятся eqn (для форматирования математических формул) и tbl (для форматирования таблиц).

Система TEX (в версии, готовой к эксплуатации) впервые появилась в 1989 году и, до определенной степени, заменила troff как инструмент для получения доку- ментов типографского качества. Мы не будем рассматривать систему TEX здесь, во-первых, из-за ее сложности (существуют целые книги, посвященные ей) и, во- вторых, из-за того, что в большинстве современных систем Linux она не устанав- ливается по умолчанию.

СОВЕТ

Если у вас появится желание установить TEX, обратите внимание на пакет texlive, присутствующий в репозиториях большинства дистрибутивов, а также на редактор графического содержимого LyX.

groff

groff — это пакет программ с GNU-реализацией troff. Он также включает сцена- рий, имитирующий работу nroff, и остальные программы семейства roff.

Семейство roff и его потомки использовались для создания форматированных документов способом, довольно чуждым современным пользователям. Большин- ство документов в наше время создается в текстовых процессорах, способных осу- ществлять составление и оформление документов в один шаг. До появления гра- фических текстовых процессоров создание документов обычно происходило в два этапа. Сначала в текстовом редакторе выполнялось составление документа, а за- тем с помощью процессора, такого как troff, осуществлялось его форматирование.

Системы форматирования документов

359

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

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

Давайте ненадолго приостановимся и рассмотрим простую страницу справочно- го руководства (man). Она хранится в каталоге /usr/share/man в виде текстового файла, сжатого с помощью gzip. Если заглянуть на распакованное содержимое, можно увидеть следующее (здесь показана страница справочного руководства из раздела 1 для команды ls):

[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | head

." DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. .TH LS “1” “January 2018” “GNU coreutils 8.28” “User Commands” .SH NAME ls - list directory contents .SH SYNOPSIS .B ls [\fI,OPTION/\fR]… [\fI,FILE/\fR]… .SH DESCRIPTION ." Add any additional description here .PP

Если сравнить содержимое страницы с ее нормальным представлением на экране, можно заметить определенную корреляцию между языком разметки и результа- том его интерпретации:

[me@linuxbox ~]$ man ls | head LS(1) User Commands LS(1)

NAME ls - list directory contents

SYNOPSIS ls [OPTION]… [FILE]…

360

Глава 21. Форматирование вывода

Этот пример интересен тем, что страницы справочного руководства отобража- ются программой groff с использованием макропакета mandoc. В действитель- ности работу команды man можно сымитировать с помощью следующего кон- вейера.

[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc -T ascii | head LS(1) User Commands LS(1)

NAME ls - list directory contents

SYNOPSIS ls [OPTION]… [FILE]…

Здесь использована программа groff с множеством параметров, определяющих макропакет mandoc и драйвер вывода для ASCII. groff может выводить информа- цию в нескольких форматах. Если формат не задан, по умолчанию вывод произ- водится в формате PostScript:

[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc | head %!PS-Adobe-3.0 %%Creator: groff version 1.18.1 %%CreationDate: Thu Feb 2 13:44:37 2009 %%DocumentNeededResources: font Times-Roman %%+ font Times-Bold %%+ font Times-Italic %%DocumentSuppliedResources: procset grops 1.18 1 %%Pages: 4 %%PageOrder: Ascend %%Orientation: Portrait

Мы упоминали PostScript вскользь в предыдущей главе и снова вернемся к нему в следующей главе. PostScript — это язык описания страниц, используемый для вывода страниц на устройства печати с типографским качеством. Вывод коман- ды можно сохранить в файл (здесь предполагается, что вы работаете в графиче- ском окружении рабочего стола и в вашем домашнем каталоге имеется каталог Desktop):

[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc > ~/Desktop/foo.ps

После выполнения этой команды на рабочем столе появится значок файла. После двойного щелчка мышью на этом значке должен запуститься инструмент просмо- тра страниц и отобразить содержимое файла (рис. 21.1).

Системы форматирования документов

361

Рис. 21.1. Отображение содержимого PostScript-файла в окне обозревателя страниц в GNOME

Здесь мы видим прекрасно отформатированную страницу справочного руковод- ства для команды ls! В действительности PostScript-файл можно преобразовать в формат PDF (Portable Document Format — формат переносимых документов), как показано ниже:

[me@linuxbox ~]$ ps2pdf ~/Desktop/foo.ps ~/Desktop/ls.pdf

Программа ps2pdf входит в состав пакета ghostscript, который устанавливается в большинстве систем Linux для поддержки печати.

СОВЕТ

Системы Linux включают множество программ командной строки для преоб- разования файлов из одного формата в другой. Они традиционно получают на- звания в соответствии с соглашением: формат2формат. Попробуйте выполнить команду ls /usr/bin/[[:alpha:]]2[[:alpha:]], чтобы найти все такие про- граммы. Также попробуйте поискать программы с именами форматtoформат.

362

Глава 21. Форматирование вывода

В последнем упражнении с программой groff мы вновь вернемся к нашему старо- му доброму другу — файлу distros.txt. На этот раз мы воспользуемся программой tbl, которая применяется для форматирования таблиц, чтобы сформировать наш список дистрибутивов Linux. В этом упражнении мы задействуем сценарий для sed, созданный ранее, и с его помощью добавим разметку в поток текста, который затем передадим программе groff.

Сначала изменим сценарий для sed, добавив в него вызовы, необходимые про- грамме tbl. Откройте сценарий distros.sed в текстовом редакторе и измените его, как показано ниже:

Сценарий для sed, создающий отчет о дистрибутивах Linux #

1 i
.TS
center box;
cb s s
cb cb cb
l n c.
Linux Distributions Report
=
Name Version Released
_ s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/ $ a
.TE

Обратите внимание, что для корректной работы сценария слова Name Version Released должны разделяться символами табуляции, а не пробелами. Сохраним получившийся файл с именем distros-tbl.sed. Программа tbl использует запросы .TS и .TE как метки начала и конца таблицы. Строки, следующие за запросом .TS, определяют глобальные свойства таблицы, в нашем случае выравнивание по цен- тру страницы и окружение внешней рамкой. Остальные строки описывают фор- матирование строк таблицы. Если теперь снова запустить наш конвейер составле- ния отчета с новым сценарием для sed, мы получим следующее:

[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-tbl.sed | groff -t -T ascii +——————————+ | Linux Distributions Report | +——————————+ | Name Version Released | +——————————+ |Fedora 5 2006-03-20 | |Fedora 6 2006-10-24 |

Системы форматирования документов

363

             |Fedora     7       2007-05-31 |
             |Fedora     8       2007-11-08 |
             |Fedora     9       2008-05-13 |
             |Fedora    10       2008-11-25 |
             |SUSE      10.1     2006-05-11 |
             |SUSE      10.2     2006-12-07 |
             |SUSE      10.3     2007-10-04 |
             |SUSE      11.0     2008-06-19 |
             |Ubuntu     6.06    2006-06-01 |
             |Ubuntu     6.10    2006-10-26 |
             |Ubuntu     7.04    2007-04-19 |
             |Ubuntu     7.10    2007-10-18 |
             |Ubuntu     8.04    2008-04-24 |
             |Ubuntu     8.10    2008-10-30 |
             +------------------------------+

Параметр -t требует от groff обработать поток текста с помощью tbl. Аналогич- но, параметр -T требует вывести данные в формате ASCII вместо используемого по умолчанию PostScript.

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

[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-tbl.sed | groff -t

~/Desktop/foo.ps

Рис. 21.2. Изображение законченной таблицы

364

Глава 21. Форматирование вывода

Заключение

Учитывая, что текст занимает главенствующее положение в Unix-подобных опе- рационных системах, было бы оправданно ожидать наличия большого числа ин- струментов для работы с текстом и его форматирования. И, как мы увидели, эти ожидания небеспочвенны! Простые инструменты форматирования, такие как fmt и pr, находят широкое применение в сценариях, производящих короткие доку- менты, тогда как groff (с сопутствующими программами) можно использовать для создания книг. Возможно, вам никогда не придется использовать инструмен- ты командной строки для написания технических статей (хотя многие делают это!), но знать о такой возможности вам не помешает.

22 Печать

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

Далее мы обсудим следующие команды:

pr — преобразует текстовые файлы для печати;

lpr — печатает файлы;

a2ps — форматирует файлы для печати на принтере с поддержкой PostScript;

lpstat — выводит информацию о состоянии принтера;

lpq — выводит информацию о состоянии очереди печати;

lprm — отменяет задания печати.

Краткая история поддержки печати

Для полного понимания особенностей печати в Unix-подобных операционных си- стемах сначала познакомимся с ее историей. Поддержка печати в Unix-подобных системах восходит к временам зарождения самой операционной системы. В те времена принтеры и способы их использования существенно отличались от со- временных.

366

Глава 22. Печать

Печать в ночное время

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

Когда принтеры были дорогими и централизованными, что было обычно в ранний период развития Unix, нормальной практикой считалось совместное использова- ние принтера несколькими пользователями. Чтобы идентифицировать принад- лежность задания печати определенному пользователю, в начале каждого задания печаталась титульная страница с именем пользователя. Персонал, обслуживаю- щий ЭВМ, извлекал корзину с напечатанными за день документами и доставлял их конкретным пользователям.

Символьные принтеры

Технология печати в 80-х существенно отличалась от современной двумя аспекта- ми. Во-первых, практически все принтеры того периода были ударного действия. В этих принтерах использовался механический узел, ударявший по красящей ленте, находящейся между узлом и бумагой и таким способом формировавший отпечатки символов на бумаге. В то время были наиболее распространены два вида печатающих механизмов: лепестковый литероноситель («ромашка») и мат- ричная печатающая головка.

Во-вторых, что особенно важно, ранние принтеры использовали фиксирован- ный набор символов, встроенный непосредственно в устройство. Например, принтер с «ромашкой» мог печатать только символы, присутствующие на ле- пестках «ромашки». В результате такие принтеры действовали как высокоско- ростные печатающие машинки. Как большинство печатающих машинок, они печатали моноширинными шрифтами (шрифтами с фиксированной шириной символов). Это означает, что все символы имели одинаковую ширину. Печать символов выполнялась в фиксированных позициях на бумаге, и область печа- ти имела фиксированное число знакомест. Большинство принтеров выводило 10 символов на дюйм (Characters Per Inch, CPI) по горизонтали и 6 строк на дюйм (Lines Per Inch, LPI) по вертикали. В соответствии с этой схемой на стра- нице формата US-letter помещалось 85 символов в ширину и 66 строк в высоту. С учетом отступов слева и справа считалось, что максимальная ширина печати составляет 80 символов в строке. Это объясняет, почему дисплеи терминалов (и наших эмуляторов терминалов) обычно имеют ширину 80 символов. Таким способом обеспечивался режим «что вижу, то и получаю» (What You See Is What

Краткая история поддержки печати

367

You Get, WYSIWYG) отображения текста перед печатью с применением моно- ширинного шрифта.

Данные, посылаемые на принтер с ударным механизмом, — это простой поток байтов, соответствующих печатаемым символам. Например, чтобы напечатать a, посылался код символа 97 в наборе ASCII. Кроме того, в начале набора ASCII были выделены коды для управления кареткой принтера и бумагой, например, для возврата каретки в исходное положение, перевода строки, перевода формата (страницы) и др. С помощью управляющих кодов можно было имитировать неко- торые эффекты, такие как жирная печать, когда принтеру посылался печатаемый символ, символ забоя (backspace) и снова тот же печатаемый символ, благодаря чему на бумаге получалось более темное изображение. В этом легко убедиться, если с помощью nroff отобразить страницу справочного руководства и исследо- вать вывод с помощью команды cat -A:

[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | nroff -man | cat -A | head LS(1) User Commands LS(1) $ $ $ N^HNA^HAM^HME^HE$ ls - list directory contents$ $ S^HSY^HYN^HNO^HOP^HPS^HSI^HIS^HS$ l^Hls^Hs [^HO^HP_^HT_^HI_^HO_^HN]… [^HF^HI_^HL_^HE]…$

Символы ^H (CTRL-H) — это символы забоя (backspace), используемые для созда- ния эффекта жирной печати. Аналогично для получения эффекта подчеркивания можно использовать последовательность из символов забоя/подчеркивания.

Графические принтеры

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

Однако переход от символьной системы печати к графической вызвал появле- ние огромных технических проблем. И вот почему. Число байтов, которое нужно

368

Глава 22. Печать

послать символьному принтеру для заполнения страницы, можно было подсчи- тать с помощью простой формулы (если предположить, что на странице умещает- ся 60 строк, по 80 символов в каждой):

60 × 80 = 4800 байт.

Для сравнения: лазерному принтеру с качеством печати 300 точек на дюйм (Dot Per Inch, DPI) для покрытия страницы размером 8 × 10 дюймов (203 × 254 мм) нужно послать

(8 × 300) × (10 × 300) × 8 = 900 000 байт.

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

Таким решением стало изобретение языка описания страниц. Язык описания стра- ниц (Page Description Language, PDL) — это язык программирования, описываю- щий содержимое страницы. Программы на этом языке как бы говорили: «перей- ти в эту позицию, нарисовать символ a шрифтом Helvetica с кеглем 10 пунктов, перейти в эту позицию…», пока вся страница не была описана. Первым основным языком PDL стал PostScript, разработанный в Adobe Systems, он все еще широко используется в наше время. Язык PostScript — это полноценный язык программи- рования, ориентированный на книгопечатание и создание разного вида графиче- ских изображений. Он включает поддержку 35 стандартных высококачественных шрифтов плюс может принимать определения дополнительных шрифтов во вре- мя выполнения. На первом этапе поддержка PostScript встраивалась непосред- ственно в принтеры. Это решало проблему передачи данных. Даже притом, что типичная программа на PostScript по объему превышала простой поток байтов для символьных принтеров, ее размер был намного меньше числа байтов, необхо- димых для представления целой страницы.

Принтер с поддержкой PostScript принимал на входе программу на PostScript. Принтер имел собственный процессор и память (нередко принтеры имели боль- шую вычислительную мощность, чем компьютеры, к которым они подключались) и выполнял специальную программу, называвшуюся интерпретатором PostScript, которая читала входящую программу на PostScript и отображала результат во внутреннюю память принтера, таким образом формируя шаблон из битов (точек) для вывода на бумагу. Такой процесс отображения чего-то в большой битовый ша- блон (его называют bitmap — растр) в общем случае называют процессором рас- тровых изображений (Raster Image Processor, RIP).

Спустя годы компьютеры и сети стали намного быстрее. Это позволило переме- стить RIP с принтера в компьютер, что, в свою очередь, позволило удешевить вы- сококачественные принтеры.

Подготовка файлов к печати

369

Многие современные принтеры все еще способны принимать потоки символов, но большинство уже не поддерживают эту возможность. Они полагаются на RIP компьютера и ожидают получить поток битов для печати их в виде точек. Суще- ствуют также современные PostScript-принтеры.

Печать в Linux

Современные системы Linux используют два комплекта программного обеспече- ния для печати. Первый, CUPS (Common Unix Printing System — общая система печати для Unix), включает драйверы печати и средства управления заданиями; второй, Ghostscript, интерпретатор PostScript, действует как RIP.

Пакет CUPS осуществляет управление принтерами, создавая очереди печа- ти и управляя ими. Как говорилось выше, в краткой исторической справке, поддержка печати в Unix первоначально была ориентирована на управление централизованным принтером, совместно используемым несколькими поль- зователями. Поскольку принтеры — довольно медленные устройства по своей природе в сравнении с компьютерами, которые поставляют им данные, систе- мы печати должны обладать возможностью планирования множества заданий печати и их организации. Система CUPS также способна распознавать данные разных типов (в разумных пределах) и преобразовывать файлы в вид, пригод- ный для печати.

Подготовка файлов к печати

Так как мы — пользователи командной строки, наибольший интерес для нас пред- ставляет печать текста, хотя при этом сохраняется возможность печатать данные других форматов.

pr — преобразование текстовых файлов для печати

Мы уже касались программы pr в предыдущей главе. А теперь исследуем все бо- гатство ее параметров, используемых при печати. В краткой исторической справке развития технологий печати рассказывалось, что символьные принтеры исполь- зовали мноноширинные шрифты, что обеспечивало фиксированное число симво- лов в строке и строк на странице. Программа pr используется для выравнивания текста в соответствии с заданным размером страницы, с учетом дополнительных заголовков и полей на странице. Наиболее часто используемые параметры пере- числены в табл. 22.1.

370

Глава 22. Печать

Таблица 22.1. Наиболее часто используемые параметры команды pr

Параметр

Описание

+первая[:последняя]

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

-колонок

Вывести содержимое страницы в указанное число колонок

-a

-d

-D формат

-f

-h заголовок

-l длина

-n

-o отступ

-w ширина

По умолчанию, когда задан вывод в несколько колонок, ко- лонки организованы по вертикали. Параметр -a (across — по- перек) позволяет организовать колонки по горизонтали

Вывести с двойным интервалом

Формат вывода даты в заголовке страницы. Описание строки формата можно найти в странице справочного руководства (man) для команды date

Использовать символ перевода формата вместо возврата ка- ретки для отделения страниц друг от друга

Текст для вывода в центре заголовка страницы вместо имени файла

Длина страницы. По умолчанию длина устанавливается равной 66 строкам (соответствует формату US-letter с плотностью печати 6 строк на дюйм)

Нумеровать строки

Создать левое поле, выполнив отступ указанного размера (в символах)

Ширина страницы в символах. По умолчанию ширина устанав- ливается равной 72 символам

Программа pr часто используется в конвейерах в роли фильтра. Следующий при- мер создает список содержимого каталога /usr/bin и с помощью pr выводит его в три колонки с разбивкой на страницы:

[me@linuxbox ~]$ ls /usr/bin | pr -3 -w 65 | head

2012-02-18 14:00 Page 1 [ apturl bsd-write 411toppm ar bsh a2p arecord btcflash

Отправка задания печати на принтер

371

a2ps arecordmidi bug-buddy a2ps-lpr-wrapper ark buildhash

Отправка задания печати на принтер

Пакет программ печати CUPS поддерживает два метода печати, исторически ис- пользуемых в Unix-подобных системах. Первый метод, с названием Berkeley, или LPD (используется в BSD-версиях Unix), основан на использовании программы lpr; второй метод, с названием SysV (используется в версиях Unix System V), ос- нован на использовании программы lp. Обе программы работают примерно оди- наково. Выбор той или иной зависит от личных предпочтений.

lpr — печать файлов (в стиле Berkeley)

Программа lpr применяется для отправки файлов на принтер. Она также может использоваться в конвейерах, так как способна принимать исходные данные со стандартного ввода. Например, напечатать предыдущий результат форматирова- ния содержимого каталога в несколько колонок можно было бы так:

[me@linuxbox ~]$ ls /usr/bin | pr -3 | lpr

В этом случае отчет будет выведен на принтер, используемый системой по умол- чанию. Для вывода файла на другой принтер используйте параметр -P:

lpr -P имя_принтера

где аргумент имя_принтера — имя требуемого принтера. Получить список принте- ров, известных системе, поможет команда

[me@linuxbox ~]$ lpstat -a

СОВЕТ

Многие дистрибутивы Linux позволяют определять фиктивные «принтеры», которые выводят результат в файл в формате PDF, а не на физический прин- тер. Это очень удобно для экспериментов с командами печати. Запустите свою программу настройки принтеров и посмотрите, поддерживает ли она такую воз- можность. Чтобы включить ее, в некоторых дистрибутивах может понадобиться установить дополнительные пакеты (такие, как cups-pdf).

В табл. 22.2 перечислены некоторые часто используемые параметры команды lpr.

372

Глава 22. Печать

Таблица 22.2. Наиболее часто используемые параметры команды lpr

Параметр

Описание

-# число

Число копий

-p

Вывести на каждой странице заголовок с датой, временем, именем за- дания и номером страницы. Этот так называемый параметр структурной печати («pretty print») можно использовать для печати текстовых файлов

-P принтер

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

-r

Удалить файлы после печати. Может пригодиться при использовании про- грамм, создающих временные файлы для печати

lp — печать файлов (в стиле System V)

Подобно lpr, программа lp принимает файлы или данные со стандартного ввода. Отличается от lpr поддержкой иного (немного более сложного) набора параме- тров. Наиболее часто используемые параметры перечислены в табл. 22.3.

Таблица 22.3. Наиболее часто используемые параметры команды lp

Параметр

-d принтер

Описание

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

-n число

Число копий

-o landscape

Вывести в альбомной ориентации

-o fitplot

-o scaling=число

-o cpi=число

-o lpi=число

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

Масштабировать файл с указанным коэффициентом. Зна- чение 100 соответствует полному заполнению страницы. Значения меньше 100 уменьшают размеры области печати, тогда как значения больше 100 — увеличивают, вследствие чего файл печатается на нескольких страницах

Установить плотность печати символов на дюйм (CPI) как указанное число символов на дюйм. По умолчанию это значе- ние устанавливается равным 10

Установить плотность печати строк на дюйм (LPI) как ука- занное число строк на дюйм. По умолчанию это значение устанавливается равным 6

Отправка задания печати на принтер

373

Параметр

Описание

-o page-bottom=пунктов -o page-left=пунктов -o page-right=пунктов -o page-top=пунктов

-P страницы

Размеры полей. Значения выражаются в пунктах, единице измерения, используемой в типографском деле. Один дюйм соответствует 72 пунктам

Список страниц для печати. Список может иметь вид пере- числения номеров страниц через запятую и/или диапазонов, например: 1,3,5,7-10

Вернемся к нашему списку содержимого каталога, но на этот раз выведем его с плотностью печати 12 CPI и 8 LPI и с левым полем размером полдюйма. Обра- тите внимание, что нам пришлось откорректировать параметры pr, чтобы учесть новые размеры страницы:

[me@linuxbox ~]$ ls /usr/bin | pr -4 -w 90 -l 88 | lp -o page-left=36 -o cpi=12 -o lpi=8

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

Еще одна возможность: a2ps

Программа a2ps довольно интересна. Как можно догадаться по ее имени, это про- грамма преобразования одного формата в другой, но не только. Первоначально ее имя означало ASCII to PostScript (из ASCII в PostScript) и она использовалась для подготовки текстовых файлов к печати на принтерах с поддержкой PostScript. С го- дами, однако, возможности программы росли, и теперь ее имя означает Anything to PostScript (все что угодно — в PostScript). Несмотря на то что имя программы гово- рит, что это — программа преобразования одного формата в другой, в действительно- сти она является программой печати. Она выводит результаты своей работы в свой вывод по умолчанию — в системный принтер, а не в стандартный вывод. По умол- чанию программа действует как программа «структурной печати», улучшая формат вывода. Мы можем с ее помощью создать PostScript-файл на своем рабочем столе:

[me@linuxbox ~]$ ls /usr/bin | pr -3 -t | a2ps -o ~/Desktop/ls.ps -L 66 [stdin (plain): 11 pages on 6 sheets] [Total: 11 pages on 6 sheets] saved into the file `/home/me/Desktop/ls.ps’

Здесь мы обработали поток с помощью программы pr, передав ей параметр -t (что- бы опустить верхние и нижние колонтитулы), и передали результат программе

374

Глава 22. Печать

a2ps, указав ей файл для вывода (параметр -o) и плотность печати 66 строк на странице (параметр -L), чтобы разбить вывод pr на страницы. Если открыть полу- чившийся файл с помощью соответствующего средства просмотра, можно уви- деть, что он выглядит, как показано на рис. 22.1.

Рис. 22.1. Результат работы a2ps

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

a2ps имеет множество параметров, они перечислены в табл. 22.4.

Таблица 22.4. Параметры команды a2ps

Параметр

Описание

–center-title текст

Текст для заголовка страницы в центре

–columns число

Число колонок для вывода страниц. По умолчанию 2

–footer текст

Текст для нижнего колонтитула

–guess

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

Отправка задания печати на принтер

375

Параметр

Описание

–left-footer текст

Текст для нижнего колонтитула слева

–left-title текст

Текст для заголовка страницы слева

–line-numbers=интервал

Выводить номера строк через заданный интервал

–list=defaults

Вывести настройки по умолчанию

–pages диапазон

Печатать страницы из указанного диапазона

–right-footer текст

Текст для нижнего колонтитула справа

–right-title текст

Текст для заголовка страницы справа

–rows число

Разместить страницы в указанное число рядов. По умол- чанию 1

-B

-b текст

-f размер

-l число

-L число

-M имя

-n число

-o файл

-P принтер

-R

-r

-T число

-u текст

Не выводить заголовки страниц

Текст заголовка страницы

Использовать шрифт указанного размера

Число символов в строке. Этот параметр и параметр -L (ниже) можно использовать, чтобы правильно разместить на странице файлы, разбитые на страницы с помощью других программ, таких как pr

Число строк на странице

Имя формата бумаги, например A4

Вывести указанное число копий каждой страницы

Вывести результат в указанный файл. Если в качестве имени файла указан - (дефис), используется стандартный вывод

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

Вывести в книжной ориентации

Вывести в альбомной ориентации

Установить табулостопы через каждое указанное число символов

Текст для нижнего слоя на странице («водяной знак»)

Это далеко не полный список. В действительности программа a2ps имеет намного больше параметров.

376

Глава 22. Печать

ПРИМЕЧАНИЕ

Кроме того, существует еще одна программа форматирования, которую можно использовать для преобразования текста в формат PostScript. Она называется enscript и способна выполнять почти те же виды форматирования и печати, что и a2ps, но, в отличие от последней, принимает только текстовые данные.

Наблюдение за заданиями печати и управление ими

Поскольку система печати в Unix изначально проектировалась для обработки заданий печати от нескольких пользователей, соответственно и система CUPS проектировалась исходя из той же предпосылки. Для каждого принтера созда- ется своя очередь печати, в которой задания хранятся, пока не будут переданы принтеру. В составе CUPS имеется несколько программ командной строки для управления состоянием принтеров и очередей печати. Подобно программам lpr и lp, эти управляющие программы создавались после появления соответствую- щих программ из систем Berkeley и System V.

lpstat — вывод информации о состоянии принтера

Программу lpstat удобно использовать для определения имен и доступности принтеров в системе. Например, если к системе подключены два принтера — фи- зический (с именем printer) и виртуальный, для вывода в файлы PDF (с именем PDF), — их состояние можно проверить так:

[me@linuxbox ~]$ lpstat -a PDF accepting requests since Mon 05 Dec 2011 03:05:59 PM EST printer accepting requests since Tue 21 Feb 2012 08:43:22 AM EST

Кроме того, с ее помощью можно получить более подробное описание конфигура- ции системы печати:

[me@linuxbox ~]$ lpstat -s system default destination: printer device for PDF: cups-pdf:/ device for printer: ipp://print-server:631/printers/printer

В этом примере видно, что имя printer соответствует системному принтеру по умол- чанию и что это сетевой принтер, для взаимодействий с которым используется

Наблюдение за заданиями печати и управление ими

377

протокол печати через Интернет (Internet Printing Protocol, ipp://), физически подключенный к системе с именем print-server.

В табл. 22.5 перечислены некоторые часто используемые параметры команды lpstat.

Таблица 22.5. Наиболее часто используемые параметры команды lpstat

Параметр

Описание

-a [принтер…]

Вывести состояние очереди печати для указанного принтера. Обра- тите внимание, что результат отражает состояние очереди печати для данного принтера и ее способность принимать задания, а не со- стояние физического принтера. Если принтер не указан, выводится информация обо всех очередях печати

-d

Вывести имя системного принтера по умолчанию

-p [принтер…]

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

-r

-s

-t

Вывести состояние сервера печати

Вывести сводную информацию о состоянии

Вывести полный отчет о состоянии

lpq — вывод информации о состоянии очереди печати

Программа lpq используется для получения информации о состоянии очереди печати. С ее помощью можно увидеть состояние очереди и список заданий в ней. Ниже приводится пример вывода информации о состоянии пустой очереди для системного принтера по умолчанию с именем printer:

[me@linuxbox ~]$ lpq printer is ready no entries

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

[me@linuxbox ~]$ ls *.txt | pr -3 | lp request id is printer-603 (1 file(s)) [me@linuxbox ~]$ lpq

378

Глава 22. Печать

printer is ready and printing Rank Owner Job File(s) Total Size active me 603 (stdin) 1024 bytes

lprm и cancel — отмена заданий печати

В составе CUPS имеется две программы для завершения заданий печати и удале- ния их из очереди. Одна программа — в стиле Berkeley (lprm), а другая — в стиле System V (cancel). Они несколько отличаются поддерживаемыми параметрами, но, по сути, выполняют одну и ту же операцию. Если использовать пример с за- данием печати, рассматриваемый выше, мы могли бы остановить выполнение за- дания и удалить его:

[me@linuxbox ~]$ cancel 603 [me@linuxbox ~]$ lpq printer is ready no entries

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

Заключение

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

23 Компиляция программ

В этой главе мы посмотрим, как собирать программы, компилируя их исходный код. Доступность исходного кода — основное преимущество Linux, оно обеспечи- вает само существование этой системы. Вся экосистема разработки в Linux опира- ется на свободный обмен информацией между разработчиками. Для многих рядо- вых пользователей компиляция — утраченное искусство. Когда-то эта процедура была вполне обыденным делом, но в настоящее время создатели дистрибутивов поддерживают огромные репозитории с предварительно скомпилированными файлами, готовыми для загрузки и использования. На момент написания этих строк в репозитории дистрибутива Debian (одном из крупнейших) насчитывалось почти 68 000 пакетов.

Но зачем может понадобиться компилировать исходный код? Могу назвать две основные причины:

 Доступность. Несмотря на большое число предварительно скомпилированных пакетов в репозиториях дистрибутивов некоторые дистрибутивы могут вклю- чать не все необходимые приложения. В этом случае остается только один спо- соб установить требуемую программу: скомпилировать ее из исходных кодов.

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

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

380

Глава 23. Компиляция программ

В этой главе будет представлена одна новая команда:

make — утилита сопровождения программ.

Что такое компиляция?

Выражаясь простым языком, компиляция — это процесс трансляции исходного кода (текста программы, описывающего ее действия и написанного программи- стом) на низкоуровневый язык, понятный процессору компьютера.

Процессор компьютера (Computer Processor Unit, CPU) работает на очень низ- ком уровне, выполняя программы на языке, который называют машинным. Это числовой код, описывающий элементарные операции, такие как «сложить эти два байта», «сослаться на эту ячейку в памяти» или «скопировать этот байт». Каждая из этих инструкций выражается в двоичной форме (нулями и единицами). Самые первые программы писались на числовом коде, поэтому программисты, писавшие такой код, как поговаривают, много курили, пили кофе литрами и носили очки с толстенными линзами.

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

Затем появились высокоуровневые языки программирования. Они называются так потому, что позволяют программисту меньше думать об особенностях работы про- цессора и больше — о решении задачи, стоящей перед ним. К числу этих первых языков (разработанных в течение 1950-х) относятся: FORTRAN (создавался для решения научных и технических задач) и СOBOL (для решения экономических задач). Оба продолжают ограниченно использоваться и по сию пору.

Несмотря на большое число популярных языков программирования, господству- ющие позиции занимают только два из них. Многие современные системы и про- граммы написаны на C или на C++. В примерах ниже мы будем компилировать программы на языке C.

Программы на языках высокого уровня преобразуются в инструкции на машин- ном языке с помощью другой программы — компилятора. Некоторые компиля- торы транслируют высокоуровневые инструкции на язык ассемблера, а затем

Что такое компиляция?

381

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

Компиляции, как правило, сопутствует процесс компоновки. Программы часто вы- полняют множество типовых операций. Возьмем для примера операцию откры- тия файла. Многие программы выполняют ее, но было бы слишком расточительно в каждой программе реализовывать свою процедуру открытия файлов. Предпо- чтительнее иметь единый программный код, знающий, как открывать файлы, и дать всем программам возможность использовать его. Поддержка решения ти- повых задач осуществляется с помощью библиотек. Они содержат множество под- программ, которые решают типовые задачи и могут использоваться множеством программ. Если заглянуть в каталоги /lib и /usr/lib, мы обнаружим подобные библиотеки. Для формирования связей между результатом работы компилятора и библиотеками, необходимыми компилируемой программе, используется про- грамма-компоновщик (linker, ее также называют редактором связей). Окончатель- ным результатом этого процесса является выполняемый файл программы, готовый к использованию.

Все ли программы компилируются?

Нет. Как мы уже видели, некоторые программы, такие как сценарии на языке командной оболочки, не требуют компиляции и выполняются непосредственно. Они написаны на языках, которые называют языками сценариев, или интерпре- тируемыми языками. К числу этих языков, популярность которых только растет в последние годы, относятся Perl, Python, PHP, Ruby и многие другие.

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

Но почему тогда интерпретирующие языки так популярны? Для многих рутинных задач они оказываются «достаточно быстрыми», но самое большое их достоин- ство в простоте и скорости разработки интерпретируемых программ в сравнении с компилируемыми. Разработка программ — это обычно циклический процесс, включающий три этапа: создание исходного кода, компиляцию и тестирование. С увеличением программы в размерах этап компиляции в упомянутом цикле

382

Глава 23. Компиляция программ

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

Компиляция программ на C

Давайте что-нибудь скомпилируем. Для этого нам понадобятся некоторые ин- струменты, такие как компилятор, компоновщик и утилита make. Практически во всех системах Linux используется один и тот же компилятор языка C с именем gcc (GNU C Compiler), первоначально написанный Ричардом Столлманом. Многие дистрибутивы не устанавливают gcc по умолчанию. Проверить его присутствие в системе можно так:

[me@linuxbox ~]$ which gcc /usr/bin/gcc

Результат свидетельствует о присутствии компилятора.

СОВЕТ

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

Получение исходного кода

В нашем упражнении мы скомпилируем программу с названием diction из про- екта GNU. Эта маленькая удобная программка проверяет качество и стиль содер- жимого текстовых файлов. А поскольку она невелика, она легко компилируется.

Следуя соглашениям, мы сначала создадим каталог src для исходного кода и за- тем загрузим в него исходный код с помощью команды ftp:

[me@linuxbox ~]$ mkdir src [me@linuxbox ~]$ cd src [me@linuxbox src]$ ftp ftp.gnu.org Connected to ftp.gnu.org. 220 GNU FTP server ready. Name (ftp.gnu.org:me): anonymous 230 Login successful.

Компиляция программ на C

383

Remote system type is UNIX. Using binary mode to transfer files. ftp> cd gnu/diction 250 Directory successfully changed. ftp> ls 200 PORT command successful. Consider using PASV. 150 Here comes the directory listing. -rw-r–r– 1 1003 65534 68940 Aug 28 1998 diction-0.7.tar.gz -rw-r–r– 1 1003 65534 90957 Mar 04 2002 diction-1.02.tar.gz -rw-r–r– 1 1003 65534 141062 Sep 17 2007 diction-1.11.tar.gz 226 Directory send OK. ftp> get diction-1.11.tar.gz local: diction-1.11.tar.gz remote: diction-1.11.tar.gz 200 PORT command successful. Consider using PASV. 150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062 bytes). 226 File send OK. 141062 bytes received in 0.16 secs (847.4 kB/s) ftp> bye 221 Goodbye. [me@linuxbox src]$ ls diction-1.11.tar.gz

В предыдущем примере мы использовали традиционный метод с применением ftp, но есть и другие способы загрузки исходного кода. Например, проект GNU поддерживает также загрузку по протоколу HTTPS. То есть мы могли бы загру- зить исходный код diction с помощью программы wget:

[me@linuxbox src]$ wget https://ftp.gnu.org/gnu/diction/diction-1.11.tar.gz –2018-07-25 09:42:20– https://ftp.gnu.org/gnu/diction/diction-1.11.tar.gz Resolving ftp.gnu.org (ftp.gnu.org)… 208.118.235.20, 2001:4830:134:3::b Connecting to ftp.gnu.org (ftp.gnu.org)|208.118.235.20|:443… connected. HTTP request sent, awaiting response… 200 OK Length: 141062 (138K) [application/x-gzip] Saving to: ‘diction-1.11.tar.gz’ diction-1.11.tar.gz 100%[===================>] 137.76K –.-KB/s in 0.09s

2018-07-25 09:42:20 (1.43 MB/s) - ‘diction-1.11.tar.gz.1’ saved [141062/141062]

ПРИМЕЧАНИЕ

Поскольку мы сами управляем процессом компиляции исходного кода, поместим его в каталог ~/src. Исходный код, устанавливаемый дистрибутивом, помеща- ется в каталог /usr/src, а исходный код, предназначенный для использования множеством пользователей, обычно устанавливается в /usr/local/src.

384

Глава 23. Компиляция программ

Исходный код обычно распространяется в виде сжатого tar-файла. Иногда назы- ваемые тарболлами (tarball), эти файлы содержат дерево исходных текстов, или иерархию каталогов и файлов, составляющих исходный код. Подключившись к FTP-сайту, мы получили список доступных tar-файлов и выбрали для загруз- ки самую свежую версию. При помощи команды get программы ftp скопировали файл с сервера FTP на локальную машину.

После загрузки tar-файла его нужно распаковать. Делается это с помощью про- граммы tar:

[me@linuxbox src]$ tar xzf diction-1.11.tar.gz [me@linuxbox src]$ ls diction-1.11 diction-1.11.tar.gz

СОВЕТ

Программа diction, подобно всем программам из проекта GNU, следует опреде- ленным стандартам упаковки исходного кода. Большая часть других исходных кодов, доступных в экосистеме Linux, также следует этому стандарту. Одним из элементов стандарта является создание каталога с деревом исходных текстов и именем project-x.xx после распаковывания tar-файла, то есть с именем, со- держащим имя проекта и номер версии. Такая схема упрощает установку не- скольких версий одной и той же программы. Однако перед распаковыванием хорошо бы исследовать организацию дерева. При распаковывании некоторых проектов каталог не создается, а файлы помещаются непосредственно в текущий каталог, что может вызвать неразбериху и путаницу в хорошо организованном каталоге src. Чтобы избежать этого, пользуйтесь следующей командой для ис- следования содержимого tar-файла:

tar tzvf tarfile | head

Исследование дерева исходных текстов

В результате распаковывания tar-файла был создан новый каталог diction-1.11. Этот каталог содержит дерево исходных текстов. Давайте заглянем внутрь:

[me@linuxbox src]$ cd diction-1.11 [me@linuxbox diction-1.11]$ ls config.guess diction.c getopt.c nl config.h.in diction.pot getopt.h nl.po config.sub diction.spec getopt_int.h README configure diction.spec.in INSTALL sentence.c configure.in diction.texi.in install-sh sentence.h

Компиляция программ на C

385

COPYING en Makefile.in style.1.in de en_GB misc.c style.c de.po en_GB.po misc.h test diction.1.in getopt1.c NEWS

Здесь мы видим множество файлов. Программы, принадлежащие проекту GNU, а также многие другие поставляются вместе с файлами документации README, INSTALL, NEWS и COPYING. В них содержится описание программы, информа- ция о порядке сборки и установки и условия лицензионного соглашения. Я реко- мендую всегда внимательно прочитывать файлы README и INSTALL перед сбор- кой программы.

Другими интересными файлами в этом каталоге являются файлы с расширения- ми .c и .h:

[me@linuxbox diction-1.11]$ ls *.c diction.c getopt1.c getopt.c misc.c sentence.c style.c [me@linuxbox diction-1.11]$ ls *.h getopt.h getopt_int.h misc.h sentence.h

Файлы с расширением .c содержат две программы на C, входящие в состав пакета (style и diction), разбитые на несколько модулей. Большие программы часто раз- бивают на более мелкие, более простые в сопровождении фрагменты. Файлы с ис- ходным кодом содержат простой текст, и их можно исследовать с помощью less:

[me@linuxbox diction-1.11]$ less diction.c

Файлы с расширением .h известны как заголовочные файлы. Они тоже содержат простой текст — описание подпрограмм, подключаемых из файла с исходным ко- дом или библиотеки. Чтобы компилятор смог связать модули, он должен иметь описание всех модулей, составляющих единую программу. Ближе к началу в фай- ле diction.c имеется следующая строка:

#include “getopt.h”

Она требует от компилятора прочитать фал getopt.h, прежде чем продолжать чтение исходного кода в diction.c, чтобы «узнать», что имеется в файле getopt.c. Файл getopt.c содержит код подпрограмм, используемых обеими программами, style и diction.

Инструкции подключения файла getopt.h предшествует еще несколько инструк- ций include:

#include #include #include #include #include

386

Глава 23. Компиляция программ

Они также ссылаются на заголовочные файлы, но эти файлы хранятся за предела- ми дерева исходных текстов. Они должны поставляться системой для поддержки компиляции программ. Эти файлы можно найти в каталоге /usr/include:

[me@linuxbox diction-1.11]$ ls /usr/include

Заголовочные файлы помещаются в этот каталог в процессе установки компиля- тора.

Сборка программ

В большинстве случаев сборка программы заключается в выполнении последова- тельности из двух команд:

./configure make

Программа configure — это сценарий командной оболочки, поставляемый вместе с деревом исходных текстов. Его задача — проанализировать окружение сборки. Большинство исходного кода поддерживает переносимость. То есть такой исходный код спроектирован так, что допускает сборку в разных Unix-подобных системах. Но для этого во время сборки в исходный код может потребоваться внести небольшие изменения, учитывающие различия между системами. Программа configure также проверяет наличие необходимых внешних инструментов и компонентов.

Давайте запустим configure. Так как эта программа находится не там, где команд- ная оболочка обычно ищет выполняемые файлы, нужно явно сообщить ей место- положение программы, добавив в команду префикс ./. Он указывает, что про- грамма находится в текущем рабочем каталоге:

[me@linuxbox diction-1.11]$ ./configure

В процессе проверки и настройки сборки configure выведет множество сообще- ний. Последние строки ее вывода должны выглядеть примерно так:

checking libintl.h presence… yes checking for libintl.h… yes checking for library containing gettext… none required configure: creating ./config.status config.status: creating Makefile config.status: creating diction.1 config.status: creating diction.texi config.status: creating diction.spec config.status: creating style.1

Компиляция программ на C

387

config.status: creating test/rundiction config.status: creating config.h [me@linuxbox diction-1.11]$

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

Как мы видим, configure создала в каталоге с исходным кодом несколько файлов. Самым важным является файл сборки Makefile. Файл сборки — это конфигура- ционный файл с инструкциями для программы make, описывающими, как собрать программу. Без такого файла утилита make работать не будет. Это обычный тексто- вый файл, то есть мы можем заглянуть в него:

[me@linuxbox diction-1.11]$ less Makefile

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

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

CC= gcc

определяющую, что роль компилятора C будет играть gcc. Далее в файле можно посмотреть, как используется это определение:

diction: diction.o sentence.o misc.o getopt.o getopt1.o $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o
getopt.o getopt1.o $(LIBS)

Здесь выполняется подстановка: во время выполнения конструкция $(CC) заме- щается командой gcc.

Большую часть файла сборки занимают строки, определяющие целевой файл (target) — в данном случае выполняемый файл diction — и файлы, от которых он зависит. Остальные строки описывают команды, которые необходимо выпол- нить для создания целевого файла из его компонентов. Мы видим, что выполняе- мый файл diction (одна из конечных программ) зависит от присутствия файлов diction.o, sentence.o, misc.o, getopt.o и getopt1.o. Далее в файле сборки присутствуют определения, в которых каждый из этих файлов играет роль целевого.

diction.o: diction.c config.h getopt.h misc.h sentence.h getopt.o: getopt.c getopt.h getopt_int.h getopt1.o: getopt1.c getopt.h getopt_int.h

388

Глава 23. Компиляция программ

misc.o: misc.c config.h misc.h sentence.o: sentence.c config.h misc.h sentence.h style.o: style.c config.h getopt.h misc.h sentence.h

Однако в этих определениях не видно ни одной команды. Обработка этих строк осуществляется определением общей цели, что находится выше в файле, где опи- сывается команда компиляции всех файлов с расширением .c в файлы с расшире- нием .o:

.c.o: $(CC) -c $(CPPFLAGS) $(CFLAGS) $

        Заголовок страницы
    
    
            Тело страницы.

Если ввести этот текст в текстовом редакторе и сохранить в файле с именем foo. html, мы сможем открыть его, введя следующий адрес URL в Firefox: file:///home/ username/foo.html.

400

Глава 25. Начало проекта

На первом этапе создадим программу, которая будет выводить эту разметку HTML в стандартный вывод. Написать такую программу очень просто. Откройте текстовый редактор и создайте файл с именем ~/bin/sys_info_page:

[me@linuxbox ~]$ vim ~/bin/sys_info_page

А затем введите следующую программу:

#!/bin/bash

Программа вывода страницы с информацией о системе #

echo "" echo " " echo " Page Title" echo " " echo " " echo " Page body." echo " " echo ""

Наша первая версия содержит строку-шебанг (shebang), комментарий (можно только приветствовать) и последовательность команд echo, по одной для вывода каждой строки. После сохранения файла сделайте его выполняемым и попробуй- те запустить:

[me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page [me@linuxbox ~]$ sys_info_page

После запуска на экране должен появиться текст HTML-документа, потому что команды echo в сценарии посылают свои строки в стандартный вывод. Запусти- те программу снова и перенаправьте вывод программы в файл sys_info_page.html, чтобы затем посмотреть результат в веб-браузере:

[me@linuxbox ~]$ sys_info_page > sys_info_page.html [me@linuxbox ~]$ firefox sys_info_page.html

Пока все идет неплохо.

Разрабатывая программы, всегда следует помнить о простоте и ясности. Сопрово- ждение дается проще, когда программа легко читается и доступна для понимания, не говоря уже о том, что программу легче писать, когда есть возможность умень- шить объем ручного ввода. Текущая версия программы работает замечательно, но ее можно упростить. Если объединить все команды echo в одну, это определенно упростит в будущем добавление новых строк в вывод программы. Поэтому изме- ним программу, как показано ниже:

Этап второй: добавление некоторых данных

401

#!/bin/bash

Программа вывода страницы с информацией о системе #

echo "

            Page Title
    
    
            Page body.

"

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

[me@linuxbox ~]$ echo "

            Page Title
    
    
            Page body.

"

Символ > в начале каждой строки — это приглашение к вводу командной обо- лочки, определяемое ее переменной PS2. Оно появляется всякий раз, когда про- исходит ввод многострочной инструкции. Эта особенность пока малопонятна, но потом, когда мы познакомимся с многострочными программными инструкциями, ее преимущества станут очевидными.

Этап второй: добавление некоторых данных

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

#!/bin/bash

Программа вывода страницы с информацией о системе #

echo "

402

Глава 25. Начало проекта

            System Information Report
    
    
            <h1>System Information Report

"

Здесь добавлено название страницы и заголовок в теле отчета.

Переменные и константы

В нашем сценарии возникла проблема. Обратили внимание, что строка System Information Report повторяется дважды? Вообще, для такого крохотного сце- нария это не такая большая проблема, но представьте по-настоящему длинный сценарий, в котором эта строка повторяется много раз. Если в таком сценарии понадобится изменить название, придется внести изменения во множестве мест, а это масса ручной работы. Можно ли изменить сценарий так, чтобы строка опре- делялась в нем только один раз? Это существенно упростило бы сопровождение сценария в будущем. Да, это возможно, например, так:

#!/bin/bash

Программа вывода страницы с информацией о системе #

title=“System Information Report”

echo "

            $title
    
    
            <h1>$title

"

Создав переменную с именем title и присвоив ей значение System Information Report, мы воспользовались преимуществами подстановки параметров и поме- стили строку во множество мест.

Но как создать переменную? Просто — достаточно использовать ее. Когда ко- мандная оболочка встречает переменную, она автоматически создает ее. Этим она отличается от многих языков программирования, в которых переменные должны явно объявляться или определяться до ее использования. Командная оболочка

Переменные и константы

403

слишком либеральна в этом отношении, что в итоге приводит к некоторым про- блемам. Например, рассмотрим следующий сценарий, выполненный в команд- ной строке:

[me@linuxbox ~]$ foo=“yes” [me@linuxbox ~]$ echo $foo yes [me@linuxbox ~]$ echo $fool

[me@linuxbox ~]$

Мы сначала присвоили значение yes переменной foo и затем вывели ее значе- ние командой echo. Далее, мы попробовали вновь вывести значение перемен- ной, но допустили опечатку, указав имя fool, и получили пустую строку. Та- кой результат объясняется тем, что командная оболочка благополучно создала переменную fool, встретив ее, и присвоила ей пустое значение по умолчанию. Из этого примера следует, что нужно внимательно следить за правописанием! Также важно понять, что в действительности произошло в этом примере. Из предыдущего знакомства с особенностями работы механизма подстановки мы знаем, что команда

[me@linuxbox ~]$ echo $foo

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

[me@linuxbox ~]$ echo yes

С другой стороны, команда

[me@linuxbox ~]$ echo $fool

превращается в

[me@linuxbox ~]$ echo

На место пустой переменной ничего не подставляется! Это может вызвать ошиб- ку в командах, требующих наличия аргументов. Например:

[me@linuxbox ~]$ foo=foo.txt [me@linuxbox ~]$ foo1=foo1.txt [me@linuxbox ~]$ cp $foo $fool cp: после ‘foo.txt’ пропущен операнд, задающий целевой файл По команде “cp –help” можно получить дополнительную информацию.

404

Глава 25. Начало проекта

Мы присвоили значения двум переменным, foo и foo1. А затем попытались вы- полнить команду cp, но допустили опечатку в имени второго аргумента. После обработки механизмом подстановки команда cp получила только один аргумент, хотя требует двух.

Ниже приводятся несколько правил именования переменных:

 Имена переменных могут состоять из алфавитно-цифровых символов (букв

и цифр) и символов подчеркивания.

 Первый символ в имени переменной может быть только буквой или символом

подчеркивания.

 Присутствие пробелов и знаков препинания в именах переменных не допуска-

ется.

Название переменная подразумевает значение, которое может изменяться, и во многих приложениях переменные именно так и используются. Однако пере- менная title в нашем приложении используется как константа. Константа, так же как переменная, имеет имя и содержит значение. Отличие лишь в том, что значение константы не изменяется. В приложении, осуществляющем геоме- трические расчеты, можно определить константу PI со значением 3.1415, вме- сто того, чтобы использовать это число по всей программе. Командная оболочка не различает константы и переменные; эти термины используются в основном для удобства программиста. Типичное соглашение — использовать буквы верх- него регистра для обозначения констант и буквы нижнего регистра для истин- ных переменных. Давайте изменим сценарий, приведя его в соответствие с этим соглашением:

#!/bin/bash

Программа вывода страницы с информацией о системе #

TITLE=“System Information Report For $HOSTNAME”

echo "

            $TITLE
    
    
            <h1>$TITLE

"

Попутно мы дополнили название, добавив в конец значение переменной команд- ной оболочки HOSTNAME. Это — сетевое имя машины.

Переменные и константы

405

ПРИМЕЧАНИЕ

В действительности командная оболочка имеет механизм, гарантирующий не- изменяемость констант, в виде встроенной команды declare с параметром -r (read-only — только для чтения). Если переменной TITLE присвоить значение, как показано ниже:

declare -r TITLE=“Page Title”

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

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

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

переменная=значение

где переменная — это имя переменной, а значение — строка. В отличие от неко- торых других языков программирования, командная оболочка не заботится о ти- пах значений, присваиваемых переменным; она все значения интерпретирует как строки. Существует возможность заставить командную оболочку ограничить круг присваиваемых значений целыми числами, задействовав команду declare с пара- метром -i, но, как и объявление переменных, доступных только для чтения, эта возможность редко используется на практике.

Обратите внимание на отсутствие пробелов в операторе присваивания между именем переменной, знаком «равно» и значением. А из чего может состоять значе- ние? Из всего что угодно, что можно развернуть в строку.

a=z # Присвоит переменной a строку “z”. b=“a string” # Внутренние пробелы должны находиться в кавычках. c=“a string and $b” # При присваивании допускается выполнять подстановку, # например, значений других переменных. d=$(ls -l foo.txt) # Результат выполнения команды. e=$((5 * 7)) # Подстановка результата арифметического выражения. f="\t\ta string\n" # Экранированные последовательности, такие как # символы табуляции и перевода строки.

В одной строке можно выполнить присваивание сразу нескольким переменным:

a=5 b=“a string”

406

Глава 25. Начало проекта

При использовании подстановки имена переменных можно заключать в необяза- тельные фигурные скобки {}. Это пригодится в том случае, когда имя переменной становится неоднозначным в окружающем контексте. В следующем примере вы- полняется попытка переименовать файл myfile в myfile1 с использованием пере- менной:

[me@linuxbox ~]$ filename=“myfile” [me@linuxbox ~]$ touch $filename [me@linuxbox ~]$ mv $filename $filename1 mv: после ‘myfile’ пропущен операнд, задающий целевой файл По команде “mv –help” можно получить дополнительную информацию.

Эта попытка не увенчалась успехом, потому что командная оболочка интерпрети- ровала второй аргумент команды mv как имя новой (и пустой) переменной. Ниже показано, как решается подобная проблема:

[me@linuxbox ~]$ mv $filename ${filename}1

Добавив фигурные скобки, мы гарантировали, что командная оболочка не будет интерпретировать последний символ 1 как часть имени переменной.

ПРИМЕЧАНИЕ

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

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

#!/bin/bash

Программа вывода страницы с информацией о системе #

TITLE=“System Information Report For $HOSTNAME” CURRENT_TIME=$(date +"%x %r %Z") TIME_STAMP=“Generated $CURRENT_TIME, by $USER”

echo "

            $TITLE

Встроенные документы

407

        <h1>$TITLE
        <p>$TIME_STAMP

"

Встроенные документы

Мы рассмотрели два разных метода вывода текста, и оба используют команду echo. Однако существует еще один, третий метод, который называется встроенным до- кументом (here document), или встроенным сценарием (here script). Встроенный документ — это дополнительная форма перенаправления ввода/вывода, которая передает текст, встроенный в сценарий, на стандартный ввод команды. Действует это перенаправление так:

команда

            $TITLE
    
    
        <h1>$TITLE
        <p>$TIME_STAMP

EOF

Теперь вместо команды echo в сценарии используются команда cat и встро- енный документ. На роль индикатора была выбрана строка EOF (означает

408

Глава 25. Начало проекта

end-of-file — конец файла, распространенное соглашение), и она отмечает конец встроенного текста. Обратите внимание, что строка-индикатор должна нахо- диться в отдельной строке, одна, и за ней не должно следовать никаких про- белов.

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

[me@linuxbox ~]$ foo=“some text” [me@linuxbox ~]$ cat $foo

“$foo” ‘$foo’ $foo EOF some text “some text” ‘some text’ $foo

Как видите, командная оболочка не обращает никакого внимания на кавычки. Она интерпретирует их как обычные символы. Благодаря этому мы свободно вставляем кавычки во встроенные документы. Этим обстоятельством можно вос- пользоваться при разработке программ составления отчетов.

Встроенные документы можно использовать с любыми командами, принимающи- ми данные со стандартного ввода. В следующем примере встроенный документ используется для передачи последовательности команд программе ftp, чтобы за- грузить файл с удаленного FTP-сервера:

#!/bin/bash

Сценарий загрузки файла через FTP #

FTP_SERVER=ftp.nl.debian.org FTP_PATH=/debian/dists/stretch/main/installer-amd64/current/images/cdrom REMOTE_FILE=debian-cd_info.tar.gz

ftp -n

            $TITLE
    
    
            <h1>$TITLE
            <p>$TIME_STAMP
            $(report_uptime)
            $(report_disk_space)
            $(report_home_space)

EOF

Создать такие команды можно двумя способами: написать три отдельных сцена- рия и поместить их в каталог, входящий в список PATH, или встроить эти сцена- рии в программу в виде функций командной оболочки. Как уже отмечалось ранее, функции — это «мини-сценарии», находящиеся внутри другого сценария, кото- рые работают как автономные программы. Функции имеют две синтаксические формы. Первая выглядит так:

function имя { команды return }

Вторая проще (и часто предпочтительнее):

имя () { команды return }

Функции командной оболочки

413

где имя — это имя функции, а команды — последовательность команд внутри функ- ции. Обе формы эквивалентны и могут использоваться одна вместо другой. Ниже приводится сценарий, демонстрирующий использование функций командной оболочки:

1 #!/bin/bash 2 3 # Демонстрация функций командной оболочки 4 5 function step2 { 6 echo “Step 2” 7 return 8 } 9 10 # Здесь начинается основная программа 11 12 echo “Step 1” 13 step2 14 echo “Step 3”

Когда командная оболочка читает сценарий, она пропускает строки с 1-й по 11-ю, так как они содержат комментарии и определение функции. Выполнение начина- ется со строки 12 с командой echo. Строка 13 вызывает функцию step2, и команд- ная оболочка выполняет функцию как любую другую команду. Управление пере- дается в строку 6, и выполняется вторая команда echo. Следующей выполняется строка 7. Команда return в этой строке завершает выполнение функции и возвра- щает управление в строку, следующую за вызовом функции (строка 14). После этого выполняется заключительная команда echo. Обратите внимание: чтобы вы- зовы функций интерпретировались не как имена внешних программ, а действи- тельно как вызовы функций, эти функции должны быть определены в сценарии до их вызова.

Добавим в наш сценарий минимальные определения функций:

#!/bin/bash

Программа вывода страницы с информацией о системе #

TITLE=“System Information Report For $HOSTNAME” CURRENT_TIME=$(date +"%x %r %Z") TIME_STAMP=“Generated $CURRENT_TIME, by $USER”

report_uptime () { return }

414

Глава 26. Проектирование сверху вниз

report_disk_space () { return } report_home_space () { return }

cat

            $TITLE
    
    
            <h1>$TITLE
            <p>$TIME_STAMP
            $(report_uptime)
            $(report_disk_space)
            $(report_home_space)

EOF

Имена функций подчиняются тем же правилам, что и имена переменных. Функ- ция должна содержать хотя бы одну команду. Команда return (которая является необязательной) помогает удовлетворить это требование.

Локальные переменные

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

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

Следующий пример сценария демонстрирует, как определяются и используются локальные переменные:

Локальные переменные

415

#!/bin/bash

local-vars: сценарий, демонстрирующий локальные переменные #

foo=0 # глобальная переменная foo

funct_1 () {

  local foo    # переменная foo, локальная для funct_1

  foo=1
  echo "funct_1: foo = $foo"

}

funct_2 () {

  local foo    # переменная foo, локальная для funct_2

  foo=2
  echo "funct_2: foo = $foo"

}

echo “global: foo = $foo” funct_1 echo “global: foo = $foo” funct_2 echo “global: foo = $foo”

Как видите, локальные переменные объявляются добавлением слова local перед именем переменной. В результате создается переменная, локальная по отноше- нию к функции, в которой она определена. Когда выполнение выйдет за пределы функции, переменная перестанет существовать. Если запустить этот сценарий, он выведет следующее:

[me@linuxbox ~]$ local-vars global: foo = 0 funct_1: foo = 1 global: foo = 0 funct_2: foo = 2 global: foo = 0

Этот пример показывает, что присваивание значений локальной переменной foo внутри обеих функций не оказывает влияния на значение переменной foo, объ- явленной за пределами функций.

416

Глава 26. Проектирование сверху вниз

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

Постоянное опробование сценария

В процессе разработки программ необходимо постоянно проверять их работоспо- собность. Запуская и тестируя программы как можно чаще, мы сможем выявить ошибки на самых ранних этапах разработки. Это существенно упрощает задачу отладки. Например, если после внесения небольших изменений и очередного запуска программы обнаружится ошибка, источник проблемы почти наверняка будет находиться в последних изменениях. Добавив пустые функции, которые на языке программистов называются заглушками, мы смогли проверить работо- способность программы на ранней стадии. Создавая заглушку, неплохо было бы включить в нее что-то, что давало бы обратную связь, позволяющую программи- сту оценить ход выполнения. Если сейчас взглянуть на вывод нашего сценария, можно заметить несколько пустых строк, следующих за строкой с текущим време- нем, но мы пока не уверены в причинах их появления.

[me@linuxbox ~]$ sys_info_page

            System Information Report For twin2
    
    
            <h1>System Information Report For linuxbox
            Generated 03/19/2012 04:02:10 PM EDT, by me

Изменим функции, добавив в них сообщения для обратной связи:

report_uptime () { echo “Function report_uptime executed.” return }

report_disk_space () {

Постоянное опробование сценария

417

    echo "Function report_disk_space executed."
    return

}

report_home_space () { echo “Function report_home_space executed.” return }

И запустим сценарий еще раз:

[me@linuxbox ~]$ sys_info_page

            System Information Report For linuxbox
    
    
            <h1>System Information Report For linuxbox
            <p>Generated 03/20/2012 05:17:26 AM EDT, by me
            Function report_uptime executed.
            Function report_disk_space executed.
            Function report_home_space executed.

Теперь можно с уверенностью сказать, что наши три функции выполняются как надо.

Теперь, когда каркас функций готов и работает, самое время добавить в них некий код. Сначала займемся функцией report_uptime:

report_uptime () { cat System Uptime

$(uptime)
EOF
return
}

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

, чтобы сохранить формат вывода команды. Функция report_disk_space вы-
глядит аналогично:

report_disk_space () {
        cat Disk Space Utilization
                
$(df -h)
                _EOF_
        return
}

Она получает информацию о дисковом пространстве с помощью команды df -h.
Наконец, определим функцию report_home_space:

report_home_space () {
        cat Home Space Utilization
                
$(du -sh /home/*)
                _EOF_
        return
}

Для  решения  поставленной  задачи  мы  использовали  команду  du  с  параметрами
-sh. Однако это не полное решение задачи. Даже притом, что его можно исполь-
зовать в некоторых системах (например, в Ubuntu), кое-где оно работать не бу-
дет. Причина в том, что во многих системах для домашних каталогов выбираются
разрешения,  не  позволяющие  читать  их  содержимое  другим  пользователям,  что
является  вполне  разумной  мерой  предосторожности.  В  этих  системах  функция
report_home_space в том виде, в каком она написана здесь, будет работать, толь-
ко  если  запустить  сценарий  с  правами  суперпользователя.  Лучшее,  что  можно
сделать в такой ситуации, — корректировать поведение сценария в соответствии
с привилегиями пользователя, запустившего его. Мы займемся этим в главе 27.

ФУНКЦИИ КОМАНДНОЙ ОБОЛОЧКИ В ФАЙЛЕ .BASHRC

Функции командной оболочки могут служить прекрасной заменой псевдонимам
и  в  действительности  считаются  предпочтительным  способом  определения  не-
больших команд для личного использования. Возможности псевдонимов весьма
ограниченны в отношении использования некоторых видов команд и особенностей
командной оболочки, тогда как функции позволяют все, что можно выразить в виде
сценария. Например, если вам понравилась функция report_disk_space, создан-
ная нами для нашего сценария, вы можете создать похожую функцию с именем
ds в своем файле .bashrc:

ds () {
      echo "Disk Space Utilization For $HOSTNAME"
      df -h
}

Заключение

  419

Заключение

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

27 Управление потоком

выполнения:
ветвление
при помощи if

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

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

x = 5
Если x = 5, тогда:
    Сказать "x равно 5".
Иначе:
    Сказать "x не равно 5".

Это — пример ветвления. Если условие «x = 5?» верно, выполняется строка: «Ска-
зать ‘x равно 5’». Иначе выполняется строка: «Сказать ‘x не равно 5’».

Инструкция if

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

x=5

if [ $x = 5 ]; then
     echo "x equals 5."

Код завершения

  421

else
     echo "x does not equal 5."
fi

То же самое можно выполнить непосредственно в командной строке, получается
немного короче:

[me@linuxbox ~]$ x=5
[me@linuxbox ~]$ if [ $x = 5 ]; then echo "equals 5"; else echo
"does not equal 5"; fi
equals 5
[me@linuxbox ~]$ x=0
[me@linuxbox ~]$ if [ $x = 5 ]; then echo "equals 5"; else echo
"does not equal 5"; fi
does not equal 5

В этом примере мы выполнили команду дважды. Первый раз со значением 5 в пе-
ременной x, что привело к выводу строки equals 5, и второй раз со значением 0
в переменной x, что привело к выводу строки not equal 5.

Инструкция if имеет следующий синтаксис:

if команды; then
      команды
[elif команды; then
      команды...]
[else
      команды]
fi

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

Код завершения

Команды (включая сценарии и функции, написанные нашими собственными ру-
ками)  по  завершении  работы  возвращают  системе  значение,  которое  называют
кодом завершения (exit status). Это значение — целое число в диапазоне от 0 до
255 — сообщает об успешном или неуспешном завершении команды. По соглаше-
ниям значение 0 служит признаком успешного завершения, а любое другое — неу-
спешного. Командная оболочка поддерживает переменную, посредством которой
можно определить код завершения. Например:

422

  Глава 27. Управление потоком выполнения: ветвление при помощи if

[me@linuxbox ~]$ ls -d /usr/bin
/usr/bin
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
[me@linuxbox ~]$ echo $?
2

В этом примере мы дважды выполнили команду ls. В первый раз команда выпол-
нилась благополучно. Если вывести значение переменной $?, можно увидеть, что
оно равно 0. Во второй раз команда ls сообщила об ошибке, а переменная $? содер-
жала значение 2, указывающее, что команда столкнулась с ошибкой. Одни коман-
ды  используют  разные  коды  завершения,  чтобы  сообщить  о  характере  ошибки,
тогда как другие, столкнувшись с любой ошибкой, просто возвращают значение 1.
Страницы  справочного  руководства  часто  включают  раздел  с  заголовком  «Exit
Status» («Коды завершения»), описывающий возвращаемые коды. Однако 0 всег-
да служит признаком успешного выполнения.

Командной  оболочкой  поддерживаются  две  чрезвычайно  простые  встроенные
коман ды, которые просто завершаются с кодом 0 или 1. Команда true всегда за-
вершается с признаком успеха, а команда false — всегда с признаком ошибки:

[me@linuxbox ~]$ true
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ false
[me@linuxbox ~]$ echo $?
1

Эти  команды  можно  использовать  для  исследования  особенностей  работы  ин-
струкции if. Инструкция if в действительности просто оценивает код заверше-
ния команды:

[me@linuxbox ~]$ if true; then echo "It's true."; fi
It's true.
[me@linuxbox ~]$ if false; then echo "It's true."; fi
[me@linuxbox ~]$

Команда echo "It's true." выполняется, только если команда, следующая за if,
завершается успешно, и не выполняется, если команда, следующая за if, заверша-
ется с признаком ошибки. Если за if следует список команд, успешность выпол-
нения всего списка определяется по последней команде:

[me@linuxbox ~]$ if false; true; then echo "It's true."; fi
It's true.

Команда test

  423

[me@linuxbox ~]$ if true; false; then echo "It's true."; fi
[me@linuxbox ~]$

Команда test

Вне  всяких  сомнений,  чаще  всего  с  инструкцией  if  используется  команда  test.
Команда test может выполнять различные проверки и сравнения. Она имеет две
эквивалентные формы:

test выражение

и более популярную

[ выражение ]

где выражение возвращает истинное (true) или ложное (false) значение. Команда
test возвращает код завершения 0, если выражение истинно, и код завершения 1,
если выражение ложно.

Выражения для проверки файлов

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

Таблица 27.1. Выражения для проверки файлов

Выражение

Истинно, если

файл1 -ef файл2

файл1 и файл2 имеют одно и то же число индексного узла
(inode; то есть два имени принадлежат жестким ссылкам, ссы-
лающимся на один и тот же файл)

файл1 -nt файл2

файл1 новее файла файл2

файл1 -ot файл2

файл1 старше файла файл2

-b файл

-с файл

-d файл

-e файл

файл существует и является специальным файлом блочного
устройства

файл существует и является специальным файлом символьного
устройства

файл существует и является каталогом

файл существует

424

  Глава 27. Управление потоком выполнения: ветвление при помощи if

Выражение

Истинно, если

Таблица 27.1 (окончание)

-f файл

-g файл

-G файл

-k файл

-L файл

-O файл

-p файл

-r файл

-s файл

-S файл

файл существует и является обычным файлом

файл существует и имеет атрибут set-group-ID (бит setgid)

файл существует и принадлежит действующей группе

файл существует и имеет атрибут «sticky bit»

файл существует и является символической ссылкой

файл существует и принадлежит действующему пользователю

файл существует и является именованным каналом

файл существует и доступен для чтения (имеет разрешение на
чтение для действующего пользователя)

файл существует и имеет размер больше нуля

файл существует и является сетевым сокетом

-t дескриптор_файла

дескриптор_файла представляет файл, подключенный к тер-
миналу. Это выражение можно использовать для проверки
стандартных потоков ввода/вывода/ошибок

-u файл

-w файл

-x файл

файл существует и имеет атрибут setuid

файл существует и доступен для записи (имеет разрешение на
запись для действующего пользователя)

файл существует и доступен для выполнения (имеет разреше-
ние на выполнение для действующего пользователя)

Следующий сценарий демонстрирует применение некоторых выражений с фай-
лами:

#!/bin/bash

# test-file: проверка файла

FILE=~/.bashrc

if [ -e "$FILE" ]; then
        if [ -f "$FILE" ]; then
                echo "$FILE is a regular file."

Команда test

  425

        fi
        if [ -d "$FILE" ]; then
                echo "$FILE is a directory."
        fi
        if [ -r "$FILE" ]; then
                echo "$FILE is readable."
        fi
        if [ -w "$FILE" ]; then
                echo "$FILE is writable."
        fi
        if [ -x "$FILE" ]; then
                echo "$FILE is executable/searchable."
        fi
else
        echo "$FILE does not exist"
        exit 1
fi
exit

Сценарий  проверяет  файл,  имя  которого  присвоено  константе  FILE,  и  выводит
результат. Этот сценарий имеет две интересные особенности, на которые следует
обратить внимание. Во-первых, отметьте, что параметр $FILE внутри выражений
заключен в кавычки. Это не является обязательным требованием, но защищает от
случаев, когда параметр пуст. Если механизм подстановки заменит $FILE пустым
значением, это приведет к ошибке (операторы в этом случае будут интерпретиро-
ваться как непустые строки, а не как операторы). Использование кавычек гаран-
тирует,  что  за  оператором  всегда  будет  следовать  строка,  даже  если  она  пустая.
Во-вторых, обратите внимание на команду exit (в конце сценария). Команда exit
принимает единственный необязательный аргумент, определяющий код возврата
сценария.  В  отсутствие  аргумента  exit  вернет  значение  по  умолчанию  0.  Такое
использование exit позволит сценарию сообщить об ошибке, если в $FILE содер-
жится  имя  несуществующего  файла.  Команда  exit  в  самом  конце  сценария  до-
бавлена исключительно для формальности. Когда командная оболочка достигает
конца сценария (то есть конца файла), она в любом случае завершает выполнение
сценария с кодом завершения 0.

Функции командной оболочки тоже могут возвращать свой код завершения, пере-
давая целочисленный аргумент команде return. Чтобы преобразовать сценарий,
приведенный выше, в функцию для использования в больших программах, нужно
заменить команды exit инструкциями return:

test_file () {

        # test-file: проверка файла

426

  Глава 27. Управление потоком выполнения: ветвление при помощи if

        FILE=~/.bashrc

        if [ -e "$FILE" ]; then
                if [ -f "$FILE" ]; then
                        echo "$FILE is a regular file."
                fi
                if [ -d "$FILE" ]; then
                        echo "$FILE is a directory."
                fi
                if [ -r "$FILE" ]; then
                        echo "$FILE is readable."
                fi
                if [ -w "$FILE" ]; then
                        echo "$FILE is writable."
                fi
                if [ -x "$FILE" ]; then
                        echo "$FILE is executable/searchable."
                fi
        else
                echo "$FILE does not exist"
                return 1
        fi
}

Выражения для проверки строк

В табл. 27.2 перечислены выражения, используемые для проверки строк.

Таблица 27.2. Выражения для проверки строк

Выражение

Истинно, если

строка

-n строка

-z строка

строка не пустая

Длина строки больше нуля

Длина строки равна нулю

строка1 = строка2

строка1 == строка2

строка1 и строка2 равны. Допускается использовать один
или два знака «равно», но предпочтительнее два

строка1 != строка2

строка1 и строка2 не равны

строка1 > строка2

строка1  и &2
      exit 1
fi

if [ "$ANSWER" == "yes" ]; then
      echo "The answer is YES."
elif [ "$ANSWER" == "no" ]; then
      echo "The answer is NO."
elif [ "$ANSWER" == "maybe" ]; then
      echo "The answer is MAYBE."
else
      echo "The answer is UNKNOWN."
fi

В этом сценарии определяется константа ANSWER. Сначала сценарий проверяет, не
является ли строка пустой. Если строка пустая, сценарий завершается с кодом 1.
Обратите внимание на оператор перенаправления в команде echo. Он перенаправ-
ляет сообщение об ошибке «There is no answer» («Нет ответа») в стандартный вы-
вод ошибок как «наиболее подходящий» для сообщений об ошибках. Если строка
не пустая, сценарий сравнивает ее значение со строками «yes», «no» или «maybe».
Проверки  выполняются  с  использованием  инструкции  elif,  которая  является

428

  Глава 27. Управление потоком выполнения: ветвление при помощи if

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

Выражения для проверки целых чисел

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

Таблица 27.3. Выражения для проверки целых чисел

Выражение

Истинно, если

число1 -eq число2

число1 и число2 равны

число1 -ne число2

число1 и число2 не равны

число1 -le число2

число1 меньше или равно числу2

число1 -lt число2

число1 меньше, чем число2

число1 -ge число2

число1 больше или равно числу2

число1 -gt число2

число1 больше, чем число2

Следующий сценарий демонстрирует их применение:

#!/bin/bash

# test-integer: проверка целочисленного значения.

INT=-5

if [ -z "$INT" ]; then
      echo "INT is empty." >&2
      exit 1
fi

if [ $INT -eq 0 ]; then
      echo "INT is zero."
else
      if [ $INT -lt 0 ]; then
            echo "INT is negative."
      else
            echo "INT is positive."
      fi
      if [ $((INT % 2)) -eq 0 ]; then
            echo "INT is even."

Более современная версия команды test

  429

      else
            echo "INT is odd."
      fi
fi

Обратите  внимание  на  то,  как  сценарий  определяет  четность  (even)  или  нечет-
ность (odd) целого числа. Он возвращает остаток от деления числа на 2, по значе-
нию которого можно судить о четности или нечетности числа.

Более современная версия команды test

Последние  версии  bash  реализуют  составную  команду,  которая  действует  как
улучшенная замена для команды test. Она имеет следующий синтаксис:

[[ выражение ]]

где выражение возвращает истинное (true) или ложное (false) значение. Команда
[[ ]] очень похожа на команду test (она поддерживает те же выражения), но до-
бавляет новое выражение для проверки строк:

строка1 =~ регулярное_выражение

возвращающее истинное значение, если строка1 соответствует расширенному ре-
гулярному выражению. Это открывает широкие перспективы для решения таких
задач, как проверка корректности данных. Предыдущий сценарий, демонстриру-
ющий применение выражений проверки целых чисел, может завершиться с ошиб-
кой, если константе INT присвоить любое значение, не являющееся целым числом.
Для  надежности  сценарию  необходима  возможность  убедиться,  что  константа
действительно  содержит  целое  число.  Используя  [[  ]]  с  оператором  проверки
строки =~, мы усовершенствуем его, как показано ниже:

#!/bin/bash

# test-integer2: проверка целочисленного значения.

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
      if [ $INT -eq 0 ]; then
            echo "INT is zero."
      else
            if [ $INT -lt 0 ]; then
                  echo "INT is negative."

430

  Глава 27. Управление потоком выполнения: ветвление при помощи if

            else
                  echo "INT is positive."
            fi
            if [ $((INT % 2)) -eq 0 ]; then
                  echo "INT is even."
            else
                  echo "INT is odd."
            fi
      fi
else
      echo "INT is not an integer." >&2
      exit 1
fi

Применив регулярное выражение, мы смогли ограничить круг проверяемых зна-
чений в константе INT только строками, начинающимися с необязательного зна-
ка «минус», за которым следует одна или несколько цифр. Это выражение также
устраняет вероятность появления пустых значений.

Еще одна дополнительная особенность [[ ]]: оператор == поддерживает сопостав-
ление с шаблоном по аналогии с механизмом подстановки путей. Например:

[me@linuxbox ~]$ FILE=foo.bar
[me@linuxbox ~]$ if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
foo.bar matches pattern 'foo.*'

Она превращает [[ ]] в удобный инструмент проверки имен файлов и путей.

(( )) — для проверки целых чисел

В  дополнение  к  составной  команде  [[  ]]  bash  поддерживает  также  составную
коман ду (( )), которую удобно использовать для работы с целыми числами. Она
поддерживает полное множество арифметических операторов, о которых подроб-
но рассказывается в главе 34.

Команда (( )) применяется для проверки истинности арифметических выраже-
ний. Арифметическое выражение считается истинным, если его результат отли-
чается от нуля.

[me@linuxbox ~]$ if ((1)); then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ if ((0)); then echo "It is true."; fi
[me@linuxbox ~]$

Объединение выражений

  431

Применив (( )), можно немного упростить сценарий test-integer2, как показано
ниже:

#!/bin/bash

# test-integer2a: проверка целочисленного значения.

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
      if ((INT == 0)); then
            echo "INT is zero."
      else
            if ((INT &2
      exit 1
fi

Обратите внимание, что здесь мы использовали знак «меньше», а равенство про-
веряется с помощью оператора ==. Такой синтаксис выглядит более естественным
при работе с целыми числами. Отметьте также, что составная команда (( )) явля-
ется частью синтаксиса командной оболочки, а не обычной командой, может при-
меняться только к целым числам, распознает переменные по именам и не требует
выполнять подстановку. Подробнее команду (( )) и связанную с ней подстановку
значения арифметического выражения мы обсудим в главе 34.

Объединение выражений

Для  более  сложных  вычислений  существует  возможность  объединения  выра-
жений.  Объединяются  выражения  с  помощью  логических  операторов.  Мы  уже
встречались  с  ними  в  главе  17,  когда  изучали  команду  find.  Всего  команды  test
и [[ ]] поддерживают три логические операции. Это И (AND), ИЛИ (OR) и НЕ

432

  Глава 27. Управление потоком выполнения: ветвление при помощи if

(NOT). Для представления этих операций test и [[ ]] используют разные опера-
торы, как показано в табл. 27.4.

Таблица 27.4. Логические операторы

Операция

test

[[ ]] и (( ))

И

ИЛИ

НЕ

-a

-o

!

&&

||

!

Ниже приводится пример использования операции И (AND). Следующий сцена-
рий  определяет  вхождение  целочисленного  значения  в  определенный  диапазон:

#!/bin/bash

# test-integer3: проверка вхождения целочисленного значения
# в определенный диапазон.

MIN_VAL=1
MAX_VAL=100

INT=50

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
      if [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]]; then
            echo "$INT is within $MIN_VAL to $MAX_VAL."
      else
            echo "$INT is out of range."
      fi
else
      echo "$INT is not an integer." >&2
      exit 1
fi

Этот сценарий определяет, попадает ли целочисленное значение  INT в диапазон
между  MIN_VAL  и  MAX_VAL.  Эта  операция  выполняется  единственной  командой
[[ ]], включающей два выражения, разделенных оператором &&. Ту же проверку
можно выполнить с помощью test:

if [ "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" ]; then
     echo "$INT is within $MIN_VAL to $MAX_VAL."
else
    echo "$INT is out of range."
fi

Объединение выражений

  433

Оператор отрицания ! обращает результат выражения. Он возвращает истинное
значение, если выражение ложно, и ложное значение, если выражение истинно.
В следующем сценарии мы изменили логику вычислений, чтобы определить, на-
ходится ли значение INT за пределами указанного диапазона:

#!/bin/bash

# test-integer4: проверка выхода целочисленного значения
# за границы определенного диапазона.

MIN_VAL=1
MAX_VAL=100

INT=50

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
      if [[ ! ("$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL") ]]; then
            echo "$INT is outside $MIN_VAL to $MAX_VAL."
      else
            echo "$INT is in range."
      fi
else
      echo "$INT is not an integer." >&2
      exit 1
fi

Здесь  выражение  заключено  в  круглые  скобки  для  группировки.  Если  этого  не
сделать, оператор отрицания будет применяться к результату первого выражения,
а не к объединению двух выражений. Ту же проверку можно реализовать с помо-
щью test:

if [ ! \( "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" \) ]; then
      echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
      echo "$INT is in range."
fi

Поскольку  все  выражения  и  операторы  в  команде  test  интерпретируются  ко-
мандной оболочкой как аргументы (в отличие от [[ ]] и (( ))), символы, имею-
щие специальное значение для bash, такие как , ( и ), необходимо заключать
в кавычки или экранировать.

Учитывая, что команды test и [[ ]] до определенной степени равноценны, возни-
кает вопрос: какой из них отдать предпочтение? Команда test является традици-
онной (и частью стандарта POSIX), тогда как команда [[ ]] характерна для bash.
Уметь  пользоваться  командой  test  крайне  важно,  потому  что  она  применяется

434

  Глава 27. Управление потоком выполнения: ветвление при помощи if

очень широко, но команда [[ ]] проще и удобнее, поэтому она часто используется
в современных сценариях.

ПЕРЕНОСИМОСТЬ — БЕСПОЧВЕННЫЕ СТРАХИ ОТ НЕПОНИМАНИЯ

Если вам доведется побеседовать с «истинными» пользователями Unix, вы быстро
обнаружите, что многие из них Linux терпеть не могут. Они оценивают его как
нечто  нечистое  и  греховное.  Один  из  принципов  таких  ревнителей  Unix  —  все
должно быть «переносимым». То есть любой сценарий, написанный вами, должен
работать без изменений в любой Unix-подобной системе.

Пользователи Unix имеют веские основания рассчитывать на это. Наблюдая по-
следствия  для  мира  Unix,  вызванные  внедрением  проприетарных  расширений
команд и командных оболочек до появления POSIX, они естественно опасаются
влияния Linux на их любимую ОС.

Но переносимость имеет серьезный недостаток. Она тормозит прогресс и требует
приведения всего и вся к «наименьшему общему знаменателю». Для сценариев на
языке командной оболочки это означает, что они должны быть совместимы с sh,
оригинальной командной оболочкой Bourne.

Этот недостаток служит отговоркой, которой пользуются производители пропри-
етарных расширений для их оправдания, только они называют их «новшествами».
Но в действительности они замыкают пользователей на себя.

Инструменты GNU, такие как bash, не имеют подобных ограничений. Они способству-
ют переносимости благодаря поддержке стандартов и всеобщей доступности. bash
и другие инструменты GNU можно установить практически в любую систему, даже
в Windows, совершенно бесплатно. Поэтому не бойтесь использовать все возмож-
ности, имеющиеся в командной оболочке bash. Она действительно переносима.

Операторы управления: еще один
способ ветвления

bash поддерживает два оператора управления, которые используются для ветвле-
ния. Операторы  && (И) и  || (ИЛИ) действуют подобно логическим операторам
в составной команде [[ ]]. Они имеют следующий синтаксис:

команда1 && команда2

и

команда1 || команда2

Заключение

  435

Важно понимать, как они действуют. В последовательности с оператором && пер-
вая команда выполняется всегда, а вторая — только если первая завершилась успе-
хом. В последовательности с оператором || первая команда выполняется всегда,
а вторая — только если первая завершилась неудачей.

В практическом смысле это означает, что можно выполнить следующую последо-
вательность команд:

[me@linuxbox ~]$ mkdir temp && cd temp

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

[me@linuxbox ~]$ [ -d temp ] || mkdir temp

проверит  существование  каталога  temp,  и  только  если  проверка  не  увенчается
успехом, будет выполнена команда его создания. Такие конструкции очень удобно
использовать для обработки ошибок в сценариях, о чем подробнее рассказывается
в следующих главах. Например, в сценарии можно предусмотреть такую последо-
вательность:

[ -d temp ] || exit 1

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

Заключение

Мы начали эту главу с вопроса, оставшегося без ответа в предыдущей главе: как
сценарию  sys_info_page  определить,  имеет  ли  текущий  пользователь  права  на
чтение всех домашних каталогов? После знакомства с инструкцией if эту пробле-
му можно решить, добавив следующий код в функцию report_home_space:

report_home_space () {
      if [[ $(id -u) -eq 0 ]]; then
            cat Home Space Utilization (All Users)
                  
$(du -sh /home/*)
                  _EOF_
      else
            cat Home Space Utilization ($USER)

436

  Глава 27. Управление потоком выполнения: ветвление при помощи if

            
$(du -sh $HOME)
            _EOF_
      fi
      return
}

Здесь проверяется вывод команды id. Если вызвать команду id с параметром -u,
она  выведет  числовой  идентификатор  действующего  пользователя.  Суперполь-
зователю  всегда  присваивается  числовой  идентификатор  0.  Зная  это,  мы  скон-
струировали два разных вложенных документа: один пользуется преимуществом
привилегий  суперпользователя,  а  другой  ограничивается  домашним  каталогом
текущего пользователя.

Теперь мы немного отдохнем от программы sys_info_page, но не волнуйтесь. Мы
еще вернемся к ней. А пока затронем те темы, знание которых потребуется, когда
мы возобновим разработку.

28 Чтение ввода

с клавиатуры

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

#!/bin/bash

# test-integer2: проверка целочисленного значения.

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
      if [ "$INT" -eq 0 ]; then
            echo "INT is zero."
      else
            if ["$INT" -lt 0 ]; then
                  echo "INT is negative."
            else
                  echo "INT is positive."
            fi
            if [ $((INT % 2)) -eq 0 ]; then
                  echo "INT is even."
            else
                  echo "INT is odd."
            fi
      fi
else
      echo "INT is not an integer." >&2
      exit 1
fi

438

  Глава 28. Чтение ввода с клавиатуры

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

read — чтение значений со стандартного ввода

Встроенная команда read используется для чтения единственной строки со стан-
дартного ввода. Эту команду можно использовать для чтения ввода с клавиатуры
или, в случае перенаправления, строки данных из файла. Команда имеет следую-
щий синтаксис:

read [-параметры] [переменная...]

где параметры — это один или несколько параметров из перечисленных в табл. 28.1
далее, а переменная — имя одной или нескольких переменных для сохранения вве-
денного значения. Если имя переменной не указано, строка с данными сохраняет-
ся в переменной REPLY.

В простейшем случае read сохраняет значения полей, прочитанные со стандарт-
ного  ввода,  в  указанные  переменные.  Ниже  показано,  как  можно  изменить  наш
сценарий проверки целочисленных значений, задействовав в нем команду read:

#!/bin/bash

# read-integer: проверка целочисленного значения.

echo -n "Please enter an integer -> "
read int

if [[ "$int" =~ ^-?[0-9]+$ ]]; then
      if [ "$int" -eq 0 ]; then
            echo "$int is zero."
      else
            if [ "$int" -lt 0 ]; then
                  echo "$int is negative."
            else
                  echo "$int is positive."
            fi
            if [ $((int % 2)) -eq 0 ]; then
                  echo "$int is even."
            else
                  echo "$int is odd."
            fi

read — чтение значений со стандартного ввода

  439

      fi
else
      echo "Input value is not an integer." >&2
      exit 1
fi

Сначала  мы  использовали  команду  echo  с  параметром  -n  (подавляющим  вы-
вод символа перевода строки в конце) для вывода приглашения к вводу, а затем
коман ду read для ввода значения в переменную int. Запуск этого сценария при-
водит к следующим результатам:

[me@linuxbox ~]$ read-integer
Please enter an integer -> 5
5 is positive.
5 is odd.

Команда read может сохранять ввод в множестве переменных, это показано в сле-
дующем сценарии:

#!/bin/bash

# read-multiple: чтение нескольких значений с клавиатуры

echo -n "Enter one or more values > "
read var1 var2 var3 var4 var5

echo "var1 = '$var1'"
echo "var2 = '$var2'"
echo "var3 = '$var3'"
echo "var4 = '$var4'"
echo "var5 = '$var5'"

Этот  сценарий  вводит,  присваивает  переменным  и  выводит  до  пяти  значений.
Обратите  внимание,  как  действует  команда  read,  когда  получает  разное  число
значений:

[me@linuxbox ~]$ read-multiple
Enter one or more values > a b c d e
var1 = 'a'
var2 = 'b'
var3 = 'c'
var4 = 'd'
var5 = 'e'
[me@linuxbox ~]$ read-multiple
Enter one or more values > a
var1 = 'a'

440

  Глава 28. Чтение ввода с клавиатуры

var2 = ''
var3 = ''
var4 = ''
var5 = ''
[me@linuxbox ~]$ read-multiple
Enter one or more values > a b c d e f g
var1 = 'a'
var2 = 'b'
var3 = 'c'
var4 = 'd'
var5 = 'e f g'

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

Если не передать переменные команде read, весь ввод будет сохранен в перемен-
ной командной оболочки REPLY:

#!/bin/bash

# read-single: чтение множества значений в переменную по умолчанию

echo -n "Enter one or more values > "
read

echo "REPLY = '$REPLY'"

Запуск этого сценария приводит к следующим результатам:

[me@linuxbox ~]$ read-single
Enter one or more values > a b c d
REPLY = 'a b c d'

Параметры

read поддерживает параметры, перечисленные в табл. 28.1.

Таблица 28.1. Параметры команды read

Параметр

Описание

-a массив

Сохранить ввод в указанный массив, начиная с элемента с индек-
сом 0. Подробнее о массивах рассказывается в главе 35

-d разделитель

Использовать в качестве признака конца ввода первый символ из
строки разделитель, а не символ перевода строки

read — чтение значений со стандартного ввода

  441

Параметр

Описание

-e

Использовать Readline для обработки ввода. Это позволяет редак-
тировать ввод, как в командной строке

-n число

Прочитать указанное число символов, а не всю строку

-p приглашение

Показывать указанное приглашение к вводу

-r

-s

Режим без промежуточной обработки. Не интерпретировать об-
ратные слеши как экранирующие символы

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

-t секунды

Предельное время ожидания. Завершить ввод по истечении ука-
занного числа секунд. По истечении указанного интервала read
вернет ненулевое значение

-u дескриптор

Произвести ввод из файла с указанным дескриптором вместо
стандартного ввода

Множество поддерживаемых параметров открывает доступ к довольно интерес-
ным способам использования read. Например, параметр -p позволяет определить
строку приглашения к вводу:

#!/bin/bash

# read-single: чтение множества значений в переменную по умолчанию

read -p "Enter one or more values > "

echo "REPLY = '$REPLY'"

Параметры -t и -s позволяют писать сценарии, реализующие ввод «секретных»
данных и прерывающие ввод по истечении заданного времени:

#!/bin/bash

# read-secret: ввод секретного пароля

if read -t 10 -sp "Enter secret passphrase > " secret_pass; then
      echo -e "\nSecret passphrase = '$secret_pass'"
else
      echo -e "\nInput timed out" >&2
      exit 1
fi

442

  Глава 28. Чтение ввода с клавиатуры

Сценарий  предлагает  пользователю  ввести  секретный  пароль  и  ждет  10  секунд.
Если в течение этого времени ввод не был завершен, сценарий завершается с ко-
дом ошибки. Поскольку в команду включен параметр -s, символы пароля не вы-
водятся на экран в процессе ввода.

С  помощью  параметров  -e  и  -i  можно  предложить  пользователю  значение  по
умолчанию для ввода.

#!/bin/bash

# read-default: предложение значения по умолчанию, для ввода
#               которого достаточно нажать клавишу Enter.

read -e -p "What is your user name? " -i $USER
echo "You answered: '$REPLY'"

Этот сценарий просит ввести имя учетной записи и использует переменную окру-
жения USER, чтобы предложить значение по умолчанию. После запуска сценарий
выведет строку со значением по умолчанию, и если пользователь просто нажмет
ENTER, команда read присвоит это значение переменной REPLY.

[me@linuxbox ~]$ read-default
What is your user name? me
You answered: 'me'

Выделение полей в строке ввода с помощью IFS

Обычно  командная  оболочка  разбивает  введенную  строку  на  слова  перед  пере-
дачей команде read. Как мы уже знаем, в результате этого слова в строке, разде-
ленные одним или несколькими пробелами, превращаются в отдельные значения
и присваиваются командой read разным переменным. Такое поведение командной
оболочки  регулируется  переменной  с  именем  IFS  (от  Internal  Field  Separator  —
внутренний разделитель полей). По умолчанию переменная IFS хранит символы
пробела,  табуляции  и  перевода  строки,  каждый  из  которых  может  служить  раз-
делителем полей.

Изменяя значение переменной IFS, можно управлять делением ввода на поля пе-
ред передачей команде read. Например, файл /etc/passwd хранит строки данных,
в которых поля отделяются друг от друга двоеточием. Присвоив переменной IFS
значение, состоящее из единственного двоеточия, можно с помощью read прочи-
тать содержимое /etc/passwd и благополучно разделить строки на поля для при-
сваивания разным переменным. Ниже приводится сценарий, который именно так
и действует:

read — чтение значений со стандартного ввода

  443

#!/bin/bash

# read-ifs: чтение полей из файла

FILE=/etc/passwd

read -p "Enter a username > " user_name

file_info=$(grep "^$user_name:" $FILE)

if [ -n "$file_info" ]; then
      IFS=":" read user pw uid gid name home shell &2
      exit 1
fi

Этот  сценарий  предлагает  пользователю  ввести  имя  учетной  записи  в  системе
и затем выводит разные поля, найденные в соответствующей записи в файле /etc/
passwd. В сценарии есть две интересные строки. Первая:

file_info=$(grep "^$user_name:" $FILE)

присваивает результат команды  grep переменной  file_info. Регулярное выраже-
ние гарантирует извлечение из файла /etc/passwd единственной строки, соответ-
ствующей введенному имени пользователя.

Вторая интересная строка:

IFS=":" read user pw uid gid name home shell &2
      exit 1
}

read -p "Enter a single item > "

# пустой ввод (недопустимо)
[[ -z "$REPLY" ]] && invalid_input

446

  Глава 28. Чтение ввода с клавиатуры

# ввод множества элементов (недопустимо)
(( "$(echo "$REPLY" | wc -w)" > 1 )) && invalid_input

# введено допустимое имя файла?
if [[ "$REPLY" =~ ^[-[:alnum:]\._]+$ ]]; then
      echo "'$REPLY' is a valid filename."
      if [[ -e "$REPLY" ]]; then
            echo "And file '$REPLY' exists."
      else
            echo "However, file '$REPLY' does not exist."
      fi

      # введено вещественное число?
      if [[ "$REPLY" =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
            echo "'$REPLY' is a floating point number."
      else
            echo "'$REPLY' is not a floating point number."
      fi

      # введено целое число?
      if [[ "$REPLY" =~ ^-?[[:digit:]]+$ ]]; then
            echo "'$REPLY' is an integer."
      else
            echo "'$REPLY' is not an integer."
      fi
else
      echo "The string '$REPLY' is not a valid filename."
fi

Этот сценарий предлагает пользователю ввести элемент данных и затем последо-
вательно анализирует его содержимое. Как видите, в сценарии использовано мно-
жество идей, с которыми мы уже познакомились, включая функции, [[ ]], (( )),
операторы управления && и if, а также разумную дозу регулярных выражений.

Меню

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

Выберите команду:

1. Вывести информацию о системе
2. Вывести информацию о дисковом пространстве
3. Вывести информацию об объеме домашнего каталога

Меню

  447

0. Выйти

Введите номер выбранной команды [0-3] >

Используя  все,  что  мы  узнали  в  ходе  создания  программы  sys_info_page,  мож-
но  сконструировать  программу,  реализующую  решение  задач,  перечисленных
в меню, приведенном выше:

#!/bin/bash

# read-menu: программа вывода системной информации,
#            управляемая с помощью меню

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"

read -p "Enter selection [0-3] > "

if [[ "$REPLY" =~ ^[0-3]$ ]]; then
      if [[ "$REPLY" == 0 ]]; then
            echo "Program terminated."
            exit
      fi
      if [[ "$REPLY" == 1 ]]; then
            echo "Hostname: $HOSTNAME"
            uptime
            exit
      fi
      if [[ "$REPLY" == 2 ]]; then
            df -h
            exit
      fi
      if [[ "$REPLY" == 3 ]]; then
            if [[ "$(id -u)" -eq 0 ]]; then
                  echo "Home Space Utilization (All Users)"
                  du -sh /home/*
            else
                  echo "Home Space Utilization ($USER)"
                  du -sh "$HOME"
            fi

448

  Глава 28. Чтение ввода с клавиатуры

            exit
      fi
else
      echo "Invalid entry." >&2
      exit 1
fi

Этот  сценарий  делится  на  две  логические  части.  Первая  часть  выводит  меню
и вводит выбор пользователя. Вторая часть идентифицирует выбор и выполняет
соответствующие действия. Обратите внимание, как используется команда exit
в этом сценарии. Она препятствует выполнению ненужного кода после заверше-
ния  затребованного  действия.  Наличие  нескольких  точек  выхода  из  программы
вообще  считается  дурным  тоном  (логику  работы  такой  программы  труднее  по-
нять), но в данном сценарии нас это устраивает.

Заключение

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

Дополнительные сведения

Постарайтесь внимательно изучить программы из этой главы и достичь полного
понимания их логической структуры, потому что программы, которые последуют
далее,  будут  еще  сложнее.  В  качестве  упражнения  перепишите  программы  этой
главы, используя команду  test вместо составной команды  [[  ]]. Подсказка: ис-
пользуйте  grep для сопоставления с регулярными выражениями, а затем прове-
ряйте код завершения. Это станет для вас хорошей практикой.

29 Управление

потоком выполнения:
циклы while и until

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

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

Циклы

Повседневная  жизнь  наполнена  повторяющимися  действиями.  Каждодневная
поездка  на  работу,  прогулка  с  собакой  и  нарезание  моркови  —  все  эти  действия
состоят из повторяющейся последовательности действий. Рассмотрим в качестве
примера  резку  моркови.  Этот  вид  деятельности  можно  выразить  на  псевдокоде
примерно так:

1.  Взять разделочную доску.

2.  Взять нож.
3.  Положить морковь на доску.
4.  Поднять нож.
5.  Сдвинуть морковь.

450

  Глава 29. Управление потоком выполнения: циклы while и until

6.  Отрезать кусок.
7.  Если вся морковь порезана, завершить операцию, иначе перейти к шагу 4.

Шаги с 4-го по 7-й образуют цикл. Действия внутри цикла повторяются, пока не
будет выполнено условие «вся морковь порезана».

while

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

#!/bin/bash

# while-count: вывод последовательности чисел

count=1

while [[ $count -le 5 ]]; do
      echo $count
      count=$((count + 1))
done
echo "Finished."

Если запустить этот сценарий, он выведет:

[me@linuxbox ~]$ while-count
1
2
3
4
5
Finished.

Команда while имеет следующий синтаксис:

while команды; do команды; done

Подобно if, команда while проверяет код завершения списка команд. Пока код за-
вершения равен 0, она выполняет команды внутри цикла. В сценарии, приведен-
ном выше, создается переменная count, и ей присваивается начальное значение 1.
Команда  while  проверяет  код  завершения  составной  команды  [[  ]].  Пока  [[  ]]
возвращает код 0, команды внутри цикла продолжают выполняться. В конце каж-
дого цикла повторно выполняется команда [[ ]]. После пяти итераций цикла зна-
чение переменной count увеличится до 6, команда [[ ]] вернет код завершения,

Циклы

  451

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

Цикл while можно использовать для усовершенствования программы read-menu
из предыдущей главы:

#!/bin/bash

# while-menu: программа вывода системной информации,
#             управляемая с помощью меню

DELAY=3 # Время отображения результатов на экране (в секундах)

while [[ "$REPLY" != 0 ]]; do
      clear
      cat  "

      if [[ "$REPLY" =~ ^[0-3]$ ]]; then
            if [[ "$REPLY" == 1 ]]; then
                  echo "Hostname: $HOSTNAME"
                  uptime
                  sleep "$DELAY"
            fi
            if [[ "$REPLY" == 2 ]]; then
                  df -h
                  sleep "$DELAY"
            fi
            if [[ "$REPLY" == 3 ]]; then
                  if [[ "$(id -u)" -eq 0 ]]; then
                        echo "Home Space Utilization (All Users)"
                        du -sh /home/*
                  else
                        echo "Home Space Utilization ($USER)"
                        du -sh "$HOME"
                  fi
                  sleep "$DELAY"
            fi
      else

452

  Глава 29. Управление потоком выполнения: циклы while и until

            echo "Invalid entry."
            sleep "$DELAY"
      fi
done
echo "Program terminated."

Заключив меню в цикл while, мы смогли заставить программу повторять вывод
меню после каждой операции выбора. Цикл продолжает выполняться и выводить
меню, пока переменная REPLY не получит значение 0, предоставляя пользователю
возможность сделать другой выбор. После выполнения выбранной операции вы-
полняется команда sleep, она приостанавливает программу на несколько секунд
и дает возможность увидеть результаты до того, как экран будет очищен и на нем
вновь появится меню. Когда переменная REPLY получит значение 0, соответству-
ющее варианту «Quit» (выйти), цикл завершится и выполнение продолжится со
строки, следующей за done.

Прерывание цикла

В  bash  имеются  две  встроенные  команды  для  управления  потоком  выполнения
внутри  циклов.  Команда  break  немедленно  завершает  цикл,  после  чего  выпол-
нение  программы  продолжается  с  первой  инструкции,  следующей  за  циклом.
Команда  пропускает  оставшуюся  часть  цикла,  и  программа  переходит  к  началу
следующей итерации цикла. Ниже приводится версия программы while-menu, ис-
пользующая обе команды — break и continue:

#!/bin/bash

# while-menu2: программа вывода системной информации,
#              управляемая с помощью меню

DELAY=3 # Время отображения результатов на экране (в секундах)

while true; do
      clear
      cat  "

Прерывание цикла

  453

      if [[ "$REPLY" =~ ^[0-3]$ ]]; then
            if [[ "$REPLY" == 1 ]]; then
                  echo "Hostname: $HOSTNAME"
                  uptime
                  sleep "$DELAY"
                  continue
            fi
            if [[ "$REPLY" == 2 ]]; then
                  df -h
                  sleep "$DELAY"
                  continue
            fi
            if [[ "$REPLY" == 3 ]]; then
                  if [[ "$(id -u)" -eq 0 ]]; then
                        echo "Home Space Utilization (All Users)"
                        du -sh /home/*
                  else
                        echo "Home Space Utilization ($USER)"
                        du -sh "$HOME"
                  fi
                  sleep "$DELAY"
                  continue
            fi
            if [[ "$REPLY" == 0 ]]; then
                  break
            fi
      else
            echo "Invalid entry."
            sleep $DELAY
      fi
done
echo "Program terminated."

В этой версии сценария используется бесконечный цикл (цикл, который никогда
не завершится сам по себе), в котором команда while проверяет код завершения
команды true. Так как true всегда возвращает код 0, цикл никогда не завершит-
ся. Этот прием на удивление широко используется в сценариях. Поскольку цикл
никогда не завершится сам по себе, программист должен предусмотреть его при-
нудительное прерывание в нужный момент времени. В этом сценарии выход из
цикла  осуществляется  с  помощью  команды  break,  когда  пользователь  выберет
пункт 0. В конец других операций добавлена команда continue, чтобы увеличить
эффективность  работы  сценария.  Встретив  команду  continue,  сценарий  пере-
прыгнет через остальной код в цикле, который не требуется выполнять для дан-
ного выбора. Например, если пользователь выбрал пункт 1, нет никаких причин
проверять выбор остальных вариантов.

454

  Глава 29. Управление потоком выполнения: циклы while и until

until

Команда until очень похожа на while, но завершает цикл не когда обнаружит не-
нулевой код завершения, а наоборот. Цикл until продолжается, пока не получит
код завершения 0. В сценарии  while-count цикл продолжает выполняться, пока
значение  переменной  count  меньше  или  равно  5.  Тот  же  результат  можно  полу-
чить, переписав сценарий с командой until:

#!/bin/bash

# until-count: вывод последовательности чисел

count=1

until [[ "$count" -gt 5 ]]; do
      echo "$count"
      count=$((count + 1))
done
echo "Finished."

С  условным  выражением  $count  -gt  5  команда  until  завершит  цикл  в  нужный
момент  времени.  Выбор  между  циклами  while  и  until  обычно  зависит  от  того,
в каком случае условное выражение будет более читабельным.

Чтение файлов в циклах

Команды while и until могут принимать данные со стандартного ввода. Это дает
возможность обрабатывать файлы с их помощью. В следующем примере мы вы-
ведем содержимое файла distros.txt, созданного в одной из предыдущих глав:

#!/bin/bash

# while-read: чтение строк из файла

while read distro version release; do
      printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
      "$distro" \
      "$version" \
      "$release"
done &2
      exit 1
fi
if ! cd "$dir_name"; then
      echo "Cannot cd to '$dir_name'" >&2
      exit 1
fi
if ! rm *; then
      echo "File deletion failed. Check results" >&2
      exit 1
fi

462

  Глава 30. Поиск и устранение ошибок

ПЕРЕНОСИМЫЕ ИМЕНА ФАЙЛОВ

Чтобы  гарантировать  переносимость  имен  файлов  между  разными  платфор-
мами (например, разными типами компьютеров и операционными системами),
ограничьте  набор  символов,  которые  можно  использовать  в  именах  файлов.
Существует стандарт POSIX Portable Filename Character Set (POSIX-совместимый
набор символов для переносимых имен файлов), который можно использовать,
чтобы  увеличить  вероятность  совместимости  имени  файла  с  разными  систе-
мами.  Стандарт  очень  прост.  Он  допускает  использовать  в  именах  файлов
только заглавные A–Z и строчные a–z, буквы, цифры 0–9, точку (.), дефис (-)
и подчеркивание (_). Также стандарт определяет, что имена файлов не могут
начинаться с дефиса.

Здесь проверяются существование каталога с указанным именем и успешное за-
вершение команды cd. Если какая-то из проверок завершается неудачей, в стан-
дартный вывод ошибок отправляется содержательное описание и сценарий завер-
шается с кодом 1, чтобы показать, что он завершился с ошибкой.

Будьте внимательны к именам файлов

У  этого  сценария  есть  еще  одна  проблема,  неочевидная,  но  очень  опасная.  Unix
(и  Unix-подобные  операционные  системы)  по  общему  признанию  имеет  суще-
ственный недостаток, касающийся имен файлов, — чрезмерно либеральное отно-
шение к ним. Фактически имена файлов не могут включать только два символа:
слеш (/), поскольку он используется для разделения элементов путей в файловой
системе, и «пустой» символ (с нулевым кодом), который внутренне используется
для обозначения концов строк. Все остальные символы считаются допустимыми,
включая пробелы, табуляции, переводы строк, ведущие дефисы, возвраты каретки
и т. д.

Особую  сложность  вызывают  ведущие  дефисы.  Например,  ничто  не  помешает
создать  файл  с  именем  -rf  ~.  А  теперь  представьте,  что  случится,  если  передать
имя этого файла команде rm.

Чтобы защититься от этой проблемы, нужно заменить команду удаления файлов
в сценарии:

rm *

следующей командой:

rm ./*

Логические ошибки

  463

Это предотвратит интерпретацию имен файлов, начинающихся с дефиса, как па-
раметров  команды.  Возьмите  за  правило  всегда  предварять  групповые  символы
(такие, как * и ?) комбинацией ./, чтобы предотвратить неверную интерпретацию
команд.  Примером  таких  подстановок  с  групповыми  символами  могут  служить
*.pdf или ???.mp3.

Проверка ввода

Главное правило надежного программирования: если программа принимает ввод,
она должна уметь обработать все, что ей передали. Обычно это означает тщатель-
ную  отбраковку  ввода  с  целью  гарантировать,  что  дальнейшей  обработке  будут
подвергнуты  только  допустимые  данные.  Пример  такой  проверки  мы  видели
в  предыдущей  главе,  когда  обсуждали  команду  read.  Там  один  из  сценариев  со-
держал следующую проверку выбранного пункта меню:

[[ "$REPLY" =~ ^[0-3]$ ]]

Это очень специализированная проверка. Она возвращает код завершения 0, толь-
ко если строка, введенная пользователем, содержит число в диапазоне от 0 до 3.
Никакой другой ввод не принимается. Иногда писать такие проверки очень уто-
мительно, но они совершенно необходимы, если вы хотите в результате получить
надежно работающий сценарий.

УДАЧНЫЙ ДИЗАЙН ЕСТЬ ФУНКЦИЯ ОТ ВРЕМЕНИ

Когда я в студенчестве изучал промышленное проектирование, мудрый профессор
учил нас, что степень проработки проекта определяется объемом времени, вы-
деленного проектировщику. Если вам дано 5 минут на проектирование устройства
для уничтожения воздушных целей, вы спроектируете мухобойку. А если срок —
5 месяцев, вы сможете спроектировать систему противовоздушной обороны с ла-
зерным прицеливанием.

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

464

  Глава 30. Поиск и устранение ошибок

Тестирование

Тестирование  — важный этап в  разработке любого программного обеспечения,
включая  сценарии.  В  мире  открытого  программного  обеспечения  в  ходу  вы-
сказывание  «выпускай  раньше,  выпускай  чаще»,  отражающее  этот  факт.  Про-
граммное обеспечение, выпускаемое раньше и  чаще, получает больше времени
на использование и тестирование. Опыт показывает, что ошибки тем легче най-
ти и тем дешевле исправить, чем раньше в цикле разработки они будут обнару-
жены.

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

Вернемся к рассматривавшейся выше задаче удаления файлов и посмотрим, как
можно  было  бы  легко  протестировать  ее  решение.  Тестировать  оригинальный
фрагмент довольно опасно, потому что его задача — удаление файлов, но его мож-
но изменить, чтобы сделать тестирование безопасным:

if [[ -d $dir_name ]]; then
      if cd $dir_name; then
            echo rm * # ТЕСТИРОВАНИЕ
      else
            echo "cannot cd to '$dir_name'" >&2
            exit 1
      fi
else
      echo "no such directory: '$dir_name'" >&2
      exit 1
fi
exit # ТЕСТИРОВАНИЕ

Так как проверка ошибочных условий уже выводит содержательные сообщения,
нам не требуется добавлять ничего нового. Самое важное изменение заключается
в  добавлении  команды  echo  перед  командой  rm,  которая  выведет  ее  и  список  ее
аргументов, но не разрешит ей выполниться. Это изменение позволит безопасно
выполнить код. В конец фрагмента мы добавили команду exit, чтобы завершить
тест и предотвратить выполнение любых других частей сценария. Необходимость
этого шага зависит от предназначения сценария.

Мы также включили несколько комментариев, которые служат «маркерами» из-
менений, имеющих отношение к тестированию. С их помощью легко можно найти
и удалить эти изменения по завершении тестирования.

Отладка

  465

Комплекты тестов

Чтобы извлечь пользу из тестирования, важно создавать и применять качествен-
ные комплекты тестов. Для этого следует тщательно подобрать данные для вво-
да или условия работы, отражающие крайние и пограничные ситуации. В нашем
фрагменте  кода  (который  очень  прост)  мы  хотим  проверить,  как  действует  код
в трех случаях:

  dir_name содержит имя существующего каталога;

  dir_name содержит имя несуществующего каталога;

  dir_name содержит пустое значение.

Проверив  каждое  из  этих  условий,  мы  получим  приличный  охват  тестиро-
ванием.

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

Отладка

Если  тестирование  выявляет  проблему  в  сценарии,  то  наступает  черед  следую-
щего  шага  —  отладки.  Под  «проблемой»  обычно  понимается  несоответствие  ре-
зультатов работы сценария ожиданиям программиста. В этом случае нужно точно
отследить, что сценарий делает и почему. Поиск ошибок иногда очень напоминает
детективное расследование.

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

Поиск проблемной области

В некоторых сценариях, особенно длинных, иногда полезным оказывается исполь-
зование приема изолирования области сценария, связанной с проблемой. Пробле-
ма не всегда является ошибкой, но изоляция часто помогает понять суть происхо-
дящего. Один из приемов изоляции заключается в том, чтобы «закомментировать»

466

  Глава 30. Поиск и устранение ошибок

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

if [[ -d $dir_name ]]; then
      if cd $dir_name; then
            rm *
      else
            echo "cannot cd to '$dir_name'" >&2
            exit 1
      fi
# else
#       echo "no such directory: '$dir_name'" >&2
#       exit 1
fi

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

Трассировка

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

Один  из  способов  трассировки  заключается  в  размещении  информативных  со-
общений в разных точках сценария, сообщающих, где протекает выполнение. На-
пример, добавим в наш фрагмент следующие сообщения:

echo "preparing to delete files" >&2
if [[ -d $dir_name ]]; then
      if cd $dir_name; then
echo "deleting files" >&2
            rm *
      else
            echo "cannot cd to '$dir_name'" >&2
            exit 1
      fi
else
      echo "no such directory: '$dir_name'" >&2
      exit 1
fi
echo "file deletion complete" >&2

Отладка

  467

Здесь сообщения посылаются в стандартный вывод ошибок, чтобы отделить их от
обычного вывода. Кроме того, отсутствуют отступы перед строками с сообщения-
ми — это упростит их поиск, когда придет время убрать эти строки.

Теперь, запустив сценарий, убедимся, что удаление файлов действительно было
выполнено:

[me@linuxbox ~]$ deletion-script
preparing to delete files
deleting files
file deletion complete
[me@linuxbox ~]$

Кроме  того,  bash  поддерживает  встроенный  метод  трассировки,  реализованный
в виде параметра -x и команды set с параметром -x. Возьмем для примера сцена-
рий trouble, написанный ранее, и активируем встроенный механизм трассировки
для всего сценария, добавив параметр -x в первую строку:

#!/bin/bash -x

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

if [ $number = 1 ]; then
      echo "Number is equal to 1."
else
      echo "Number is not equal to 1."
fi

После запуска мы получим следующие результаты:

[me@linuxbox ~]$ trouble
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.

Включенный  механизм  трассировки  позволяет  увидеть,  какой  вид  приобре-
тают  команды  после  применения  подстановки.  Начальные  знаки  «плюс»  по-
могают  отличить  трассировочную  информацию  от  обычного  вывода.  Знак
«плюс»  —  это  символ  по  умолчанию,  используемый  для  вывода  трассировки.
Он хранится в переменной командной оболочки PS4 (prompt string 4 — строка
приглашения 4). Изменим значение этой переменной, чтобы сделать трассиро-
вочный вывод более полезным. Ниже мы изменили эту переменную, включив
в трассировочный вывод текущий номер выполняемой строки в сценарии. Об-
ратите  внимание  на  необходимость  использования  одиночных  кавычек  —  это

468

  Глава 30. Поиск и устранение ошибок

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

[me@linuxbox ~]$ export PS4='$LINENO + '
[me@linuxbox ~]$ trouble
5 + number=1
7 + '[' 1 = 1 ']'
8 + echo 'Number is equal to 1.'
Number is equal to 1.

Выполнить трассировку только выбранного фрагмента сценария можно с помо-
щью команды set с параметром -x:

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

set -x # Включить трассировку
if [ $number = 1 ]; then
      echo "Number is equal to 1."
else
      echo "Number is not equal to 1."
fi
set +x # Выключить трассировку

Здесь мы использовали команду set с параметром -x, чтобы включить трассиров-
ку, и с параметром +x, чтобы выключить ее. Этот прием используется для исследо-
вания сразу нескольких проблемных фрагментов в сценарии.

Исследование значений в процессе выполнения

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

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

echo "number=$number" # ОТЛАДКА
set -x # Включить трассировку

Заключение

  469

if [ $number = 1 ]; then
      echo "Number is equal to 1."
else
      echo "Number is not equal to 1."
fi
set +x # Выключить трассировку

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

Заключение

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

31 Управление потоком

выполнения:
ветвление
с помощью case

В  этой  главе  мы  продолжим  знакомство  с  инструментами  управления  потоком
выполнения. В главе 28 мы сконструировали простое меню и реализовали логи-
ку обработки выбора его пунктов пользователем. Для этого использовалась серия
команд if, выясняющих, какой из возможных вариантов выбран. Такие конструк-
ции часто можно увидеть в программах, причем так часто, что в некоторых языках
программирования  (включая  командную  оболочку)  был  реализован  механизм
управления потоком выполнения для случаев с множеством альтернативных ва-
риантов.

Команда case

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

case слово in
      [шаблон [| шаблон]...) команды ;;]...
esac

Взгляните  еще  раз,  как  программа  read-menu  из  главы  28  обрабатывает  выбор
пользователя:

#!/bin/bash

# read-menu: программа вывода системной информации,
#            управляемая с помощью меню

Команда case

  471

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "

if [[ "$REPLY" =~ ^[0-3]$ ]]; then
      if [[ "$REPLY" == 0 ]]; then
            echo "Program terminated."
            exit
      fi
      if [[ "$REPLY" == 1 ]]; then
            echo "Hostname: $HOSTNAME"
            uptime
            exit
      fi
      if [[ "$REPLY" == 2 ]]; then
            df -h
            exit
      fi
      if [[ "$REPLY" == 3 ]]; then
            if [[ "$(id -u)" -eq 0 ]]; then
                  echo "Home Space Utilization (All Users)"
                  du -sh /home/*
            else
                  echo "Home Space Utilization ($USER)"
                  du -sh "$HOME"
            fi
            exit
      fi
else
      echo "Invalid entry." >&2
      exit 1
fi

С помощью case можно сделать логику выбора немного проще:

#!/bin/bash

# case-menu: программа вывода системной информации,

472

  Глава 31. Управление потоком выполнения: ветвление с помощью case

#            управляемая с помощью меню

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "

case "$REPLY" in
    0)    echo "Program terminated."
          exit
          ;;
    1)    echo "Hostname: $HOSTNAME"
          uptime
          ;;
    2)    df -h
          ;;
    3)    if [[ "$(id -u)" -eq 0 ]]; then
              echo "Home Space Utilization (All Users)"
              du -sh /home/*
          else
              echo "Home Space Utilization ($USER)"
              du -sh "$HOME"
          fi
          ;;
    *)    echo "Invalid entry" >&2
          exit 1
          ;;
esac

Команда  case  берет  значение  слова  —  в  данном  примере  значение  переменной
REPLY — и затем сопоставляет его с указанными шаблонами. Найдя соответствие,
она  выполняет  команды,  связанные  с  найденным  шаблоном.  После  нахождения
соответствия сопоставление с нижележащими шаблонами уже не производится.

Шаблоны

Шаблоны обрабатываются командой case точно так же, как пути механизмом под-
становки. Шаблоны завершаются символом ). В табл. 31.1 перечислены некото-
рые допустимые шаблоны.

Команда case

  473

Таблица 31.1. Примеры шаблонов в команде case

Шаблон

Описание

a)

Соответствует, если слово содержит a

[[:alpha:]])

Соответствует, если слово содержит единственный алфавитный
символ

???)

*.txt)

*)

Соответствует, если слово содержит ровно три символа

Соответствует, если слово заканчивается символами .txt

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

Следующий пример демонстрирует работу шаблонов:

#!/bin/bash

read -p "enter word > "

case "$REPLY" in
    [[:alpha:]])   echo "is a single alphabetic character." ;;
    [ABC][0-9])    echo "is A, B, or C followed by a digit." ;;
    ???)           echo "is three characters long." ;;
    *.txt)         echo "is a word ending in '.txt'" ;;
    *)             echo "is something else." ;;
esac

Мы можем объединить несколько шаблонов, перечислив их через символ верти-
кальной  черты.  В  результате  получается  комбинированный  условный  шаблон,
объединенный  по  «ИЛИ».  Эта  возможность  может  пригодиться,  например,  для
обработки символов верхнего и нижнего регистров:

#!/bin/bash

# case-menu: программа вывода системной информации,
#            управляемая с помощью меню

clear
echo "
Please Select:
A. Display System Information

474

  Глава 31. Управление потоком выполнения: ветвление с помощью case

B. Display Disk Space
C. Display Home Space Utilization
Q. Quit
"
read -p "Enter selection [A, B, C or Q] > "

case "$REPLY" in
    q|Q)    echo "Program terminated."
            exit
            ;;
    a|A)    echo "Hostname: $HOSTNAME"
            uptime
            ;;
    b|B)    df -h
            ;;
    c|C)    if [[ "$(id -u)" -eq 0 ]]; then
                echo "Home Space Utilization (All Users)"
                du -sh /home/*
            else
                echo "Home Space Utilization ($USER)"
                du -sh "$HOME"
            fi
            ;;
    *)      echo "Invalid entry" >&2
            exit 1
            ;;
esac

Здесь  мы  изменили  программу  case-menu,  предложив  пользователю  выбирать
пункты  меню  вводом  букв,  а  не  цифр.  Обратите  внимание,  что  новые  шаблоны
позволяют вводить буквы обоих регистров — верхнего и нижнего.

Выполнение нескольких вариантов

В версиях bash до 4.0 команда case могла выполнить только один вариант, соот-
ветствующий  совпавшему  шаблону.  После  этого  команда  завершалась.  Рассмо-
трим сценарий, проверяющий введенный символ:

#!/bin/bash

# case4-1: проверка символа

read -n 1 -p "Type a character > "
echo
case "$REPLY" in

Команда case

  475

    [[:upper:]])   echo "'$REPLY' is upper case." ;;
    [[:lower:]])   echo "'$REPLY' is lower case." ;;
    [[:alpha:]])   echo "'$REPLY' is alphabetic." ;;
    [[:digit:]])   echo "'$REPLY' is a digit." ;;
    [[:graph:]])   echo "'$REPLY' is a visible character." ;;
    [[:punct:]])   echo "'$REPLY' is a punctuation symbol." ;;
    [[:space:]])   echo "'$REPLY' is a whitespace character." ;;
    [[:xdigit:]])  echo "'$REPLY' is a hexadecimal digit." ;;
esac

Вот как выглядит результат выполнения этого сценария:

[me@linuxbox ~]$ case4-1
Type a character > a
'a' is lower case.

В большинстве случаев сценарий прекрасно справляется со своей задачей, но тер-
пит неудачу, если символ соответствует нескольким символьным классам POSIX.
Например,  символ  a  соответствует  классам  алфавитных  символов  и  символов
нижнего регистра, а также шестнадцатеричных цифр. В bash до версии 4.0 не было
никакой возможности заставить case выполнить больше одной успешной провер-
ки. Современные версии bash поддерживают дополнительную нотацию ;;& в кон-
це каждого варианта, которая используется, как показано ниже:

#!/bin/bash

# case4-2: проверка символа

read -n 1 -p "Type a character > "
echo
case "$REPLY" in
    [[:upper:]])   echo "'$REPLY' is upper case." ;;&
    [[:lower:]])   echo "'$REPLY' is lower case." ;;&
    [[:alpha:]])   echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]])   echo "'$REPLY' is a digit." ;;&
    [[:graph:]])   echo "'$REPLY' is a visible character." ;;&
    [[:punct:]])   echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]])   echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]])  echo "'$REPLY' is a hexadecimal digit." ;;&
esac

Запустив этот сценарий, мы получим:

[me@linuxbox ~]$ case4-2
Type a character > a
'a' is lower case.

476

  Глава 31. Управление потоком выполнения: ветвление с помощью case

'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

Дополнительный  синтаксис  ;;&  позволяет  команде  case  продолжить  проверку
вместо простого завершения  после первого найденного совпадения.

Заключение

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

32 Позиционные

параметры

Во всех предыдущих наших программах отсутствовала одна особенность — воз-
можность принимать и обрабатывать параметры и аргументы командной строки.
В этой главе мы исследуем эту возможность и позволим нашим программам об-
ращаться к содержимому командной строки.

Доступ к командной строке

Командная оболочка поддерживает множество переменных, которые называются
позиционными  параметрами  и  содержат  отдельные  слова  из  командной  строки.
Эти переменные имеют имена от 0 до 9. Продемонстрируем их:

#!/bin/bash

# posit-param: сценарий для просмотра параметров командной строки

echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

478

  Глава 32. Позиционные параметры

Этот очень простой сценарий выводит значения переменных с именами от $0 до
$9. Запустим его без аргументов командной строки:

[me@linuxbox ~]$ posit-param

$0 = /home/me/bin/posit-param
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

Даже  в  отсутствие  аргументов  переменная  $0  всегда  содержит  первый  элемент
командной строки — путь к файлу выполняемой программы. Давайте передадим
сценарию несколько аргументов:

[me@linuxbox ~]$ posit-param a b c d

$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

ПРИМЕЧАНИЕ

В  действительности,  если  использовать  механизм  подстановки  параметров,
можно получить доступ более чем к девяти параметрам. Чтобы указать число
больше  девяти,  следует  заключить  его  в  фигурные  скобки;  например,  ${10},
${55}, ${211} и т. д.

Определение числа аргументов

Командная оболочка поддерживает также переменную $#, хранящую число аргу-
ментов командной строки:

Доступ к командной строке

  479

#!/bin/bash

# posit-param: сценарий для просмотра параметров командной строки

echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

Результат:

[me@linuxbox ~]$ posit-param a b c d

Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

shift — доступ к множеству аргументов

Но как быть, если программе передается большое число аргументов, как в следу-
ющем примере:

[me@linuxbox ~]$ posit-param *

Number of arguments: 82
$0 = /home/me/bin/posit-param
$1 = addresses.ldif
$2 = bin
$3 = bookmarks.html

480

  Глава 32. Позиционные параметры

$4 = debian-500-i386-netinst.iso
$5 = debian-500-i386-netinst.jigdo
$6 = debian-500-i386-netinst.template
$7 = debian-cd_info.tar.gz
$8 = Desktop
$9 = dirlist-bin.txt

В  системе,  где  выполнялся  этот  пример,  механизм  подстановки  развернул  сим-
вол  *  в  82  аргумента.  Как  обработать  такое  количество?  Командная  оболочка
предусматривает решение и для подобных случаев, правда, следует отметить, что
изяществом  оно  не  отличается.  Команда  shift  выполняет  «сдвиг»  параметров
к  началу  списка.  Фактически,  используя  shift,  можно  обойтись  единственной
переменной-параметром (помимо $0, которая никогда не изменяется).

#!/bin/bash

# posit-param2: сценарий вывода всех аргументов

count=1

while [[ $# -gt 0 ]]; do
      echo "Argument $count = $1"
      count=$((count + 1))
      shift
done

Каждый  раз,  когда  выполняется  команда  shift,  значение  $2  перемещается  в  $1,
значение $3 перемещается в $2 и т. д. Значение $# при этом уменьшается на 1.

В программе posit-param2 мы создали цикл, проверяющий число оставшихся ар-
гументов и продолжающийся до тех пор, пока оно не уменьшится до нуля. Цикл
выводит текущий аргумент, в каждой итерации увеличивает счетчик обработан-
ных аргументов count и, наконец, выполняет shift, чтобы загрузить в $1 следую-
щий аргумент. Вот как работает эта программа:

[me@linuxbox ~]$ posit-param2 a b c d
Argument 1 = a
Argument 2 = b
Argument 3 = c
Argument 4 = d

Простые приложения

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

Доступ к командной строке

  481

#!/bin/bash

# file_info: простая программа получения информации о файле

PROGNAME="$(basename "$0")"

if [[ -e "$1" ]]; then
      echo -e "\nFile Type:"
      file "$1"
      echo -e "\nFile Status:"
      stat "$1"
else
      echo "$PROGNAME: usage: $PROGNAME file" >&2
      exit 1
fi

Эта программа выводит тип указанного файла (определяется с помощью команды
file) и его атрибуты (командой stat). Интересной особенностью программы являет-
ся переменная PROGNAME. Ей присваивается результат выполнения команды basename
"$0". Команда basename удаляет начальную часть из пути к файлу, оставляя только
базовое имя. В данном примере basename удалит начальную часть из параметра $0,
хранящего полный путь к данной программе. Такой результат удобно использовать
для конструирования сообщений, например, о правилах использования программы.
При подобном подходе можно переименовать сценарий, и при выводе сообщений
новое имя программы будет использоваться автоматически.

Использование позиционных параметров в функциях

Позиционные параметры используются для передачи аргументов не только в сце-
нарии, но и в функции командной оболочки. Для демонстрации преобразуем сце-
нарий file_info в функцию:

file_info () {

      # file_info: функция для вывода информации о файле

      if [[ -e "$1" ]]; then
            echo -e "\nFile Type:"
            file "$1"
            echo -e "\nFile Status:"
            stat "$1"
      else
            echo "$FUNCNAME: usage: $FUNCNAME file" >&2
            return 1
      fi
}

482

  Глава 32. Позиционные параметры

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

Благодаря этому мы получаем возможность написать множество полезных функ-
ций для использования не только в наших сценариях, но и в файле .bashrc.

Обратите внимание, что в этом примере вместо переменной PROGNAME использует-
ся переменная командной оболочки FUNCNAME. Оболочка автоматически присваи-
вает значение этой переменной в момент вызова функции. Отметьте также, что $0
всегда содержит полный путь к первому элементу командной строки (то есть имя
программы), а не имя функции, как можно было бы ожидать.

Обработка позиционных параметров скопом

Иногда бывает необходимо выполнить операцию сразу со всеми позиционными
параметрами. Например, может понадобиться написать «обертку» для некоторой
программы, то есть сценарий или функцию, упрощающие запуск этой программы.
Обертка  принимает  список  непонятных  для  нее  параметров  командной  строки
и просто передает его обертываемой программе.

Для  этой  цели  командная  оболочка  предоставляет  два  специальных  параметра.
Они оба замещаются полным списком позиционных параметров, но имеют неко-
торые тонкие отличия. Описание этих параметров приводится в табл. 32.1.

Таблица 32.1. Специальные параметры $* и $@

Параметр

Описание

$*

$@

Замещается списком позиционных параметров, начиная с $1. Если имя
параметра $* заключить в двойные кавычки, позиционные параметры будут
перечислены в списке через первый символ в переменной IFS (по умол-
чанию пробел), а сам список будет размещен в одной строке и заключен
в кавычки

Замещается списком позиционных параметров, начиная с $1. Если имя пара-
метра $@ заключить в двойные кавычки, механизм подстановки заменит его
списком позиционных параметров, заключенных в кавычки по отдельности

Следующий сценарий демонстрирует, как действуют эти специальные параметры:

#!/bin/bash

# posit-params3 : сценарий для демонстрации $* и $@

Обработка позиционных параметров скопом

  483

print_params () {
      echo "\$1 = $1"
      echo "\$2 = $2"
      echo "\$3 = $3"
      echo "\$4 = $4"
}

pass_params () {
      echo -e "\n" '$* :';   print_params $*
      echo -e "\n" '"$*" :'; print_params "$*"
      echo -e "\n" '$@ :';   print_params $@
      echo -e "\n" '"$@" :'; print_params "$@"
}

pass_params "word" "words with spaces"

В этой довольно замысловатой программе мы создали два аргумента, word и words
with  spaces, и передали их функции  pass_params. Эта функция, в свою очередь,
передает их функции print_params, с применением каждого из четырех методов,
доступных для специальных параметров $* и $@. Вывод сценария показывает раз-
ницу между ними:

[me@linuxbox ~]$ posit-param3
 $* :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =

 $@ :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =

484

  Глава 32. Позиционные параметры

В данном примере оба параметра, $* и $@, возвращают результат из четырех слов:

word words with spaces

"$*" возвращает результат в виде одного слова, содержащего пробелы:

"word words with spaces"

"$@" возвращает результат в виде двух слов, второе из которых включает пробелы:

"word" "words with spaces"

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

Более сложное приложение

После долгой паузы мы продолжим работу над программой sys_info_page. Теперь
мы добавим в нее поддержку нескольких параметров командной строки:

  Выходной файл. Мы добавим параметр, который позволит указать имя файла
для вывода результатов работы программы. Сделать это можно будет с помо-
щью -f файл или --file файл.

  Интерактивный режим. При передаче этого параметра программа будет пред-
лагать пользователю ввести имя выходного файла и определять, существует ли
этот  файл.  Если  файл  существует,  пользователю  будет  предложено  подтвер-
дить свое решение, прежде чем затереть существующий файл. Этот параметр
можно будет передать как -i или --interactive.

  Справка. Передав параметр -h или --help, можно потребовать от программы

вывести сообщение с информацией о правилах пользования программой.

Далее приводится код, реализующий обработку командной строки:

usage () {
      echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
      return
}

# обработка параметров командной строки

interactive=
filename=

Более сложное приложение

  485

while [[ -n "$1" ]]; do
      case "$1" in
            -f | --file)           shift
                                   filename="$1"
                                   ;;
            -i | --interactive)    interactive=1
                                   ;;
            -h | --help)           usage
                                   exit
                                   ;;
            *)                     usage >&2
                                   exit 1
                                   ;;
      esac
      shift
done

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

Затем следует цикл обработки параметров. Цикл продолжается, пока позицион-
ный параметр $1 не получит пустое значение. В конце цикла вызывается команда
shift,  чтобы  сдвинуть  позиционные  параметры  и,  в конечном итоге, гарантиро-
вать завершение цикла.

Внутри цикла инструкция case проверяет текущий позиционный параметр на со-
ответствие поддерживаемым вариантам. Если данный параметр поддерживается,
выполняется соответствующая операция, если нет — выводится сообщение с ин-
формацией о правилах пользования программой и сценарий завершается с при-
знаком ошибки.

Обратите внимание, как обрабатывается параметр -f. Обнаружив этот параметр,
программа  выполняет  команду  shift,  которая  сдвинет  аргумент  параметра  -f
с именем файла в позиционный параметр $1.

Далее следует код, реализующий интерактивный режим:

# интерактивный режим

if [[ -n "$interactive" ]]; then
      while true; do
            read -p "Enter name of output file: " filename
            if [[ -e "$filename" ]]; then
                  read -p "'$filename' exists. Overwrite? [y/n/q] > "
                  case "$REPLY" in
                      Y|y)      break
                                ;;

486

  Глава 32. Позиционные параметры

                      Q|q)      echo "Program terminated."
                                exit
                                ;;
                      *)        continue
                                ;;
                  esac
            elif [[ -z "$filename" ]]; then
                  continue
            else
                  break
            fi
      done
fi

Если переменная interactive содержит непустое значение, начинается бесконеч-
ный цикл, который предлагает ввести имя файла и затем обрабатывает ситуацию,
если введенное имя соответствует существующему файлу. Если указанный файл
уже существует, пользователю на выбор предлагается три варианта: затереть су-
ществующий  файл,  выбрать  другое  имя  или  завершить  программу.  Если  поль-
зователь  предпочтет  затереть  существующий  файл,  выполняется  команда  break
и цикл прерывается. Обратите внимание, что инструкция case различает только
вариант перезаписи существующего файла и завершения программы. Любой дру-
гой ответ пользователя будет приводить к переходу в начало цикла с повторным
предложением ввести имя файла.

Для поддержки вывода в файл сначала необходимо имеющийся код вывода стра-
ницы преобразовать в функцию. Необходимость такого решения станет понятна
чуть позже:

write_html_page () {
      cat 
              
                     $TITLE
              
              
                     

$TITLE

$TIMESTAMP $(report_uptime) $(report_disk_space) $(report_home_space) _EOF_ return } Заключение 487 # вывод страницы html if [[ -n "$filename" ]]; then if touch "$filename" && [[ -f "$filename" ]]; then write_html_page > "$filename" else echo "$PROGNAME: Cannot write file '$filename'" >&2 exit 1 fi else write_html_page fi Код, обслуживающий логику параметра -f, находится в конце листинга, приве- денного выше. Он проверяет, определено ли имя файла и затем — доступность для записи файла с указанным именем. Для этого выполняется команда touch с последующей проверкой, что файл является обычным файлом. Эти две провер- ки позволяют обработать ситуацию неправильно указанного пути (в этом случае touch потерпит неудачу) и убедиться, что существующий файл является обычным файлом. Как видите, функция write_html_page вызывается, чтобы сгенерировать факти- ческое содержимое страницы, которое затем либо выводится в стандартный вы- вод (если переменная filename содержит пустое значение), либо перенаправляется в указанный файл. Поскольку код HTML может выводиться в файл или в стан- дартный вывод, есть смысл преобразовать процедуру write_html_page в функцию, чтобы избежать повторения кода. Заключение С помощью дополнительных позиционных параметров мы можем теперь писать довольно функциональные сценарии. Позиционные параметры помогают созда- вать очень полезные функции командной оболочки для выполнения повседнев- ных задач, которые можно поместить в файл .bashrc. Наша программа sys_info_page выросла и усложнилась. Ниже приводится пол- ный листинг программы с выделенными последними изменениями: #!/bin/bash # sys_info_page: программа вывода страницы с информацией о системе PROGNAME="$(basename $0)" TITLE="System Information Report For $HOSTNAME" 488 Глава 32. Позиционные параметры CURRENT_TIME=$(date +"%x %r %Z") TIME_STAMP="Generated $CURRENT_TIME, by $USER" report_uptime () { cat System Uptime

$(uptime)
            _EOF_
      return
}

report_disk_space () {
      cat Disk Space Utilization
            
$(df -h)
            _EOF_
      return
}

report_home_space () {
      if [[ "$(id -u)" -eq 0 ]]; then
            cat Home Space Utilization (All Users)
                  
$(du -sh /home/*)
                  _EOF_
      else
            cat Home Space Utilization ($USER)
                  
$(du -sh "$HOME")
                  _EOF_
      fi
      return
}

usage () {
      echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
      return
}

write_html_page () {
      cat 
              
                     $TITLE
              
              
                     

$TITLE Заключение 489

$TIMESTAMP $(report_uptime) $(report_disk_space) $(report_home_space) _EOF_ return } # обработка параметров командной строки interactive= filename= while [[ -n "$1" ]]; do case "$1" in -f | --file) shift filename="$1" ;; -i | --interactive) interactive=1 ;; -h | --help) usage exit ;; *) usage >&2 exit 1 ;; esac shift done # интерактивный режим if [[ -n "$interactive" ]]; then while true; do read -p "Enter name of output file: " filename if [[ -e "$filename" ]]; then read -p "'$filename' exists. Overwrite? [y/n/q] > " case "$REPLY" in Y|y) break ;; Q|q) echo "Program terminated." exit ;; *) continue ;; esac 490 Глава 32. Позиционные параметры fi done fi # вывод страницы html if [[ -n "$filename" ]]; then if touch "$filename" && [[ -f "$filename" ]]; then write_html_page > "$filename" else echo "$PROGNAME: Cannot write file '$filename'" >&2 exit 1 fi else write_html_page fi У нас уже получился неплохой сценарий, но он еще не закончен. В следующей главе мы добавим в него последнее улучшение. 33 Управление потоком выполнения: цикл for В этой заключительной главе, посвященной управлению потоком выполнения, мы познакомимся еще с одной конструкцией организации циклов в командной обо- лочке. Цикл for отличается от циклов while и until поддержкой средств обработки последовательностей. Это очень полезная возможность. Как следствие, цикл for пользуется большой популярностью среди создателей сценариев для bash. Цикл for реализован, что вполне естественно, в виде команды for. В современных версиях bash поддерживается две формы команды for. for: традиционная форма Оригинальный синтаксис команды for имеет следующий вид: for переменная [in слова]; do команды done где переменная — это имя переменной, значение которой будет увеличиваться в ходе выполнения цикла, слова — необязательный список элементов, которые последовательно будут присваиваться переменной, и команды — это команды, вы- полняемые в каждой итерации. Команду for удобно использовать в командной строке. Рассмотрим, как она ра- ботает: [me@linuxbox ~]$ for i in A B C D; do echo $i; done A B C D 492 Глава 33. Управление потоком выполнения: цикл for В этом примере команда for получает список из четырех слов: A, B, C и D. Для об- хода этого списка выполняется четыре итерации цикла. В начале каждой итерации переменной i присваивается очередное слово. Внутри цикла находится команда echo, она выводит значение i, чтобы показать, что присваивание действительно выполняется. Так же как в случае с циклами while и until, цикл for заканчивается ключевым словом done. По-настоящему мощной особенностью for является разнообразие способов фор- мирования списка слов. Например, можно использовать подстановку в фигурных скобках: [me@linuxbox ~]$ for i in {A..D}; do echo $i; done A B C D или подстановку имен файлов: [me@linuxbox ~]$ for i in distros*.txt; do echo "$i"; done distros-by-date.txt distros-dates.txt distros-key-names.txt distros-key-vernums.txt distros-names.txt distros.txt distros-vernums.txt distros-versions.txt Механизм подстановки имен файлов позволяет получить четкий список с име- нами файлов, который можно обработать в цикле. Единственное, о чем следует позаботиться, — убедиться, что механизм подстановки вернул действительные со- впадения. По умолчанию, если операция подстановки не найдет ни одного файла, соответствующего шаблону, она вернет сам шаблон (в данном случае distros*. txt). Чтобы защититься от этой проблемы, предыдущий код можно преобразовать в сценарий, как показано ниже: for i in distros*.txt; do if [[ -e "$i" ]]; then echo "$i" fi done Добавив проверку существования файла, мы обезопасили себя от ситуации, когда механизм подстановки не нашел ни одного файла. Другой распространенный ме- тод получения списка слов — подстановка команд. for: традиционная форма 493 #!/bin/bash # longest-word : поиск самой длинной строки в файле while [[ -n "$1" ]]; do if [[ -r "$1" ]]; then max_word= max_len=0 for i in $(strings "$1"); do len="$(echo "$i" | wc -c)" if (( len > max_len )); then max_len="$len" max_word="$i" fi done echo "$1: '$max_word' ($max_len characters)" fi shift done Этот пример осуществляет поиск самой длинной строки в файле. Когда в ко- мандной строке указано несколько имен файлов, сценарий вызывает процедуру strings (входит в состав пакета GNU binutils), чтобы получить список «слов» из каждого файла. Цикл for обрабатывает каждое слово по очереди и определяет, является ли оно самым длинным из встречавшихся до сих пор. По завершении цикла сценарий выводит самое длинное слово. Обратите внимание, что здесь, вопреки обычной практике, мы не заключили под- становку команд $(strings "$1") в кавычки. Объясняется это просто: здесь мы действительно хотим разбить строку на слова, чтобы получить список. Заключив подстановку команд в кавычки, мы получили бы одно слово, содержащее все стро- ки из файла. А это не совсем то, что нам нужно. Если необязательный компонент слова в команде for отсутствует, она по умолча- нию обрабатывает позиционные параметры. Чтобы показать использование этого способа, изменим сценарий longest-word: #!/bin/bash # longest-word2 : поиск самой длинной строки в файле for i; do if [[ -r "$i" ]]; then max_word= max_len=0 for j in $(strings "$i"); do 494 Глава 33. Управление потоком выполнения: цикл for len="$(echo "$j" | wc -c)" if (( len > max_len )); then max_len="$len" max_word="$j" fi done echo "$i: '$max_word' ($max_len characters)" fi done Как видите, мы заменили внешний цикл while циклом for. Так как список слов в команде for отсутствует, она перебирает позиционные параметры. Во внутрен- нем цикле вместо переменной i теперь используется переменная j. Кроме того, нам больше не нужна команда shift. ПОЧЕМУ I? Вы могли заметить, что во всех примерах цикла for выше использовалась пере- менная i. Почему? В действительности за этим выбором не стоят какие-то опре- деленные причины, кроме стремления следовать традициям. В команде for можно использовать любую допустимую переменную, но чаще всего используется пере- менная i, а также j и k. Своими корнями эта традиция уходит в язык программирования Fortran. В Fortran необъявленные переменные, начинающиеся с букв I, J, K, L и M, автоматически становились целочисленными, тогда как переменные, начинающиеся с любой другой буквы, — действительными, или вещественными (способными хранить числа с дробной частью). Эта особенность вынуждала программистов использо- вать переменные I, J и K в качестве переменных цикла, так как использование их в качестве временных переменных (чем переменные цикла в действительности и являются) требовало меньших усилий. Из-за этого даже в среде программистов на Fortran ходила острота: «GOD is real, unless declared integer» (Бог действителен, пока явно не объявлен целым). for: форма в стиле языка C В последние версии bash добавлена вторая форма синтаксиса команды for, напо- минающая одноименный оператор в языке программирования C, которая поддер- живается также многими другими языками. for: форма в стиле языка C 495 for (( выражение1; выражение2; выражение3 )); do команды done где выражение1, выражение2 и выражение3 — это арифметические выражения, а коман ды — это команды, выполняемые в каждой итерации цикла. Своим поведением эта форма эквивалентна следующей конструкции: (( выражение1 )) while (( выражение2 )); do команды (( выражение3 )) done выражение1 инициализирует цикл, выражение2 определяет условие завершения цикла, выражение3 выполняется в конце каждой итерации. Ниже приводится пример типичного применения: #!/bin/bash # simple_counter : демонстрация команды for в стиле языка C for (( i=0; iHome Space Utilization (All Users)

$(du -sh /home/*)
                  _EOF_
      else
            cat Home Space Utilization ($USER)
                  
$(du -sh "$HOME")
                  _EOF_
      fi
      return
}

Теперь мы можем переписать ее, добавив вывод информации о домашнем катало-
ге каждого пользователя и включив в вывод общее число файлов и подкаталогов
в каждом из них:

report_home_space () {

      local format="%8s%10s%10s\n"
      local i dir_list total_files total_dirs total_size user_name

      if [[ "$(id -u)" -eq 0 ]]; then
            dir_list=/home/*
            user_name="All Users"
      else
            dir_list="$HOME"
            user_name="$USER"
      fi

      echo "

Home Space Utilization ($user_name)" for i in $dir_list; do total_files="$(find "$i" -type f | wc -l)" total_dirs="$(find "$i" -type d | wc -l)" total_size="$(du -sh "$i" | cut -f 1)" Заключение 497 echo "

$i" echo "
"
            printf "$format" "Dirs" "Files" "Size"
            printf "$format" "----" "-----" "----"
            printf "$format" "$total_dirs" "$total_files" "$total_size"
            echo ""
      done
      return
}

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

34 Строки и числа

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

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

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

Механизм подстановки параметров уже рассматривался в главе 7, но там этот ме-
ханизм не был описан детально, потому что большая часть его возможностей ис-
пользуется в сценариях, а не в командной строке. Мы уже знакомы с некоторыми
формами подстановки параметров, например, с подстановкой значений перемен-
ных командной оболочки. Но в командной оболочке их намного больше.

ПРИМЕЧАНИЕ

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

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

  499

Простые параметры

Простейшую форму подстановки параметров можно наблюдать в использовании
переменных. Например, запись

$a

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

${a}

Это не оказывает влияния на результат подстановки, но является необходимым,
если сразу за именем переменной следует какой-то другой текст, который может
сбивать с толку командную оболочку. В следующем примере выполняется попыт-
ка сконструировать имя файла добавлением строки _file к содержимому перемен-
ной a:

[me@linuxbox ~]$ a="foo"
[me@linuxbox ~]$ echo "$a_file"

Если выполнить эту последовательность команд, результатом будет пустое значе-
ние, потому что командная оболочка попытается выполнить подстановку значе-
ния переменной a_file вместо a. Эта проблема устраняется с помощью фигурных
скобок:

[me@linuxbox ~]$ echo "${a}_file"
foo_file

Мы видели также, что доступ к позиционным параметрам с порядковыми номе-
рами выше 9 тоже осуществляется с помощью фигурных скобок. Например, про-
читать 11-й позиционный параметр можно следующим образом:

${11}

Подстановка пустых переменных

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

${параметр:-слово}

500

  Глава 34. Строки и числа

Если параметр не определен (то есть отсутствует) или содержит пустое значение,
механизм подстановки вернет значение указанного слова. Если параметр не пу-
стой, механизм подстановки вернет значение параметра.

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
substitute value if unset
[me@linuxbox ~]$ echo $foo

[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
bar
[me@linuxbox ~]$ echo $foo
bar

Вот еще один вариант подстановки, где вместо дефиса используется знак «равно»:

${параметр:=слово}

Если параметр не определен или содержит пустое значение, механизм подстанов-
ки  вернет  значение  указанного  слова  и  дополнительно  присвоит  его  параметру.
Если параметр не пустой, механизм подстановки вернет значение параметра.

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:="default value if unset"}
default value if unset
[me@linuxbox ~]$ echo $foo
default value if unset
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:="default value if unset"}
bar
[me@linuxbox ~]$ echo $foo
bar

ПРИМЕЧАНИЕ

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

Ниже демонстрируется форма со знаком вопроса:

${параметр:?слово}

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

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

  501

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

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:?"parameter is empty"}
bash: foo: parameter is empty
[me@linuxbox ~]$ echo $?
1
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:?"parameter is empty"}
bar
[me@linuxbox ~]$ echo $?
0

Ниже демонстрируется форма со знаком «плюс»:

${параметр:+слово}

Если параметр не определен или содержит пустое значение, механизм подстанов-
ки вернет пустое значение. Если параметр не пустой, механизм подстановки вер-
нет значение слова, но сам параметр не изменится.

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:+"substitute value if set"}

[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:+"substitute value if set"}
substitute value if set

Получение имен переменных

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

${!префикс*}
${!префикс@}

Эти две формы подстановки возвращают имена существующих переменных, на-
чинающиеся  с  указанного  префикса.  Согласно  документации  bash,  обе  формы
действуют совершенно одинаково. Следующая команда выводит список всех пе-
ременных окружения с именами, начинающимися с BASH:

[me@linuxbox ~]$ echo ${!BASH*}
BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION BASH_COMPLETION_DIR BASH_
LINENO BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION

502

  Глава 34. Строки и числа

Операции со строками

Существует множество форм подстановки, которые можно использовать для ра-
боты со строками. Многие из них особенно хорошо подходят для операций с пу-
тями. Форма

${#параметр}

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

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo "'$foo' is ${#foo} characters long."
'This string is long.' is 20 characters long.

Следующая форма подстановки:

${параметр:смещение}
${параметр:смещение:длина}

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

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string

Если указать отрицательное смещение, его отсчет начнется с конца строки вместо
начала. Обратите внимание, что отрицательному значению должен предшество-
вать пробел, чтобы предотвратить путаницу с формой ${параметр:-слово}. Длина,
если указана, в этом случае не должна быть меньше 0.

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

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo: -5}
long.
[me@linuxbox ~]$ echo ${foo: -5:2}
lo

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

  503

Следующие две формы:

${параметр#шаблон}
${параметр##шаблон}

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

В шаблоне допускается использовать групповые символы: например, те, что ис-
пользуются  в  подстановке  путей.  Эти  две  формы  отличаются  тем,  что  форма  #
удаляет  кратчайшее  совпадение,  тогда  как  форма  ##  удаляет  самое  длинное  со-
впадение.

[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo#*.}
txt.zip
[me@linuxbox ~]$ echo ${foo##*.}
zip

Следующие две формы:

${параметр%шаблон}
${параметр%%шаблон}

действуют  так  же,  как  формы  #  и  ##,  представленные  выше,  но  удаляют  текст
с конца строки, содержащейся в параметре.

[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo%.*}
file.txt
[me@linuxbox ~]$ echo ${foo%%.*}
file

Следующие формы:

${параметр/шаблон/строка}
${параметр//шаблон/строка}
${параметр/#шаблон/строка}
${параметр/%шаблон/строка}

выполняют  поиск  с  заменой  в  содержимом  указанного  параметра.  Если  в  пара-
метре  будет  найдено  совпадение  с  шаблоном,  который  может  содержать  груп-
повые  символы,  это  совпадение  будет  заменено  содержимым  указанной  строки.
Первая форма заменит только первое совпадение с шаблоном. Форма // заменит

504

  Глава 34. Строки и числа

все найденные совпадения. Форма /# выполняет замену, только если совпадение
с шаблоном найдено в самом начале строки, а форма /% выполняет замену, только
если совпадение найдено в конце строки. Часть /строка можно опустить, и тогда
совпавший фрагмент будет удален.

[me@linuxbox ~]$ foo=JPG.JPG
[me@linuxbox ~]$ echo ${foo/JPG/jpg}
jpg.JPG
[me@linuxbox ~]$ echo ${foo//JPG/jpg}
jpg.jpg
[me@linuxbox ~]$ echo ${foo/#JPG/jpg}
jpg.JPG
[me@linuxbox ~]$ echo ${foo/%JPG/jpg}
JPG.jpg

Механизм подстановки параметров — ценный инструмент. Его возможности для
работы со строками можно использовать вместо других широко используемых
команд, таких как sed и cut. Применение механизма подстановки способствует
увеличению  производительности  сценария  за  счет  отсутствия  необходимости
выполнять  внешние  программы.  Например,  изменим  программу  longest-word
из  предыдущей  главы,  задействовав  подстановку  параметра  ${#j}  взамен  под-
становки команды $(echo $j | wc -c), которая к тому же выполняется в подобо-
лочке:

#!/bin/bash

# longest-word3 : поиск самой длинной строки в файле

for i; do
      if [[ -r "$i" ]]; then
            max_word=
            max_len=
            for j in $(strings $i); do
                  len="${#j}"
                  if (( len > max_len )); then
                        max_len="$len"
                        max_word="$j"
                  fi
            done
            echo "$i: '$max_word' ($max_len characters)"
      fi
      shift
done

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

  505

Далее, сравним эффективность двух версий с помощью команды time:

[me@linuxbox ~]$ time longest-word2 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38 characters)

real 0m3.618s
user 0m1.544s
sys 0m1.768s
[me@linuxbox ~]$ time longest-word3 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38 characters)

real 0m0.060s
user 0m0.056s
sys 0m0.008s

Первоначальной  версии  потребовалось  3,618  секунды,  чтобы  просканировать
текстовый  файл,  тогда  как  новой  версии,  использующей  механизм  подстановки
параметров, понадобилось всего 0,06 секунды — весьма существенное улучшение.

Преобразование регистра символов

bash  поддерживает  четыре  подстановки  параметров  и  два  варианта  команды
declare для преобразования регистра символов в строках.

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

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

Для нормализации строк — приведения их к верхнему или нижнему регистру —
можно использовать команду  declare. C помощью  declare мы сможем гаранти-
ровать, что переменная всегда будет содержать строки в желаемом формате, неза-
висимо от их первоначального вида.

506

  Глава 34. Строки и числа

#!/bin/bash

# ul-declare: демонстрация преобразования регистра символов с использованием
declare

declare -u upper
declare -l lower

if [[ $1 ]]; then
      upper="$1"
      lower="$1"
      echo "$upper"
      echo "$lower"
fi

В  предыдущем  сценарии  мы  использовали  declare  для  создания  двух  перемен-
ных, upper и lower. Затем мы присвоили им значение первого аргумента команд-
ной строки (позиционного параметра $1) и вывели их на экран.

[me@linuxbox ~]$ ul-declare aBc
ABC
abc

Как видите, аргумент командной строки (aBc) был нормализован.

Кроме команды declare поддерживаются также четыре подстановки параметров,
выполняющие преобразование символов в  верхний/нижний регистр. Они пере-
числены в табл. 34.1.

Таблица 34.1. Операции подстановки параметров, выполняющие преобразование
регистра символов

Формат

Результат

${параметр,,шаблон}

Возвращает значение указанного параметра после преобра-
зования всех символов в нижний регистр. Необязательный
шаблон можно использовать для ограничения символов,
подлежащих преобразованию (например [A-F]). Подробное
описание шаблонов можно найти в странице справочного
руководства (man) для команды bash

${параметр,шаблон}

Возвращает значение указанного параметра после преобра-
зования в нижний регистр только первого символа

${параметр^^шаблон}

Возвращает значение указанного параметра после преоб-
разования всех символов в верхний регистр

${параметр^шаблон}

Возвращает значение указанного параметра после преобра-
зования в верхний регистр только первого символа

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

  507

Работу этих операций подстановки демонстрирует следующий сценарий:

#!/bin/bash

# ul-param: демонстрация преобразования регистра символов
            с использованием подстановки параметров

if [[ "$1" ]]; then
      echo "${1,,}"
      echo "${1,}"
      echo "${1^^}"
      echo "${1^}"
fi

Результат выполнения этого сценария:

[me@linuxbox ~]$ ul-param aBc
abc
aBc
ABC
ABc

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

Вычисление и подстановка
арифметических выражений

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

$((выражение))

где выражение — это любое допустимое арифметическое выражение.

Этот вид подстановки тесно связан с составной командой (( )), использовавшей-
ся в главе 27 для вычисления арифметических выражений (оценки истинности).

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

508

  Глава 34. Строки и числа

Основание системы счисления

В главе 9 мы познакомились с восьмеричными (в системе счисления с основани-
ем  8)  и  шестнадцатеричными  (в  системе  счисления  с  основанием  16)  числами.
В арифметических выражениях командная оболочка позволяет использовать це-
лочисленные константы в системах счисления с любым основанием. В табл. 34.2
показаны формы записи чисел с указанием основания системы счисления.

Таблица 34.2. Определение основания системы счисления

Форма записи

Описание

Число

0число

По умолчанию числа без упоминания системы счисления ин-
терпретируются как десятичные числа (в системе счисления
с основанием 10)

В арифметических выражениях числа, начинающиеся с нуля,
интерпретируются как восьмеричные (в системе счисления
с основанием 8)

0xчисло

Форма записи шестнадцатеричных чисел

основание#число

Число в системе счисления с указанным основанием

Несколько примеров:

[me@linuxbox ~]$ echo $((0xff))
255
[me@linuxbox ~]$ echo $((2#11111111))
255

В  этих  примерах  выводится  значение  шестнадцатеричного  числа  ff  (наиболь-
шее двухзначное число) и наибольшее восьмизначное двоичное число (в системе
счисления с основанием 2).

Унарные операторы

Оболочка поддерживает два унарных оператора, + и -, используемых для обозна-
чения положительных и отрицательных чисел соответственно.

Простая арифметика

В табл. 34.3 перечислены обычные арифметические операторы.

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

  509

Таблица 34.3. Арифметические операторы

Оператор

Описание

+

-

*

/

**

%

Сложение

Вычитание

Умножение

Целочисленное деление

Степень числа

Деление по модулю (остаток от целочисленного деления)

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

Поскольку  оболочка  поддерживает  только  арифметические  операции  с  целыми
числами, результатом деления всегда будет целое число:

[me@linuxbox ~]$ echo $(( 5 / 2 ))
2

Это обстоятельство увеличивает важность операции определения остатка от де-
ления:

[me@linuxbox ~]$ echo $(( 5 % 2 ))
1

Используя операторы деления и деления по модулю, можно определить, что деле-
ние 5 на 2 дает в результате 2 с остатком 1.

Вычисление остатка от деления удобно использовать в циклах. Это позволяет вы-
полнять в цикле определенные операции с заданным интервалом. В примере ниже
выводится строка чисел, в которой выделяются числа, кратные 5:

#!/bin/bash

# modulo : демонстрация оператора деления по модулю

for ((i = 0; i  " "$i"
      else

510

  Глава 34. Строки и числа

            printf "%d " "$i"
      fi
done
printf "\n"

Запустив этот сценарий, вы получите следующий результат:

[me@linuxbox ~]$ modulo
 1 2 3 4  6 7 8 9  11 12 13 14  16 17 18 19 

Присваивание

Хотя на данном этапе это не очевидно, тем не менее арифметические выражения
могут  выполнять  операцию  присваивания.  Мы  уже  выполняли  присваивание
много раз, хотя и в других контекстах. Каждый раз, передавая переменной число,
мы выполняем присваивание. То же самое можно делать в  арифметических вы-
ражениях:

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo $foo
[me@linuxbox ~]$ if (( foo = 5 ));then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ echo $foo
5

В примере выше мы сначала присвоили переменной foo пустое значение и прове-
рили, что она действительно получила пустое значение. Далее выполнили коман-
ду if с составной командой (( foo = 5 )). Эта команда имеет два интересных аспек-
та: (1) она присваивает значение 5 переменной foo и (2) оценивает ее значение как
истинное, потому что присваивание прошло успешно.

ПРИМЕЧАНИЕ

Важно  запомнить  значение  оператора  =  в  примере  выше.  Одиночный  знак  =
выполняет присваивание: выражение foo = 5 говорит: «Сделать значение пере-
менной foo равным 5». Двойной знак == определяет эквивалентность: выражение
foo == 5 говорит: «Переменная foo равна 5?» Это обстоятельство может вызывать
путаницу, потому что команда test интерпретирует одиночный знак = как опе-
ратор сравнения строк. Это еще одна причина предпочесть более современные
составные команды [[ ]] и (( )) вместо test.

В  дополнение  к  оператору  =  командная  оболочка  поддерживает  еще  несколько
очень полезных операторов присваивания, перечисленных в табл. 34.4.

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

  511

Таблица 34.4. Операторы присваивания

Форма записи

Описание

параметр = значение

Простое присваивание. Присваивает указанное значение
указанному параметру

параметр += значение

Присваивание со сложением. Эквивалентно выражению
параметр = параметр + значение

параметр -= значение

Присваивание с вычитанием. Эквивалентно выражению
параметр = параметр - значение

параметр *= значение

Присваивание с умножением. Эквивалентно выражению
параметр = параметр × значение

параметр /= значение

Присваивание с целочисленным делением. Эквивалентно
выражению параметр = параметр ÷ значение

параметр %= значение

Присваивание с делением по модулю. Эквивалентно выра-
жению параметр = параметр % значение

параметр++

параметр--

++параметр

--параметр

Постинкремент переменной. Эквивалентно выражению па-
раметр = параметр + 1. (Но см. пояснение ниже.)

Постдекремент переменной. Эквивалентно выражению па-
раметр = параметр - 1

Преинкремент переменной. Эквивалентно выражению пара-
метр = параметр + 1

Предекремент переменной. Эквивалентно выражению пара-
метр = параметр - 1

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

Операторы  инкремента  и  декремента  могут  находиться  перед  параметром  или
после  него.  Хотя  в  обоих  случаях  они  увеличивают  или  уменьшают  значение
параметра на 1, тем не менее их местоположение играет важную роль. Если опе-
ратор  помещается  перед  параметром,  сначала  выполняется  операция  инкремен-
та  (или  декремента)  и  только  потом  возвращается  измененное  значение  пара-
метра.  Если  оператор  помещается  за  параметром,  операция  выполняется  после

512

  Глава 34. Строки и числа

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

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((foo++))
1
[me@linuxbox ~]$ echo $foo
2

Если присвоить переменной foo значение 1 и затем увеличить ее значение с помо-
щью оператора ++, следующего за именем переменной, выражение вернет прежнее
значение  1  переменной  foo.  Однако  если  вывести  значение  переменной  второй
раз, мы увидим увеличенное значение. Если поместить оператор ++ перед параме-
тром, мы получим более ожидаемый результат:

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((++foo))
2
[me@linuxbox ~]$ echo $foo
2

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

Операторы ++ и -- часто используются совместно с циклами. Внесем некоторые
улучшения в сценарий, демонстрирующий применение оператора деления по мо-
дулю, чтобы немного сократить его:

#!/bin/bash

# modulo2 : демонстрация оператора деления по модулю

for ((i = 0; i  " "$i"
      else
            printf "%d " "$i"
      fi
done
printf "\n"

Битовые операции

Командной оболочкой поддерживается класс операторов, которые манипулируют
числами не совсем обычным способом. Эти операторы действуют на уровне битов.

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

  513

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

Таблица 34.5. Битовые операторы

Оператор

Описание

~

>

&

|

^

Поразрядное отрицание. Изменяет значения всех битов в числе на противо-
положные

Поразрядный сдвиг влево. Сдвигает все биты в числе на один разряд влево

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

Поразрядная операция И (AND). Выполняет операцию И над всеми битами
двух чисел

Поразрядная операция ИЛИ (OR). Выполняет операцию ИЛИ над всеми
битами двух чисел

Поразрядная операция ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR). Выполняет операцию
ИСКЛЮЧАЮЩЕЕ ИЛИ над всеми битами двух чисел

Обратите внимание, что для всех битовых операторов, кроме поразрядного отри-
цания,  существуют  соответствующие  операторы  присваивания  (например,  =



==

!=

&&

||

Меньше или равно

Больше или равно

Меньше

Больше

Равно

Не равно

Логическое И (AND)

Логическое ИЛИ (OR)

выражение1?выражение2:
выражение3

Тернарный (трехместный) оператор сравнения. Если вы-
ражение1 вернет ненулевое значение (арифметическую
истину), будет выполнено выражение2, иначе — выражение3

При использовании логических операторов в арифметических выражениях дей-
ствуют  следующие  правила:  выражение,  возвращающее  0,  считается  ложным,
а выражение, возвращающее ненулевое значение, — истинным. Составная коман-
да (( )) отображает результаты в обычные для командной оболочки коды завер-
шения:

[me@linuxbox ~]$ if ((1)); then echo "true"; else echo "false"; fi
true
[me@linuxbox ~]$ if ((0)); then echo "true"; else echo "false"; fi
false

Самым  странным  из  логических  операторов  выглядит  тернарный  (или  трех-
местный) оператор. Этот оператор (заимствованный из языка программирова-
ния C) самостоятельно выполняет логическую проверку. Его можно использо-
вать  вместо  инструкции  if/then/else.  Он  оперирует  тремя  арифметическими
выражениями (этот оператор не работает со строками), и если первое выраже-
ние оценивается как истинное (то есть возвращает ненулевое значение), выпол-
няется второе выражение. Иначе выполняется третье выражение. Опробуем его
в командной строке.

[me@linuxbox ~]$ a=0
[me@linuxbox ~]$ ((a&2
}

# Убедиться, что аргумент является каталогом
if [[ ! -d "$1" ]]; then
      usage
      exit 1
fi

# Инициализировать массив
for i in {0..23}; do hours[i]=0; done

# Собрать данные
for i in $(stat -c %y "$1"/* | cut -c 12-13); do
      j="${i#0}"
      ((++hours[j]))
      ((++count))
done

# Вывести данные
echo -e "Hour\tFiles\tHour\tFiles"
echo -e "----\t-----\t----\t-----"
for i in {0..11}; do
      j=$((i + 12))
      printf "%02d\t%d\t%02d\t%d\n" \
            "$i" \
            "${hours[i]}" \
            "$j" \
            "${hours[j]}"
done
printf "\nTotal files = %d\n" $count

Сценарий  состоит  из  одной  функции  (usage)  и  основного  тела  с  четырьмя  раз-
делами. В первом разделе проверяется, является ли аргумент командной строки

524

  Глава 35. Массивы

именем каталога. Если нет, сценарий выводит сообщение с информацией о поряд-
ке использования и завершается.

Второй раздел инициализирует массив hours. Для этого каждому элементу мас-
сива  присваивается  значение  0.  Массивы  не  требуют  специальной  инициали-
зации перед использованием, но нашему сценарию важно, чтобы в массиве не
оставалось  элементов  с  пустыми  значениями.  Обратите  внимание  на  необыч-
ный  способ  организации  цикла.  Используя  подстановку  в  фигурных  скобках
({0..23}),  мы  смогли  без  труда  сгенерировать  последовательность  слов  для
коман ды for.

Следующий раздел осуществляет сбор данных, вызывая программу stat для каж-
дого файла в каталоге. С помощью cut из результата извлекается двузначный час.
Внутри цикла выполняется удаление ведущих нулей из поля с часом, потому что
иначе  командная  оболочка  попытается  (и,  в  конечном  счете,  потерпит  неудачу)
интерпретировать  значения  с  00  по  09  как  восьмеричные  числа  (см.  табл.  34.1).
Далее сценарий увеличивает на единицу значение элемента массива, соответству-
ющего  полученному  часу  дня.  Наконец,  будет  увеличен  счетчик  (count),  храня-
щий общее число файлов в каталоге.

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

Операции с массивами

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

Вывод содержимого всего массива

Для доступа к каждому элементу массива используются индексы * и @. Так же как
и в случае с позиционными параметрами, индекс @ имеет большую практическую
ценность. Например:

[me@linuxbox ~]$ animals=("a dog" "a cat" "a fish")
[me@linuxbox ~]$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat

Операции с массивами

  525

a
fish
[me@linuxbox ~]$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
[me@linuxbox ~]$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish

Мы создали массив animals и сохранили в нем три строки по два слова в каждой.
Затем  выполнили  четыре  цикла,  чтобы  посмотреть,  как  выполняется  разбиение
содержимого массива на слова. Инструкции ${animals[*]} и ${animals[@]} дей-
ствуют идентично, если они не заключены в кавычки. Индекс * возвращает содер-
жимое массива, разбитое на отдельные слова, тогда как индекс @ возвращает три
«слова», соответствующие «истинному» содержимому массива.

Определение числа элементов в массиве

Определить число элементов в массиве, так же как длину строки, можно с помо-
щью механизма подстановки параметров. Например:

[me@linuxbox ~]$ a[100]=foo
[me@linuxbox ~]$ echo ${#a[@]} # число элементов в массиве
1
[me@linuxbox ~]$ echo ${#a[100]} # длина элемента с индексом 100
3

Мы создали массив a и записали строку foo в элемент с индексом 100. Далее с по-
мощью  механизма  подстановки  параметров  мы  определили  длину  массива,  ис-
пользуя  при  этом  форму  записи  индекса  @.  Затем  определили  длину  элемента
с  индексом  100,  содержащего  строку  foo.  Обратите  внимание,  что  даже  притом,
что мы присвоили строку элементу с индексом 100, bash сообщает, что в массиве
имеется только один элемент. Такое поведение необычно для тех языков, в кото-
рых  неиспользуемые  элементы  массива  (элементы  с  индексами  0–99)  были  бы
инициализированы пустыми значениями и учитывались бы при определении раз-
мера массива. В bash элемент массива существует, только если ему присвоено зна-
чение, независимо от индекса.

526

  Глава 35. Массивы

Поиск используемых индексов

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

${!массив[*]}
${!массив[@]}

где  массив  —  это  имя  переменной-массива.  Как  и  в  других  случаях  использова-
ния * и @ в операциях подстановки, форма @, заключенная в кавычки, оказывается
наиболее  полезной,  так  как  выполняет  подстановку  нераздробленных  значений
элементов:

[me@linuxbox ~]$ foo=([2]=a [4]=b [6]=c)
[me@linuxbox ~]$ for i in "${foo[@]}"; do echo $i; done
a
b
c
[me@linuxbox ~]$ for i in "${!foo[@]}"; do echo $i; done
2
4
6

Добавление элементов в конец массива

Знание количества элементов в массиве не поможет, если понадобится добавить
значения в конец массива, потому что значения, возвращаемые индексами * и @, не
сообщают наибольший занятый индекс в массиве. К счастью, командная оболочка
предоставляет  собственное  решение.  Оператор  присваивания  +=  автоматически
добавляет значения в конец массива. Ниже мы записали три значения в массив,
а затем добавили в конец еще три.

[me@linuxbox ~]$ foo=(a b c)
[me@linuxbox ~]$ echo ${foo[@]}
a b c
[me@linuxbox ~]$ foo+=(d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f

Сортировка массива

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

Операции с массивами

  527

#!/bin/bash

# array-sort : сортировка массива

a=(f e d c b a)

echo "Original array: ${a[@]}"
a_sorted=($(for i in "${a[@]}"; do echo $i; done | sort))
echo "Sorted array: ${a_sorted[@]}"

Если запустить этот сценарий, он выведет следующее:

[me@linuxbox ~]$ array-sort
Original array: f e d c b a
Sorted array: a b c d e f

Сценарий  копирует  содержимое  исходного  массива  (a)  во  второй  массив  (a_
sorted), выполняя трюк с подстановкой команды. Этот простой прием можно ис-
пользовать для выполнения самых разных операций с массивами, просто изменяя
состав конвейера.

Удаление массива

Удалить массив можно с помощью команды unset:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox ~]$ unset foo
[me@linuxbox ~]$ echo ${foo[@]}

[me@linuxbox ~]$

Командой unset можно также удалить единственный элемент массива:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox ~]$ unset 'foo[2]'
[me@linuxbox ~]$ echo ${foo[@]}
a b d e f

В этом примере мы удалили третий элемент массива, с индексом 2. Не забывайте,
что индексация элементов массива начинается с 0, а не с 1! Отметьте также, что
элемент массива нужно заключить в кавычки, чтобы предотвратить подстановку
путей оболочкой.

528

  Глава 35. Массивы

Интересно отметить, что присваивание пустого значения массиву не уничтожает
его содержимое:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo[@]}
b c d e f

Любая  ссылка  на  переменную-массив  без  индекса  возвращает  элемент  с  индек-
сом 0:

[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f
[me@linuxbox ~]$ foo=A
[me@linuxbox ~]$ echo ${foo[@]}
A b c d e f

Ассоциативные массивы

bash  версии  4.0  и  выше  поддерживает  ассоциативные  массивы.  Для  индексации
элементов в ассоциативных массивах используются строки, а не целые числа, как
в обычных массивах. Эта возможность открывает новые подходы к управлению
данными. Например, можно создать массив colors и в качестве индексов исполь-
зовать названия цветов:

declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

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

echo ${colors["blue"]}

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

Заключение

  529

Заключение

Если на странице справочного руководства (man) для bash выполнить поиск сло-
ва array, можно найти множество его упоминаний, где описываются приемы рабо-
ты с переменными-массивами. Большая часть этих описаний довольно туманна,
но иногда они содержат весьма полезные сведения. Фактически массивы недоста-
точно широко используются в программировании на языке командной оболочки,
в основном потому, что традиционные командные оболочки для Unix (такие, как
sh)  не  поддерживают  их.  Об  этом  недостатке  остается  только  сожалеть,  потому
что  массивы  очень  популярны  в  других  языках  программирования  и  являются
мощным инструментом, позволяющим решать многие задачи программирования.

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

for ((выражение1; выражение2; выражение3))

36 Экзотика

В этой главе, завершающей наше путешествие, обратимся к совершенно случай-
ным темам. Несмотря на то что в предыдущих главах мы рассмотрели основные
темы, немало особенностей bash остались неохваченными. Многие из них плохо
освещены в документации и полезны в основном для тех, кто занимается интегра-
цией bash в дистрибутивы Linux. Но есть среди них и такие, которые, хотя и ис-
пользуются нечасто, могут пригодиться при решении некоторых задач програм-
мирования. Их-то мы и рассмотрим.

Группы команд и подоболочки

bash поддерживает возможность группировки команд. Воспользоваться ею мож-
но двумя способами: либо путем группировки команд, либо путем применения по-
доболочки. Ниже приводятся примеры синтаксиса обоих подходов.

Группа команд:

{ команда1; команда2; [команда3; ...] }

Подоболочка:

(команда1; команда2; [команда3;...])

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

Группы команд и подоболочки

  531

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

ls -l > output.txt
echo "Listing of foo.txt" >> output.txt
cat foo.txt >> output.txt

Выглядит достаточно просто: вывод трех команд перенаправляется в файл с име-
нем output.txt. Воспользовавшись приемом группировки, то же самое можно вы-
разить более кратко:

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt

Подоболочка используется аналогично:

(ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt

Этот прием помог нам сэкономить силы и время на вводе текста сценария, но ис-
тинная  мощь  групп  команд  и  подоболочек  проявляется  в  конвейерах.  Создавая
конвейеры из команд, мы часто сталкиваемся с необходимостью объединения ре-
зультатов нескольких команд в общий поток. Группы команд и подоболочки упро-
щают эту задачу:

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr

Здесь  мы  объединили  вывод  трех  команд  и  передали  его  по  конвейеру  на  вход
команды lpr, чтобы напечатать отчет.

В следующем сценарии мы задействуем группы команд и рассмотрим несколько
приемов,  которые  можно  использовать  в  сочетании  с  ассоциативными  массива-
ми.  Получив  имя  каталога,  сценарий  с  названием  array-2  выводит  список  фай-
лов, находящихся в этом каталоге, с именами пользователей и групп, владеющих
файлами. В конце сценарий выводит общее количество файлов, принадлежащих
каждому  пользователю  и  каждой  группе.  Ниже  приводится  сокращенный  (для
экономии места) вывод сценария для каталога /usr/bin:

[me@linuxbox ~]$ array-2 /usr/bin
/usr/bin/2to3-2.6                      root      root
/usr/bin/2to3                          root      root
/usr/bin/a2p                           root      root
/usr/bin/abrowser                      root      root
/usr/bin/aconnect                      root      root
/usr/bin/acpi_fakekey                  root      root

532

  Глава 36. Экзотика

/usr/bin/acpi_listen                   root      root
/usr/bin/add-apt-repository            root      root
--пропущено--
/usr/bin/zipgrep                       root      root
/usr/bin/zipinfo                       root      root
/usr/bin/zipnote                       root      root
/usr/bin/zip                           root      root
/usr/bin/zipsplit                      root      root
/usr/bin/zjsdecode                     root      root
/usr/bin/zsoelim                       root      root

File owners:
daemon    :    1 file(s)
root      : 1394 file(s)

File group owners:
crontab   :    1 file(s)
daemon    :    1 file(s)
lpadmin   :    1 file(s)
mail      :    4 file(s)
mlocate   :    1 file(s)
root      : 1380 file(s)
shadow    :    2 file(s)
ssh       :    1 file(s)
tty       :    2 file(s)
utmp      :    2 file(s)

Далее следует исходный код сценария (с номерами строк):

 1   #!/bin/bash
 2
 3   # array-2: Используем массивы для подсчета файлов, принадлежащих
     # разным владельцам
 4
 5   declare -A files file_group file_owner groups owners
 6
 7   if [[ ! -d "$1" ]]; then
 8       echo "Usage: array-2 dir" >&2
 9       exit 1
10   fi
11
12   for i in "$1"/*; do
13       owner="$(stat -c %U "$i")"
14       group="$(stat -c %G "$i")"
15       files["$i"]="$i"
16       file_owner["$i"]="$owner"
17       file_group["$i"]="$group"

Группы команд и подоболочки

  533

18       ((++owners[$owner]))
19       ((++groups[$group]))
20   done
21
22   # Вывод списка файлов
23   { for i in "${files[@]}"; do
24       printf "%-40s %-10s %-10s\n" \
25           "$i" "${file_owner["$i"]}" "${file_group["$i"]}"
26   done } | sort
27   echo
28
29   # Вывод списка владельцев
30   echo "File owners:"
31   { for i in "${!owners[@]}"; do
32       printf "%-10s: %5d file(s)\n" "$i" "${owners["$i"]}"
33   done } | sort
34   echo
35
36   # Вывод списка групп
37   echo "File group owners:"
38   { for i in "${!groups[@]}"; do
39       printf "%-10s: %5d file(s)\n" "$i" "${groups["$i"]}"
40   done } | sort

Рассмотрим механику работы этого сценария.

Строка 5: ассоциативные массивы должны создаваться командой declare с пара-
метром -A. Этот сценарий создает пять таких массивов:

  files  хранит  имена  файлов,  найденных  в  каталоге,  индексируется  именами

файлов.

  file_group хранит имена групп, владеющих файлами, индексируется именами

файлов.

  file_owner  хранит  имена  пользователей,  владеющих  файлами,  индексируется

именами файлов.

  groups хранит число файлов, принадлежащих каждой группе, индексируется

именами групп.

  owners хранит число файлов, принадлежащих каждому пользователю, индек-

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

Строки 7–10: эти строки проверяют имя каталога, переданное в позиционном па-
раметре. Если каталог с указанным именем не существует, выводится сообщение,
описывающее порядок использования сценария, после чего сценарий завершает-
ся с кодом 1.

534

  Глава 36. Экзотика

Строки  12–20:  Эти  строки  выполняют  обход  файлов  в  каталоге.  Команды  stat
в строках 13 и 14 извлекают имена пользователя и группы, владеющих файлом,
которые затем сохраняются в соответствующих массивах (строки 16 и 17), с ис-
пользованием имени файла в роли индекса. Само имя файла сохраняется в масси-
ве files (строка 15).

Строки 18–19: количество файлов, принадлежащих пользователю и группе, уве-
личивается на 1.

Строки  22–27:  вывод  списка  файлов.  Вывод  осуществляется  с  использованием
подстановки параметров "${массив[@]}", которая возвращает полный список эле-
ментов массива, каждый из которых интерпретируется как отдельное слово. При
таком подходе не возникает проблем с именами файлов, содержащими пробелы.
Отметьте также, что весь цикл заключен в фигурные скобки, формирующие груп-
пу команд. Это позволяет передать вывод, полученный в цикле, команде sort. Это
важно, потому что операция подстановки в строке 12 возвращает имена файлов
в неотсортированном порядке.

Строки 29–40: эти два цикла реализованы подобно циклу вывода списка файлов
и отличаются только использованием подстановки "${!массив[@]}", которая воз-
вращает не список элементов, а список индексов массива.

Подстановка процессов

Несмотря на внешнее сходство и возможность объединения потоков для последу-
ющего перенаправления, между группами команд и подоболочками существуют
важные отличия. Все команды, входящие в группу, выполняются в текущей обо-
лочке, подоболочка (как можно догадаться из названия) выполняет свои команды
в дочерней копии текущей командной оболочки. Это означает, что в момент запу-
ска подоболочки создается копия окружения текущей оболочки и передается но-
вому экземпляру оболочки. Когда подоболочка завершается, ее копия окружения
уничтожается, соответственно, теряются любые изменения в окружении подобо-
лочки (включая значения переменных). Поэтому если нет прямой необходимости
в  использовании  подоболочки,  предпочтительнее  использовать  группы  команд.
Группы команд выполняются быстрее и требуют меньше памяти.

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

echo "foo" | read
echo $REPLY

Подстановка процессов

  535

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

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

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

(список)

где список — это список команд.

Ниже показано, как использовать подстановку процессов для решения проблемы
с командой read:

read &1
      exit 0
}

exit_on_signal_SIGTERM () {
      echo "Script terminated." 2>&1
      exit 0
}

trap exit_on_signal_SIGINT SIGINT
trap exit_on_signal_SIGTERM SIGTERM

for i in {1..5}; do
      echo "Iteration $i of 5"
      sleep 5
done

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

Если  во  время  выполнения  этого  сценария  пользователь  нажмет  комбинацию
CTRL+C, он увидит следующее:

[me@linuxbox ~]$ trap-demo2
Iteration 1 of 5
Iteration 2 of 5
^CScript interrupted.

Ловушки

  539

ВРЕМЕННЫЕ ФАЙЛЫ

Одним из побудительных мотивов включения обработчиков сигналов в сценарии
является необходимость удаления временных файлов, которые сценарии могут
создавать для хранения промежуточных результатов. Выбор имен для временных
файлов — целое искусство. Традиционно программы в Unix-подобных системах
создают свои временные файлы в каталоге /tmp, общем для всех и предназначен-
ном именно для таких файлов. Однако из-за того, что каталог является общим,
возникает проблема безопасности, особенно остро проявляющаяся в программах,
действующих с привилегиями суперпользователя. Помимо очевидной необходи-
мости установки соответствующих разрешений для файлов, которые могут быть
доступны всем пользователям в системе, важно также давать временным файлам
непредсказуемые имена. Это поможет избежать атак вида гонка за временными
файлами  (temp  race  attack).  Ниже  показан  один  из  способов  создания  непред-
сказуемого (но все еще осмысленного) имени:

tempfile=/tmp/$(basename $0).$$.$RANDOM

Эта  команда  сконструирует  имя  файла  из  имени  программы,  идентификатора
процесса  (PID)  и  случайного  целого  числа.  Но  имейте  в  виду,  что  переменная
командной  оболочки  $RANDOM  возвращает  значения  только  из  диапазона  от  1
до 32 767, не очень большого по компьютерным меркам, поэтому единственного
экземпляра переменной недостаточно, чтобы противостоять заинтересованному
злоумышленнику.

Лучший результат дает программа mktemp (не путайте с функцией mktemp из стан-
дартной библиотеки) — она автоматически выбирает имя и создает временный
файл.  Программа  mktemp  принимает  аргумент  с  шаблоном,  на  основе  которого
конструирует имя файла. Шаблон должен включать последовательность симво-
лов X, которые будут заменены соответствующим числом случайных букв и цифр.
Чем длиннее последовательность из символов X, тем длиннее последовательность
случайных символов. Например:

tempfile=$(mktemp /tmp/foobar.$$.XXXXXXXXXX)

Эта команда создаст временный файл и сохранит его имя в переменной tempfile.
Символы X в шаблоне будут заменены случайными буквами и цифрами, соответ-
ственно, окончательное имя файла (которое в данном примере включает также
значение специального параметра $$, возвращающего идентификатор процесса)
может выглядеть, например, так:

/tmp/foobar.6593.UOZuvM6654

В сценариях, предназначенных для запуска рядовыми пользователями, разумнее
отказаться  от  использования  каталога  /tmp  и  создать  каталог  для  временных
файлов в домашнем каталоге пользователя, например, так:

[[ -d $HOME/tmp ]] || mkdir $HOME/tmp

540

  Глава 36. Экзотика

Асинхронное выполнение с командой wait

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

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

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

#!/bin/bash

# async-parent : пример асинхронного выполнения (родитель)

echo "Parent: starting..."

echo "Parent: launching child script..."
async-child &
pid=$!
echo "Parent: child (PID= $pid) launched."

echo "Parent: continuing..."
sleep 2

echo "Parent: pausing to wait for child to finish..."
wait $pid

echo "Parent: child is finished. Continuing..."
echo "Parent: parent is done. Exiting."

и дочерний сценарий:

#!/bin/bash

# async-child : пример асинхронного выполнения (потомок)

Именованные каналы

  541

echo "Child: child is running..."
sleep 5
echo "Child: child is done. Exiting."

В  этом  примере  дочерний  сценарий  тривиально  прост.  Фактическая  работа  вы-
полняется родителем. Родительский сценарий запускает дочерний сценарий и пе-
реводит его в фоновый режим выполнения. Идентификатор дочернего процесса
сохраняется в переменной pid путем присваивания ей значения параметра $!, ко-
торый всегда содержит идентификатор процесса последнего задания, переведен-
ного в фоновый режим.

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

В  ходе  выполнения  родительский  и  дочерний  сценарии  производят  следующий
вывод:

[me@linuxbox ~]$ async-parent
Parent: starting...
Parent: launching child script...
Parent: child (PID= 6741) launched.
Parent: continuing...
Child: child is running...
Parent: pausing to wait for child to finish...
Child: child is done. Exiting.
Parent: child is finished. Continuing...
Parent: parent is done. Exiting.

Именованные каналы

В большинстве Unix-подобных систем существует возможность создавать файлы
специального  типа,  которые  называются  именованными  каналами  (named  pipe).
Именованные каналы создают соединения между двумя процессами и могут ис-
пользоваться как обычные файлы. Они не пользуются большой популярностью,
но знать о такой возможности и уметь пользоваться ею желательно.

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

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

542

  Глава 36. Экзотика

как  клиент,  посылая  запросы  серверу,  в  ответ  на  которые  сервер  посылает  веб-
страницы.

Именованные  каналы  имеют  некоторое  сходство  с  файлами,  но  на  самом  деле
образуют  буферы,  действующие  по  принципу  очереди:  первым  пришел,  первым
вышел (First-In, First-Out, FIFO). Так же как в случае с обычными (неименован-
ными) каналами, данные записываются с одного конца канала и извлекаются из
другого. С применением именованных каналов можно, например, выполнять сле-
дующие команды:

процесс1 > именованный_канал

и

процесс2  pipe1

После нажатия клавиши ENTER появится ощущение, что команда «зависла». Это
объясняется  тем,  что  с  другого  конца  канала  данные  еще  не  были  прочитаны.

Заключение

  543

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

[me@linuxbox ~]$ cat < pipe1

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

Заключение

Итак, мы закончили наше путешествие. Единственное, что осталось, — это прак-
тика, практика и еще раз практика. Даже притом, что на своем пути мы охвати-
ли  широкий  круг  вопросов,  в  действительности  мы  лишь  затронули  верхушку
айсберга под названием «командная строка». Существуют еще тысячи программ
командной строки, которые вам предстоит открыть и изучить. Начните свои ис-
следования с каталога /usr/bin, и вы увидите их!

 У. Шоттс

Командная	строка	Linux.	Полное	руководство

2-е	международное	издание

Перевел с английского А. Киселев

Заведующая редакцией
Ведущий редактор
Литературный редактор
Художественный редактор
Корректоры
Верстка

Ю. Сергиенко
К. Тульцева
А. Руденко
В. Мостипан
С. Беляева, Н. Викторова
Л. Егорова

Изготовлено в России. Изготовитель: ООО «Прогресс книга».
Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург,
Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373.

Дата изготовления: 08.2019. Наименование: книжная продукция. Срок годности: не ограничен.

Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12.000 —
Книги печатные профессиональные, технические и научные.

Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01.

Подписано в печать 12.07.19. Формат 70×100/16. Бумага офсетная. Усл. п. л. 43,860. Тираж 1200. Заказ 0000.