Просмотр и редактирование текстовых файлов
Тема 2 показала, как ориентироваться в файловой системе: переходить по каталогам, создавать, копировать и удалять файлы. Теперь мы переходим к содержимому файлов. Большинство файлов, с которыми работает администратор и программист, — это текст: журналы сервера, конфигурационные файлы, скрипты, отчёты, исходные тексты программ. Философия Unix прямо опирается на текст как на универсальный формат данных 4. Поэтому умение быстро посмотреть такой файл, найти в нём нужное место, сравнить две версии и аккуратно исправить строку относится к базовым навыкам работы в терминале.
В теме рассматриваются четыре задачи: что на самом деле лежит внутри текстового файла (байты, кодировки, символы конца строки), как просматривать файлы разного размера (cat, less, head, tail, wc), как сравнивать версии (cmp, diff) и как редактировать файл прямо в терминале (nano и vim). Отдельный раздел посвящён одной из самых частых бед при переносе файлов между Windows и Linux — скрипту, который «не запускается» из-за невидимого символа в конце каждой строки. Изложение идёт от байтов к инструментам: сначала становится понятно, что именно видит программа, а затем понятны и команды.
Примеры выполнены в оболочке Bash на Ubuntu 24.04 с локалью UTF-8. Вывод команд приведён без изменений, за исключением меток времени в выводе diff -u, которые сокращены.
Текстовый файл на уровне байтов
Что такое текстовый файл
Для компьютера нет «текстовых» и «нетекстовых» файлов в том смысле, в каком они различаются для человека. Файл — это последовательность байтов, то есть чисел от 0 до 255. Текстовым файл делает договорённость о том, как эти числа интерпретировать: какое число означает букву, какое — пробел, какое — конец строки. Программа, которая открывает файл, должна знать эту договорённость так же, как читатель знает алфавит. Если программа прочитает байты по неверной договорённости, на экране появится набор неразборчивых символов, но файл при этом не будет повреждён.
Текстовый файл состоит из строк. Строка — это последовательность символов, завершающаяся специальным символом конца строки. В Unix-подобных системах таким символом служит перевод строки (англ. line feed, код 10, в записи \n). Именно поэтому утилиты Linux считают строки по числу переводов строки, а не по видимым переносам на экране. Файл, в конце которого нет перевода строки, формально содержит на одну строку меньше, чем видно глазом; мы вернёмся к этому в разделе о счётчиках.
Важно различать символ и байт. Символ — это единица текста: буква, цифра, знак препинания, пробел. Байт — единица хранения. Для латинских букв и цифр в простейших кодировках один символ занимает один байт, а для кириллицы и многих других алфавитов — два и более. Поэтому размер файла в байтах не равен количеству символов в тексте, и обе величины полезно уметь измерять.
Байты и их просмотр: od и xxd
Чтобы увидеть содержимое файла на уровне байтов, используются два инструмента: od (англ. octal dump, из пакета coreutils) и xxd (из пакета vim). Первый выводит байты в восьмеричной записи, второй — в шестнадцатеричной, рядом с печатаемыми символами. Создадим файл с одним словом на русском языке и переводом строки:
printf 'Привет\n' > hello.txt
cat hello.txt
Привет
Теперь посмотрим, какие байты записаны в файле:
xxd hello.txt
00000000: d09f d180 d0b8 d0b2 d0b5 d182 0a .............
В строке вывода слева направо стоят смещение от начала файла в шестнадцатеричной записи (00000000), затем шестнадцать байтов группами по два (здесь заполнены не все позиции), и справа — их печатаемое представление. Точка означает байт, который нельзя показать как символ. Видно, что в файле тринадцать байтов, хотя слово состоит из шести букв. Тот же файл в восьмеричной записи показывает od -c:
od -c hello.txt
0000000 320 237 321 200 320 270 320 262 320 265 321 202 \n
0000015
Опция -c печатает байты как символы там, где это возможно, и как управляющие последовательности вроде \n. Числа в начале строк — смещения в восьмеричной записи, а последняя строка 0000015 — полная длина файла, также в восьмеричной записи: это тринадцать байтов. Сравнение с латинским текстом помогает увидеть разницу:
printf 'Hi\n' > ascii.txt
xxd ascii.txt
00000000: 4869 0a Hi.
Буквы H и i занимают по одному байту (0x48 и 0x69), а перевод строки — байт 0x0a. Такой файл занимает ровно столько байтов, сколько символов, и именно поэтому старые программы считали «символы» и «байты» одним и тем же.
Просмотр байтов — не экзотика, а рабочий инструмент. Когда файл выглядит правильно, но программа его не понимает, первым делом стоит посмотреть его xxd: лишний невидимый байт, неожиданный пробел в начале, знак порядка байтов (BOM) в начале файла — всё это видно только на уровне байтов.
Кодировки: ASCII, UTF-8 и кириллица
Кодировка — это соглашение, которое сопоставляет символы и числа. Самая старая широко распространённая кодировка — ASCII (англ. American Standard Code for Information Interchange). Она использует семь бит и содержит 128 символов: латинские буквы, цифры, знаки препинания и управляющие символы вроде перевода строки. Кириллица в ASCII отсутствует, поэтому для русского текста были придуманы национальные кодировки. В русскоязычных Windows долгое время использовалась кодировка CP1251, а в старых системах — KOI8-R и CP866.
Современный стандарт — Unicode, в котором каждому символу любого алфавита присвоен номер (кодовая точка). Способ записи этих номеров в байты называется кодировкой. Самая распространённая из них — UTF-8. Её главное свойство: символы из ASCII записываются одним байтом, как и раньше, поэтому любой латинский текст в ASCII является корректным текстом в UTF-8. Символы кириллицы занимают по два байта; двумя байтами записываются кодовые точки от U+0080 до U+07FF (кириллица, греческий, арабский алфавиты), а иероглифы и многие другие символы занимают три или четыре байта. Посмотрим на это на примере слова «Привет»:
python3 -c "s = open('hello.txt', encoding='utf-8').read(); print(len(s), [hex(ord(c)) for c in s[:2]])"
7 ['0x41f', '0x440']
Программа сообщает, что строка состоит из семи символов (шесть букв и перевод строки), а первые два символа имеют кодовые точки U+041F («П») и U+0440 («р»). Переменная s здесь получает символы, потому что Python декодирует файл по указанной кодировке. Утилиты командной строки декодировать текст не умеют в общем случае — они работают с байтами, и количество символов им нужно выяснять отдельно. Команда wc (мы разберём её ниже) делает это по опции -m:
wc -c -m hello.txt
7 13 hello.txt
Здесь -m — число символов, -c — число байтов. Строка «Привет» с переводом строки содержит семь символов и тринадцать байтов, поскольку шесть букв кириллицы по два байта каждая дают двенадцать байтов, а перевод строки — один.
Рисунок показывает, как распределяются байты между символами этой строки: буквы кириллицы занимают по два байта, а перевод строки — один.
Та же строка в кодировке CP1251 будет иметь другие байты. Преобразуем её утилитой iconv:
printf 'Привет\n' | iconv -f UTF-8 -t CP1251 > cp1251.txt
xxd cp1251.txt
file cp1251.txt
00000000: cff0 e8e2 e5f2 0a .......
cp1251.txt: ISO-8859 text
Каждая буква теперь занимает один байт (cf вместо d0 9f), а в конце по-прежнему стоит 0a. Команда file определила, что это текст, но не смогла уверенно назвать кодировку: она сравнивает байты с известными шаблонами, и однобайтные кодировки угадываются неоднозначно. Подобные неточности нужно знать заранее: file даёт подсказку, но не гарантию. Если файл из Windows выглядит как «кракозябры» (например, «Ïðèâåò» вместо «Привет»), значит программа читает CP1251 как UTF-8 или наоборот. Исправить это можно перекодированием через iconv, а проверить результат — через xxd и file.
Перед тем как переходить дальше, полезно зафиксировать практическое правило: в современных системах и в учебных материалах курса все файлы сохраняются в UTF-8. Если текст получен из другой кодировки, его переводят в UTF-8 однократно, а не пытаются «подправить» на глаз.
Конец строки: LF и CRLF
Символ перевода строки — не единственный вариант. Исторически разные системы договорились о конце строки по-разному. Unix и его наследники (Linux, macOS начиная с версии 10.0, BSD) используют один байт — LF (код 0x0a, \n). Системы семейства DOS и Windows используют два байта — CR и LF (\r\n, коды 0x0d и 0x0a). Название CRLF образовано от англ. carriage return (возврат каретки) и line feed. Эта особенность восходит к печатным машинкам: возврат каретки возвращал головку в начало строки, а перевод строки двигал бумагу вниз. Unix сократил эту последовательность до одного символа, а DOS сохранил обе.
Проверим на двух файлах с одинаковым текстом:
printf 'первая\nвторая\nтретья\n' > lf.txt
printf 'первая\r\nвторая\r\nтретья\r\n' > crlf.txt
cat -A lf.txt
cat -A crlf.txt
M-PM-?M-PM-5M-QM-^@M-PM-2M-PM-0M-QM-^O$
M-PM-2M-QM-^BM-PM->M-QM-^@M-PM-0M-QM-^O$
M-QM-^BM-QM-^@M-PM-5M-QM-^BM-QM-^LM-QM-^O$
M-PM-?M-PM-5M-QM-^@M-PM-2M-PM-0M-QM-^O^M$
M-PM-2M-QM-^BM-PM->M-QM-^@M-PM-0M-QM-^O^M$
M-QM-^BM-QM-^@M-PM-5M-QM-^BM-QM-^LM-QM-^O^M$
Опция -A команды cat делает невидимые символы видимыми: перевод строки обозначается знаком $, возврат каретки — ^M, а байты не-ASCII символов выводятся в виде M- с последующим символом (это обозначение для старшего бита). Последовательности вроде M-PM-? — это способ показать два байта кодировки UTF-8 для одной кириллической буквы. Различие между файлами видно в конце каждой строки: в первом есть только $, во втором — ^M$.
Разницу в размере удобно измерить wc:
wc -l lf.txt crlf.txt
wc -c lf.txt crlf.txt
3 lf.txt
3 crlf.txt
6 total
39 lf.txt
42 crlf.txt
81 total
Число строк одинаково, а размеры отличаются ровно на три байта — по одному лишнему \r в каждой строке. Команда file тоже различает эти файлы:
file lf.txt crlf.txt
lf.txt: Unicode text, UTF-8 text
crlf.txt: Unicode text, UTF-8 text, with CRLF line terminators
Рисунок ниже показывает ту же разницу на уровне байтов для короткого слова и для первой строки скрипта.
Почему это важно для Linux? Большинство программ, которые читают текст, относятся к \r как к обычному символу. В заголовке и в строках текста это почти незаметно: \r оказывается невидимым хвостом строки. Но в местах, где программа ожидает точного совпадения с другой строкой или с именем, появляются ошибки. Разбор такой ошибки составляет раздел 5. Здесь достаточно запомнить: невидимый \r — частая причина сбоев файлов, перенесённых из Windows.
Просмотр содержимого файлов
cat и границы его применения
Самая простая команда просмотра — cat (от англ. concatenate, «сцеплять»); подробный разбор таких команд есть в книге Шоттса 1. Она читает файлы один за другим и выводит их в стандартный поток вывода, то есть на экран. Название показывает её основное назначение — склеивать файлы, а просмотр оказался побочным. Команда подходит для коротких файлов. Для длинного журнала на несколько тысяч строк вывод вылетит за верхний край окна терминала, и прочитать начало будет невозможно: прокрутки для этого недостаточно. Поэтому для больших файлов применяют постраничные просмотрщики, которые рассматриваются в следующем подразделе.
cat полезна, когда нужно увидеть точное содержимое файла, включая невидимые символы. Опция -A уже встречалась в разделе 1: она показывает конец строки и управляющие символы. Опция -n нумерует строки. Пара команд cat -n и cat -A — одно из самых полезных средств диагностики текста.
less: постраничный просмотр
Программа less открывает файл на весь экран, показывает одну страницу и ждёт команд. Название — игра слов: во времена программы more говорили, что «less is more», то есть «меньше — значит больше». Программа less умеет прокручивать вперёд и назад, искать текст, переходить к нужной строке и не загружает весь файл в память целиком, что важно для больших журналов.
Запуск выглядит так:
less app.log
Открывается экран с первыми строками журнала, в самом низу — строка состояния с именем файла и процентом прочитанного. Управление делается клавишами, которые не требуют набора Enter. Основные из них приведены в таблице. Список проверен по справочной странице man less и по работе программы в псевдотерминале.
| Клавиша | Действие |
|---|---|
Пробел или f | страница вперёд |
b | страница назад |
d или u | полстраницы вперёд или назад |
g или G | в начало или в конец файла |
/шаблон и Enter | поиск вперёд по тексту |
n или N | следующее или предыдущее совпадение |
&шаблон и Enter | показать только строки, содержащие шаблон |
F | режим слежения, как у tail -f; выход по Ctrl+C |
h | краткая справка по клавишам |
q | выход из программы |
Поиск в less подсвечивает найденные совпадения инверсным шрифтом. Например, в файле из ста строк последовательность «пробел, G, g, /50, Enter» сначала листает страницу, затем переходит в конец и обратно в начало, а поиск /50 находит первую строку с этим числом. Поиск начинается от текущей позиции и идёт вперёд, поэтому после g он находит первое совпадение в файле, а клавиша n переходит к следующему.
Программу полезно знать и для вывода команд: если результат длинный, его можно передать в less через конвейер, например ls -l /usr/bin | less. Конвейеры подробно рассматриваются в теме 5. Справочник по всем клавишам открывается по h, а выход — по q; полное описание опций и клавиш приведено на справочной странице man less 3.
head и tail
Для быстрого взгляда на начало или конец файла удобнее head и tail. По умолчанию head выводит первые десять строк, tail — последние десять. Число строк задаётся опцией -n. Чтобы показать, как это работает, создадим файл из двенадцати строк:
seq -f 'строка %g' 1 12 > seq.txt
head -n 3 seq.txt
echo ...
tail -n 2 seq.txt
строка 1
строка 2
строка 3
...
строка 11
строка 12
Опция -c задаёт число байтов вместо числа строк. Это полезно, когда нужно заглянуть на уровне байтов в начало файла:
head -c 10 seq.txt | xxd
00000000: d181 d182 d180 d0be d0ba ..........
Первые десять байтов соответствуют первым пяти буквам слова «строка» (по два байта на букву), то есть фрагменту «строк», и граница попала между символами. Если бы граница пришлась на середину двухбайтового символа, head -c вывел бы неполную букву, и терминал показал бы «кракозябру». Это ещё одна причина, по которой -c нужен для диагностики, а не для обычного просмотра текста.
Опция -n +N у tail начинает вывод с заданной строки. Команда tail -n +10 seq.txt выведет строки с десятой по последнюю, то есть «строка 10», «строка 11» и «строка 12». Эта форма удобна, когда нужно пропустить заголовок файла.
tail -f и растущие журналы
Журналы работающих программ растут: новые записи дописываются в конец файла. Чтобы следить за такими записями в реальном времени, используется опция -f (англ. follow). Команда tail -f не завершается сама: она выводит последние строки, а затем продолжает ждать и печатает каждую новую строку, как только она появится. Остановить её можно сочетанием Ctrl+C.
Для демонстрации создадим фоновый процесс, который каждые 0,4 секунды дописывает строку в файл, и одновременно запустим tail -f с ограничением по времени в 1,1 секунды:
(for i in 1 2 3 4; do echo "событие $i" >> live.log; sleep 0.4; done) &
timeout 1.1 tail -f live.log
событие 1
событие 2
событие 3
Команда timeout 1.1 прекращает tail через 1,1 секунды, потому что сам tail -f без ограничения не закончил бы работу. Четвёртая строка появилась бы позже этого момента, поэтому её нет в выводе. Такой способ удобен для тестов: в реальной работе Ctrl+C делает то же самое.
Существует важная тонкость: tail -f следит за открытым файлом, а не за его именем (режим --follow=descriptor). Если журнал ротируется (переименовывается, а на его месте создаётся новый файл), программа продолжит следить за старым, переименованным файлом, который больше не растёт. Для этого случая есть вариант tail -F (то же, что --follow=name --retry), который следит за именем и переоткрывает файл. Работа с системными журналами и их ротацией описана в руководстве по администрированию 6, а настройка ротации программой logrotate разобрана в теме 9.
wc: счётчики строк, слов и символов
Команда wc (от англ. word count) считает строки, слова, символы и байты. Без опций она выводит все четыре значения; отдельные величины выбираются опциями. -l считает строки, то есть переводы строк, -w — слова, разделённые пробелами, -c — байты, -m — символы в текущей кодировке, -L — длину самой длинной строки в символах. Примеры уже встречались в этом разделе, а полное описание опций дано в руководстве GNU Coreutils 2; здесь собраны основные выводы:
wc -l seq.txt
wc -w hello.txt
wc -L hello.txt
12 seq.txt
1 hello.txt
6 hello.txt
Строка «Привет» содержит одно слово и шесть символов в самой длинной строке. Файл из двенадцати строк считается по переводам строк, поэтому, если последняя строка не завершена переводом, wc -l сообщит на одну меньше, чем видно на экране. Это свойство стоит помнить при проверке журналов: строки без завершающего перевода команда wc -l не учитывает.
Сравнение файлов
cmp и diff: два способа сказать «различаются»
Когда нужно понять, одинаковы ли два файла, первым приходит на ум простое сравнение. Утилита cmp сравнивает файлы побайтно и сообщает первое место расхождения. Проверим на двух версиях конфигурации сервера, которые приведены в лабораторной работе. Версия 1 использует порт 8080, версия 2 — порт 8443:
cmp server.v1.conf server.v2.conf
echo "код возврата: $?"
cp server.v1.conf copy.conf
cmp server.v1.conf copy.conf
echo "код возврата: $?"
server.v1.conf server.v2.conf differ: byte 105, line 2
код возврата: 1
код возврата: 0
Выход cmp нельзя прочитать как «да» или «нет» по тексту: программа молчит, если файлы совпадают, и сообщает только первое отличие. Код возврата ($?) содержит итог: 0 означает полное совпадение, 1 — различие. Код возврата — общее свойство программ командной строки, и тема 5 рассмотрит его подробнее.
Команда diff отвечает на вопрос «что именно изменилось». В обычном режиме она выводит команды, которые превращают первый файл во второй:
diff server.v1.conf server.v2.conf
2c2
< # Версия 1
---
> # Версия 2
5c5
< port = 8080
---
> port = 8443
7c7,8
< max_connections = 100
---
> max_connections = 200
> timeout = 30
10c11
< level = info
---
> level = warn
Запись 2c2 означает «строка 2 первого файла заменяется на строку 2 второго» (буква c — от англ. change). Строки с < принадлежат первому файлу, строки с > — второму. Запись 7c7,8 говорит о замене одной строки на две. Этот формат удобен для чтения, но его трудно передать коллегам и программам, поэтому чаще применяют унифицированный формат, описанный далее.
Для простого вопроса «различаются ли файлы» пригодна опция -q: она подавляет вывод деталей и сообщает только факт различия.
diff -q server.v1.conf server.v2.conf
Files server.v1.conf and server.v2.conf differ
Таким образом, cmp даёт место первого различия, diff — полный перечень изменений, а diff -q — ответ на вопрос «совпадают ли». Выбор зависит от задачи: для проверки целостности копии достаточно cmp, для проверки изменений в конфигурации нужен diff.
Формат unified diff
В промышленной практике применяется унифицированный формат, который выводит diff -u. Его понимают редакторы, системы контроля версий и программы применения патчей. Сравним те же два файла:
diff -u server.v1.conf server.v2.conf
--- server.v1.conf <метка времени>
+++ server.v2.conf <метка времени>
@@ -1,11 +1,12 @@
# Конфигурация учебного сервера «Северный узел»
-# Версия 1
+# Версия 2
[server]
-port = 8080
+port = 8443
host = 0.0.0.0
-max_connections = 100
+max_connections = 200
+timeout = 30
[log]
-level = info
+level = warn
file = /var/log/nsu/app.log
Метки времени в заголовке сокращены: в реальном выводе там стоят дата и время изменения файлов. Вывод делится на заголовок и блоки (англ. hunk). Строки --- и +++ указывают первый и второй файл. Строка вида @@ -1,11 +1,12 @@ описывает блок: в первом файле он начинается со строки 1 и занимает 11 строк, во втором — начинается со строки 1 и занимает 12 строк. Далее идут строки блока, и каждая из них начинается с одного из трёх префиксов. Смысл префиксов сведён в таблицу.
| Префикс | Значение |
|---|---|
| пробел | строка есть в обоих файлах без изменений (контекст) |
- | строка есть только в первом файле и удалена |
+ | строка есть только во втором файле и добавлена |
Контекст позволяет понять, где именно произошло изменение: по умолчанию diff -u показывает три строки до и после изменения. Именно поэтому блок начинается не с первой изменённой строки. Изменение строки выглядит как пара «минус — плюс», а добавление новой строки timeout = 30 — как одна строка с плюсом. Строка # Версия 1 заменена на # Версия 2 целиком: программа не выделяет отдельно изменившуюся цифру.
Вывод diff -u можно сохранить в файл и применить к другой копии исходного файла. Такой файл называется патчем. Патчи широко используются в разработке, когда исправление нужно передать коллегам по почте или через систему контроля версий, не передавая весь файл целиком:
diff -u server.v1.conf server.v2.conf > v1-v2.patch
wc -l v1-v2.patch
19 v1-v2.patch
Файл патча короче полной версии только тогда, когда изменений немного. Для больших файлов с мелкими правками патч выигрывает, а для почти полностью переписанных файлов разница между версиями в патче может оказаться длиннее самого файла. Применение патчей (программа patch) выходит за рамки этой темы; здесь важно уметь читать формат и понимать, что строки с минусом удаляются, а строки с плюсом добавляются.
Внимательный читатель заметит, что в списке изменений отсутствует смена кодировки или конца строк. Это свойство diff: программа сравнивает содержимое строк, и если отличаются символы конца строки, это будет показано как отличие всей строки. Для файлов с разными окончаниями строк diff выводит все строки как изменённые, и это служит диагностическим признаком — о нём подробно сказано в разделе 5.
Редакторы: nano и vim
nano: редактор с подсказками
Редактировать текст в терминале можно разными программами. Самая простая из них — nano. Она открывает файл на весь экран, показывает текст и внизу — таблицу подсказок с сочетаниями клавиш. Символ ^ в подсказках означает клавишу Ctrl: запись ^O — это Ctrl+O. Программа ничего не требует от пользователя заранее: набрал текст, сохранил, вышел.
Открытие файла на редактирование выглядит так:
nano s.conf
Основные действия: Ctrl+O — записать файл (после нажатия программа просит подтвердить имя, Enter подтверждает); Ctrl+X — выйти, при несохранённых изменениях nano спросит, сохранить ли их; Ctrl+W — поиск; Ctrl+\ — замена; Ctrl+K — вырезать строку; Ctrl+U — вставить вырезанное; Ctrl+G — справка; Alt+G или Ctrl+_ — переход к строке с заданным номером. Полный список показывает Ctrl+G. Стрелки и Backspace работают так, как ожидается от обычного текстового поля.
Проверим, что сценарий «перейти на третью строку, заменить значение, сохранить и выйти» работает именно так. Исходный файл состоит из трёх строк, на третьей стоит level = info. Клавиши стрелки вниз дважды, Ctrl+E (конец строки), четыре Backspace, слово warn, Ctrl+O и Enter, затем Ctrl+X. Результат:
cat s.conf
[server]
port = 8080
level = warn
Эта последовательность нажатий выполнена в псевдотерминале, поскольку интерактивная программа не может быть записана в обычный вывод. Результат файла при этом такой же, как при работе с клавиатурой.
nano удобен для небольших правок и хорошо подходит для первого знакомства с редактированием. Его недостаток в том, что он не даёт мощных инструментов работы с текстом: повторных замен, перемещения по структуре, сложных правок одной командой. Для этого в Unix-системах традиционно используется vim.
Почему стоит знать vim
Редактор vim (англ. Vi IMproved) — улучшенный наследник vi, созданного Биллом Джоем в 1976 году для системы BSD. Редактор vi входит в стандарт POSIX для системного окружения и установлен практически на любой Unix-подобной машине, включая серверы, куда по сети попадают только через консоль. Если на чужом сервере нет nano, vi почти наверняка есть: в Ubuntu он входит в базовую установку в виде пакета vim-tiny. Программа ориентирована на клавиатуру: руки не покидают основную зону, а операции записываются короткими сочетаниями. Этот стиль описан в классических книгах по Unix, например в работе Кернигана и Пайка 5.
Цена за эффективность — необычная модель работы. В vim одна и та же клавиша в разных ситуациях делает разное: буква x может удалить символ, а может быть набрана как текст. Чтобы не путаться, программа работает в режимах. Режим показывает, что сейчас происходит с клавишами, а переход между режимами выполняется клавишами. Рисунок показывает основные режимы и переходы между ними.
Ключевой вывод из схемы: из любого режима клавиша Esc возвращает в обычный режим, и в обычном режиме клавиши действуют как команды. Новичку полезно запомнить правило: если непонятно, что происходит, нажмите Esc. Программа в комплекте содержит обучающий курс vimtutor (он запускается командой vimtutor и занимает около получаса), который знакомит с основными командами на практике.
Минимальный набор команд vim
Для лабораторной работы и повседневной правки достаточно нескольких команд. Таблица перечисляет их с указанием режима. Запись : означает переход в командную строку, в которой команды завершаются нажатием Enter.
| Клавиши | Режим | Действие |
|---|---|---|
i, a, o | обычный | вставка перед курсором, после курсора, новая строка ниже |
Esc | любой | возврат в обычный режим |
h, j, k, l | обычный | перемещение влево, вниз, вверх, вправо (стрелки тоже работают) |
x | обычный | удалить символ под курсором |
dd | обычный | удалить строку |
u | обычный | отменить последнее действие |
G, gg | обычный | перейти в конец или в начало файла |
/шаблон и Enter, затем n | обычный | поиск вперёд и переход к следующему совпадению |
:w и Enter | командная строка | сохранить файл |
:q и Enter | командная строка | выйти, если нет несохранённых изменений |
:wq и Enter | командная строка | сохранить и выйти |
:q! и Enter | командная строка | выйти без сохранения |
:%s/старое/новое/g и Enter | командная строка | заменить все совпадения во всём файле |
Проверим сценарий, близкий к лабораторной работе: заменить порт во всём файле, добавить строку-комментарий в конец и сохранить. Команды набирались в псевдотерминале, как и в предыдущем примере:
vim -Nu NONE v.conf
Последовательность: Esc, затем :%s/8080/8443/ и Enter, затем G (переход в конец), o (новая строка ниже и вход в режим вставки), набор текста # комментарий, Esc, и :wq с Enter. Результат файла:
cat v.conf
port = 8443
level = info
# комментарий
Опция -N включает современный режим, а -u NONE отключает пользовательскую настройку ~/.vimrc, чтобы результат не зависел от чужих настроек. В лабораторной работе эти опции не нужны, но полезно знать, что поведение vim зависит от настроек, и при проблемах их стоит проверить.
Команда vim поддерживает и пакетный режим, в котором последовательность команд передаётся аргументами и редактор завершается сам. Такой режим используется в скриптах и для проверки этого раздела:
vim -Nes -u NONE -c '%s/info/warn/' -c 'wq' e.conf
cat e.conf
port = 8080
level = warn
Здесь -e запускает vim в режиме Ex (построчный редактор), -s подавляет приглашения, -c выполняет команду, а wq в конце сохраняет файл и выходит. Пакетный режим удобен для автоматических правок, но им не следует пользоваться вместо интерактивного редактирования, пока не понятно, что именно делает каждая команда.
Конфигурационные файлы как текст
Конфигурационные файлы — главный повод для редактирования текста в администрировании. Типичный файл состоит из комментариев (строки, начинающиеся с #), секций в квадратных скобках и пар «параметр = значение». Например, версия 1 конфигурации сервера, использованная в разделе о сравнении, содержит секции [server] и [log]. Программа, которая читает такой файл, понимает его по этой структуре и по точным значениям, поэтому правки должны быть аккуратными: лишний пробел может оказаться значимым, а перенос строки — неверным.
Перед правкой конфигурации полезно сохранить резервную копию (копирование — тема 2) и после правки сравнить результат с копией через diff -u. Такая проверка показывает ровно те строки, которые были изменены, и исключает случайные правки, которые легко не заметить глазом. Если изменилось больше строк, чем предполагалось, это сигнал вернуться к копии.
Разбор ошибки CRLF: «bad interpreter»
Симптом и воспроизведение
Одна из самых распространённых ошибок начинающих: скрипт, скопированный с Windows-компьютера или из письма, не запускается, и сообщение об ошибке мало что объясняет. Воспроизведём её на скрипте из лабораторной работы. Скрипт выводит сводку по журналу, его первая строка — #!/bin/bash, и он сохранён с окончаниями строк CRLF:
./report.sh
/bin/bash: ./report.sh: /bin/bash^M: bad interpreter: No such file or directory
Сообщение указывает на /bin/bash^M: программа пытается запустить интерпретатор с именем, в конце которого стоит невидимый возврат каретки. Файла /bin/bash^M в системе нет, поэтому запуск прерывается. Если запустить тот же скрипт через явный вызов bash, ошибка будет другой, но её причина та же:
bash report.sh
report.sh: line 14: syntax error: unexpected end of file
Вызов bash report.sh не требует исполняемого файла и не использует первую строку, поэтому интерпретатор запускается правильно. Однако каждая строка скрипта в конце содержит \r, и Bash видит в конце строки then\r или journal.log\r вместо ключевых слов и имён. Простой пример показывает, как это проявляется в имени файла:
printf '#!/bin/bash\r\nF=journal.log\r\nwc -l < "$F"\r\n' > crlf-var.sh
printf 'x\ny\n' > journal.log
bash crlf-var.sh
crlf-var.sh: line 3: journal.log: No such file or directory
Имя файла в переменной F содержит скрытый \r, поэтому файл journal.log с точки зрения программы называется иначе. Глаз этого не видит, cat тоже не покажет отличия, а cat -A — покажет.
Причина
Чтобы понять, почему первая строка скрипта так важна, нужно знать, как ядро запускает программу. Если файл начинается с символов #!, это так называемая строка шебанга (англ. shebang). Ядро читает путь после #! и запускает указанную программу, передавая ей имя скрипта. Путь берётся до конца строки, и для ядра символ \r является частью пути. Поэтому ядро ищет интерпретатор с именем /bin/bash\r, не находит его и сообщает об ошибке в той форме, которую видели выше. Руководство по Bash 7 описывает, как оболочка разбирает слова команды, и там видно, что скрытый символ становится частью слова.
Главный урок: файл, сохранённый в CRLF, для Linux выглядит иначе, чем для Windows, и даже если текст совпадает, программа видит другие байты. Заметить это помогает команда file, которая явно пишет «with CRLF line terminators»:
file report.sh
report.sh: Bourne-Again shell script, Unicode text, UTF-8 text executable, with CRLF line terminators
Рис. 2 показывает оба варианта окончания строки и место, где \r попадает в путь интерпретатора.
Исправление
Самое прямое исправление — удалить \r в конце каждой строки. Команда sed (потоковый редактор, подробно в теме 6) обрабатывает файл построчно. Выражение s/\r$// означает: в каждой строке найти символ возврата каретки, стоящий непосредственно перед концом строки, и заменить его пустотой. Опция -i меняет файл на месте, без неё результат выводится на экран. Исправим скрипт и проверим его работу:
sed -i 's/\r$//' report.sh
file report.sh
./report.sh
report.sh: Bourne-Again shell script, Unicode text, UTF-8 text executable
Строк в журнале: 2400
INFO: 2023
WARN: 284
ERROR: 93
Последние ошибки:
2026-03-02 20:58:12 ERROR backup: файл /srv/project/отдел_кадров.csv повреждён, строка 33 не разобрана
2026-03-02 21:02:17 ERROR db: файл /srv/project/логистика.csv повреждён, строка 17 не разобрана
2026-03-02 21:17:29 ERROR backup: не удалось открыть /srv/project/бухгалтерия.csv: Permission denied
Сводка получена из журнала приложения, созданного генератором лабораторной работы. Команда file после исправления не упоминает CRLF, значит окончания строк стали LF. Альтернатива — команда tr, которая удаляет все символы \r потоком и записывает результат в новый файл. Применим её к исходной, ещё не исправленной копии скрипта из архива (здесь она сохранена под именем report-orig.sh) и проверим результат через xxd:
tr -d '\r' < report-orig.sh > report-fixed.sh
xxd report-fixed.sh | head -1
00000000: 2321 2f62 696e 2f62 6173 680a 2320 d0a1 #!/bin/bash.# ..
После #!/bin/bash теперь стоит 0a вместо 0d 0a, а дальше начинается вторая строка — комментарий # Сводка…, то есть файл стал корректным. Программа dos2unix делает то же самое одной командой и заменяет файл на месте; её устанавливают пакетом dos2unix (пример установки: sudo apt install dos2unix). В этой теме она не используется, потому что её нет в базовом составе системы, а sed и tr есть всегда.
Редактор vim тоже может исправить формат. Он хранит признак формата конца строки и умеет переключать его:
vim -Nes -u NONE -c 'set ff?' -c 'q' crlf.txt
vim -Nes -u NONE -c 'set ff=unix' -c 'wq' crlf.txt
cat -A crlf.txt
fileformat=dos
M-PM-?M-PM-5M-QM-^@M-PM-2M-PM-0M-QM-^O$
M-PM-2M-QM-^BM-PM->M-QM-^@M-PM-0M-QM-^O$
M-QM-^BM-QM-^@M-PM-5M-QM-^BM-QM-^LM-QM-^O$
Первая команда показывает текущий формат (dos — значит CRLF), вторая устанавливает unix и сохраняет файл. Последний вывод cat -A подтверждает результат: везде только $, без ^M.
Профилактика
Ошибку CRLF удобнее не исправлять, а предупреждать. Текстовые редакторы Windows по умолчанию сохраняют файлы в CRLF, поэтому переносимые скрипты стоит сохранять в LF, а перед запуском проверять их командой file. Если скрипт получен извне, полезно сначала посмотреть его первые строки через head -n 1 файл | cat -A: наличие ^M$ говорит о CRLF до запуска. Курс продолжит работу со скриптами в темах 13–15; там эти привычки понадобятся постоянно, а автоматическая проверка скриптов инструментом ShellCheck поможет выявлять подобные проблемы раньше, чем они проявятся.
Итоги темы
Текстовый файл — это последовательность байтов, которую программы читают по договорённым правилам. Из этого следуют два практических вывода: размер файла в байтах не совпадает с числом символов, а невидимые символы конца строки могут сделать корректный на вид текст непригодным для программы. Инструменты xxd, od, cat -A и file позволяют увидеть то, что скрыто от глаза, а wc измеряет файлы по строкам, словам, символам и байтам.
Для просмотра большого файла используется less, для начала и конца файла — head и tail, для растущего журнала — tail -f. Сравнение версий выполняют cmp для ответа «совпадают ли» и diff для перечня изменений, а формат diff -u служит основой патчей. Редактирование в терминале начинается с nano, а для регулярной работы полезен vim, режимы которого обязательны к пониманию. Ошибка CRLF разобрана целиком: симптом, причина в строке шебанга и исправление. Полученные навыки понадобятся в теме 4, где файлы прав доступа и конфигурации читаются тем же способом, и в теме 6, где текст обрабатывается уже не вручную, а программами grep, sed и awk.
Литература
- Shotts W. The Linux Command Line: A Complete Introduction. 2nd ed. — No Starch Press, 2019. Открытая версия: https://linuxcommand.org/tlcl.php
- Free Software Foundation. GNU Coreutils Manual. — https://www.gnu.org/software/coreutils/manual/
- The Linux man-pages project. — https://man7.org/linux/man-pages/
- Raymond E. S. The Art of UNIX Programming. — Addison-Wesley, 2003. Открытая версия: http://www.catb.org/esr/writings/taoup/
- Kernighan B. W., Pike R. The UNIX Programming Environment. — Prentice Hall, 1984.
- Nemeth E., Snyder G., Hein T. R., Whaley B., Mackin D. UNIX and Linux System Administration Handbook. 5th ed. — Addison-Wesley, 2017.
- Free Software Foundation. GNU Bash Reference Manual. — https://www.gnu.org/software/bash/manual/