Главные команды UNIX: работа с содержимым файлов
Содержание
В рассказе про историю UNIX я говорил, что UNIX строился вокруг нескольких ключевых идей, среди которых были:
- взаимодействие между пользователем и компьютером происходит посредством текста (эта идея возникла, разумеется, не от хорошей жизни, а потому, что у ранних машин, на которых разрабатывали UNIX, не было интерфейса лучше, чем пара из печатной машинки и принтера; и всё же, именно такое ограничением породило идею, которую до сих пор можно считать гениальной и очень важной)
- взаимодействие между разными программами происходит посредством текста
- следовательно, и настройка программы происходит посредством редактирования текстовых файлов
- каждая программа делает только одно дело, но делает его хорошо
Из всех этих идей следует, что в UNIX должно быть много небольших утилит для работы с текстом. Так и есть.
Этот конспект не претендует на дублирование содержимого стандартной документации UNIX, а только даёт первое представление о том, что есть и зачем оно нужно. Если вы будете достаточно долго пользоваться UNIX, рано или поздно вам придётся читать документацию и по этим основным утилитам тоже, и вы обнаружите, что они умеют гораздо больше, чем я здесь рассказал.
cat
cat(1) – сокращение от слова conCATenate – выводит последовательно на стандартный вывод (на экран) содержимое всех файлов, которые ей дали на вход.
Для всех древних утилит для обработки текста в UNIX есть традиция, что если их запускать без аргумента, то они ждут текст входного файла со стандартного ввода (с клавиатуры).
Перенаправления
Здесь необходимо сделать небольшое отступление и рассказать о свойствах интерпретатора командной строки (шелла).
Интерпретатор умеет перенаправлять стандартный ввод (который по умолчанию сваливается на экран) и стандартный вывод в другие места:
команда > файл перенаправляет стандартный вывод команды в файл, т.е. команда не пишет ничего на экран, а то, что она хотела написать на экран, оказывается в файле. При этом файл потирается, если он уже существовал до этого или создаётся, если его до этого не было.
команда >> файл делает то же самое, но дописывает в конец файла, если файл уже был не пустой.
команда < файл перенаправляет стандартный ввод команды из файла, т.е. если команда чего-нибудь ожидала с клавиатуры, то теперь она ничего с клавиатуры не ожидает, а считает, что с клавиатуры ей набили содержимое этого файла.
команда1 | команда2 перенаправляет стандартный вывод команды1 на стандартный ввод команды2 (это почти синоним того, чтобы сказать команда1 > файл; команда2 < файл) – это называется pipeline (трубопровод).
Соответственно,
head, tail
Утилита head(1) показывает голову файла – первые несколько строк или букв. tail(1) – хвост.
По умолчанию head и tail показывают первые/последние 10 строк.
Наиболее полезные аргументы у них:
head -n 3, tail -n 3 – первые/последние 3 строки
head -c 100, tail -c 100 – первые/последние 100 байт
head -n 1000 a.txt b.txt c.txt – если у нас есть три маленьких файла a.txt, b.txt, c.txt, и мы хотим быстро их все увидеть сразу, то для этого очень удобен head – когда ему дают больше одного файла, он выводит имя файла и следом за ним нужное количество первых строк файла
sort, uniq
Утилита sort(1) сортирует входные строки. Например:
Утилиту sort можно заставить сортировать не по первому столбцу; её можно убедить, что разделителем между столбцами является не пробел; её можно заставить сортировать не в лексикографическом порядке, а интерпретировать числа и сортировать по возрастанию чисел; её можно убедить сортировать не по возрастанию, а в обратном порядке – всё это смотрите в документации.
В пару к sort есть полезная утилита uniq(1), основное назначение которой в том, чтобы оставлять из идущих подряд строк только различные (но если одинаковые строки идут не подряд, uniq не будет вспоминать, что он их уже видел):
Утилиту uniq можно заставить писать число одинаковых вхождений подряд, писать только строки, для которых есть повторы или только те, для которых повторов нет; аналогично sort'у, её можно заставить учитывать различия только в некоторых заданных столбцах – смотрите в документации.
grep
Утилита grep(1) ищет строку/выражение в содержимом файла / файлов.
Наиболее полезные аргументы:
grep -f строка – поиск в точности строки в файле; этот вызов считается настолько полезным, что ради него сделали отдельную команду: fgrep
grep -e регулярное_выражение – поиск регулярного выражения в файле; этот вызов тоже считается очень полезным, что он сокращается до слова egrep
grep -v выражение – показывать только строки, в которых выражения нет
grep -l выражение много файлов – показывать только имена файлов, в которых выражение есть
grep -r выражение директория или несколько – искать во всех файлах в директории и поддиректориях (рекурсивно)
grep -n выражение файл – писать номера строк, в которых выражение нашлось
$ grep -rl "Hello, world" ~ # найти все файлы в домашней директории, в которых есть Hello world ...
Регулярные выражения
Реуглярное выражение – это способ задавать подробные шаблоны на то, что мы ищем. Определяются регулярные выражения так:
буква – это регулярное выражение
если A и B – это регулярные выражения, то AB – это регулярное выражение, которое находит то, что находит A, за которым идёт то, что находит B
если A – это регулярное выражение, то A* – это регулярное выражение, которое находит пустую строку или то же, что нашло бы выражение A, или то же, что нашло бы выражение AA, или то же, что нашло бы выражение AAA и т.п. Т.е. это повторение 0 или более раз выражения A
если A и B – это регулярные выражения, то A|B – это регулярное выражение, которое находит всё, что находит хотя бы одно из выражений A или B
В выражениях можно использовать скобки для обозначения, к чему относится следующая звёздочка.
Выражение вида a|b|c|d можно сокращать до [abcd] или даже до [a-d] (если a, b, c, d – это именно буквы).
Для выражения (|A) есть сокращение: A? (выражение находит пустую строку или то же, что находит A).
Примеры:
abc – регулярное выражение, которое ищет строку abc
a|bc – регулярное выражение, которе ищет строку a или строку bc
ab*c – регулярное выражение, которое находит строки ac, abc, abbc, abbbc и т.п.
ab?c находит ac и abc
(ab)*c находит c, abc, ababc, abababc и т.п.
Если вы в регулярном выражении хотите найти какой-нибудь из символов, который используется для разметки самих регулярных выражений, то перед таким символом нужно ставить \ (backslash, он же обратный слэш, он же обратная косая черта):
a\*b\(c ищет строку a*b(c
Начало строки в регулярном выражении обозначается символом ^, конец строки – $.
Развитий синтаксиса регулярных выражений за рамками этого минимума существует довольно много, и даже для этого минимума существуют разные способы обозначений. Тот синтаксис, который я здесь привёл, сейчас набиолее широко принят, он используется в языках программирования (perl, python, tcl, гугловская реализация), в древних утилитах UNIX этот синтаксис называется "extended regular expressions".
См. regex(5)
Пример:
tr
Самая простая команда для преобразования (а не фильтрации) потока текста – это tr(1). Она умеет далать только одно: побуквенную замену.
tr AB CD заменяет все A на C, B на D
tr -d AB удаляет все буквы A и B
tr -cd AB оставляет только буквы A и B
tr a-z A-Z заменяет все строчные буквы на прописные
Пример:
sed, awk
Как я рассказывал в истории UNIX, первым конкурентным преимуществом UNIX перед другими системами на заре его существования было то, что в нём был текстовый редактор (а заодно и компилятор и система "вёрстки" текстов). Этот текстовый редактор назывался ed, это был текстовый редактор командной строки, в нём были команды "распечатать текущую строку" (p), "заменить текущую строку" (c), "применить команду к заданному диапазону (g/регулярное выражение/ команда), "заменить в строке что-то на что-то" (s/что-то/что-то/).
Из этого редактора родилась утилита grep. Собственно, её название расшифровывается именно так: g/RE/p.
Ещё из него родилась утилита sed (string editor), которая убирала из него все интерактивные возможности, но доделывала его до полноценного, хотя и страшного, языка программирования. sed нацелен на простое автоматизированное редактирование строк.
Из sed родился более навороченный и человекоприемлемый язык awk, ориентированный на редактирование "табличных" данных.
Ещё немного позже из ed родился текстовый редактор командной строки ex, а когда к нему приделали визуальный режим, его назвали vi – но это уже совсем другая история.
Полный синтаксис sed(1) не сложен, но как правило, он и не нужен. Про него в 90% случаях достаточно знать один способ использования:
sed 's/что-нибудь/куда-нибудь/g' заменяет в потоке всякое вхождение что-нибудь на куда-нибудь.
Чтобы sed использовал привычные нам регулярные выражения, нужно вызывать его с флагом -r:
$ cat a | sed -r 's/[24]/5/g' 1 5 5 3 1 5
Если вы будете знать о sed ещё и то, что в его языке перед командой можно указывать диапазон, и диапазон может быть нескольких видов, например номер строки, два номера строки через запятую (от и до), или регулярное выражение, заключённое в два прямых слэша, то у вас в руках окажется сильно более мощное средство. Но об этом читайте документацию, когда возникнет потребность.
Аналогично и с awk: это полноценный язык, из которого на деле нужна примерно одна команда: awk '{print $1}' распечатает первое слово из каждой строки.
Пример
Пример использования всего этого вместе: каким образом я составлял список команд, о которых нужно рассказать в этой части курса?
В шелле есть команда history, которая печатает последние 10000 команд, которые я исполнял. (10000 – это настройка; у меня настроено 10000, но по умолчанию настроено меньше). Выдача её выглядит так:
Сначала нужно отрезать номера строк. Для этого годится sed или awk. Я достаточно знаю awk, чтобы это было проще сделать через него:
Теперь в случаях, когда я использую составную команду, мне нужно вынести название команды в первый столбец, чтобы можно было считать количество всех команд, включая те, которые я использую в составных командах:
Т.е. нужно некоторые интересные нам символы (мне интересны |, ;, {, () заменить на перенос строки (в sed, как и в большинстве языков программирования, он обозначается как \n):
Получилось и некоторое количество мусора из-за того, что символы, по которым я разбивал, встречаются ещё и, например, в этом же самом скрипте, но это не так страшно.
Теперь нужно оставить только первую колонку – добавляем | awk '{ print $1 }'.
Далее нужно посчитать количество вхождений каждой из команд. Это умеет делать uniq, но чтобы он сработал, историю нужно посортировать:
Теперь нас интересуют только самые часто используемые команды. Что может быть проще, нужно ещё раз посортировать получившийся список (ведь первым числом идёт количество раз, которое команда встречалась):
Теперь из полученного можно уже глазами выбрать наиболее интересное...
wc
Ещё одна полезная утилита – wc(1) – она считает количество букв, слов и строк в файле.
file
Ещё одна полезная штука – file(1) – она пытается определить тип файла по его содержимому.
Она умеет как минимум отличать простой текст (в названии формата почти всегда должно быть слово text) от бинарных форматов, которые можно читать только специальными программами, например, ворд (тогда в названии формата будет слово data). Разумеется, всякие общепринятые штуки типа исполняемых файлов, вордовских документов и популярных языков программирования она умеет распознавать и может написать про них немного более содержательной информации.
Это полезная штука на случай, если к вам свалилось что-нибудь без расширения или вы сами поназапутывали расширений у своих файлов.
less
less(1) – это просмотрщик текстовых файлов. (По-английски этот класс программ называется pager или paging tool; иногда и в русских переводах встречалось уродское слово пагинатор).
Первый просмотрщик текстовых файлов в UNIX назывался more – это была программа, которая печатала файл на принтер (ведь другого способа общаться с компьютером тогда не было), но не целиком, а только одну страницу (чтобы случайно не истратить всю бумагу), а если файл одной страницей не ограничивался, то в конце страницы печатала "-- MORE --" в смысле "есть ещё буквы в этом файле" и ожидала, пока пользователь нажмёт пробел, чтобы продолжить печать файла. В честь этой подписи просмотрщик и назвали.
Когда для проекта GNU делали свой просмотрщик, никак не могли придумать, как его назвать, и в конце концов, назвали как попало, сказали, что "less is more than more", и остались довольны.
Про less полезно знать несколько вещей:
чтобы из него выйти нужно нажать q, и никак иначе
в нём работают всякие курсорчики, PageUp, PageDown, Home, End (последние два означают начало/конец файла) – не для всех UNIX'овых программ такая роскошь характерна
кнопка / открывает вам диалог поиска
кнопка H (с шифтом) открывает встроенные хелпы