Опубликован: 22.06.2005 | Уровень: для всех | Доступ: платный | ВУЗ: Компания IBM
Лекция 7:

Работа с текстовыми данными

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Аннотация: В Linux очень многие задачи использования и администрирования системы сводятся к обработке текстовых данных. В лекции описаны способы эффективной обработки текста при помощи интерфейса командной строки и набора стандартных утилит. Вводятся понятия стандартного ввода/вывода, конвейера. Последний раздел посвящен разбору типичных задач, возникающих в ходе работы с системой, и их решения при помощи стандартных утилит, объединенных в конвейере.

Ввод и вывод

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

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

"Текстовость" данных - всего лишь договоренность об их формате. Никто не мешает выводить на экран нетекстовый файл, однако пользы в этом будет мало. Во-первых, раз уж файл содержит не текст, то не предполагается, что человек сможет что-либо понять из его содержимого. Во-вторых, если в нетекстовых данных, выводимых на терминал, случайно встретится управляющая последовательность, терминал ее выполнит. Например, если в скомпилированной программе записано число 1528515121, оно представлено в виде четырех байтов: 27, 91, 49 и 74. Соответствующий им текст состоит из четырех символов ASCII: " esc ", " [ ", " 1 " и " J ", и при выводе файла на виртуальную консоль Linux в этом месте выполнится очистка экрана, так как " ^[[1J " - именно такая управляющая последовательность для виртуальной консоли. Не все управляющие последовательности столь безобидны, поэтому использовать нетекстовые данные в качестве текстов не рекомендуется.

Что же делать, если содержимое нетекстового файла все-таки желательно просмотреть (то есть превратить в текст )? Можно воспользоваться программой hexdump, которая выдает содержимое файла в виде шестнадцатеричных ASCII-кодов, или strings, показывающей только те части файла, которые могут быть представлены в виде текста:

[methody@localhost methody]$ hexdump -C /bin/cat | less
 00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
 00000010  02 00 03 00 01 00 00 00  90 8b 04 08 34 00 00 00  |............4...|
 00000020  e0 3a 00 00 00 00 00 00  34 00 20 00 07 00 28 00  |Ю:......4. ...(.|
  . . .
 00000100  00 00 00 00 00 00 00 00  00 00 00 00 06 00 00 00  |................|
 00000110  04 00 00 00 2f 6c 69 62  2f 6c 64 2d 6c 69 6e 75  |..../lib/ld-linu|
 00000120  78 2e 73 6f 2e 32 00 00  04 00 00 00 10 00 00 00  |x.so.2..........|
 00000130  01 00 00 00 47 4e 55 00  00 00 00 00 02 00 00 00  |....GNU.........|
  . . .
[methody@localhost methody]$ strings /bin/cat | less
 /lib/ld-linux.so.2
 _Jv_RegisterClasses
 __gmon_start__
 libc.so.6
 stdout
  . . .
[methody@localhost methody]$ strings -n3 /bin/cat | less
 /lib/ld-linux.so.2
 GNU
 _Jv_RegisterClasses
 __gmon_start__
 libc.so.6
 stdout
  . . .
Пример 7.1. Использование hexdump и strings

В примере Мефодий, зная заранее, что текста будет выдано много, воспользовался конвейером " | less ", описанным в разделе " Конвейер ". С ключом " -C " утилита hexdump выводит в правой стороне экрана текстовое представление данных, заменяя непечатные символы точками (чтобы среди выводимого текста не встретилось управляющей последовательности ). Мефодий заметил, что strings "не нашла" в файле /bin/cat явно текстовых подстрок " ELF " и " GNU ": первая из них - вообще не текст (перед ней стоит непечатный символ с кодом 7f, а после - символ с кодом 1 ), а вторая - слишком короткая, хоть и ограничена символами с кодом 0, как это и полагается строке в скомпилированной программе. Наименьшая длина строки передается strings ключом " -n ".

Перенаправление ввода и вывода

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

Каждый процесс Linux получает при старте три "файла", открытых для него системой. Первый из них (дескриптор 0 ) открыт на чтение, это стандартный ввод процесса. Именно со стандартным вводом работают все операции чтения, если в них не указан дескриптор файла. Второй (дескриптор 1 ) - открыт на запись, это стандартный вывод процесса. С ним работают все операции записи, если дескриптор файла не указан в них явно. Наконец, третий поток данных (дескриптор 2 ) предназначается для вывода диагностических сообщений, он называется стандартный вывод ошибок. Поскольку эти три дескриптора уже открыты к моменту запуска процесса, первый файл, открытый самим процессом, будет, скорее всего, иметь дескриптор 3.

Дескриптор - это описатель потока данных, открытого процессом. Дескрипторы нумеруются, начиная с 0. При открытии нового потока данных его дескриптор получает наименьший из неиспользуемых в этот момент номеров. Три заранее открытых дескриптора - стандартный ввод ( 0 ), стандартный вывод ( 1 ) и стандартный вывод ошибок ( 2 ) - выдаются при запуске.

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

Стандартный вывод

Мефодий уже сталкивался с тем, что некоторые программы умеют выводить не только на терминал, но и в файл. Например, info при указании параметрического ключа " -o " с именем файла выведет текст руководства в файл, вместо того, чтобы отображать его на мониторе. Даже если разработчиками программы не предусмотрен такой ключ, Мефодию известен и другой способ сохранить вывод программы в файле вместо того, чтобы выводить его на монитор: поставить знак " > " и указать после него имя файла. Таким образом Мефодий уже создавал короткие текстовые файлы ( сценарии ) при помощи утилиты cat (см. лекцию 5):

[methody@localhost methody]$ cat > textfile
Это файл для примеров.
^D
[methody@localhost methody]$ ls -l textfile
-rw-r--r--  1 methody methody 23 Ноя 15 16:06 textfile
Пример 7.2. Перенаправление стандартного вывода в файл

От использования символа " > " возможности самой утилиты cat, конечно, не расширились. Более того, cat в этом примере не получила от командной оболочки никаких параметров: ни знака " > ", ни последующего имени файла. В этом случае cat работала как обычно, не зная (и даже не интересуясь!), куда попадут выведенные данные: на экран монитора, в файл или куда-нибудь еще. Вместо того, чтобы самой обеспечивать доставку вывода до конечного адресата (будь то человек или файл), cat отправляет все данные на стандартный вывод (сокращенно - stdout ).

Подмена стандартного вывода - задача командной оболочки (shell). В данном примере shell создает пустой файл, имя которого указано после знака " > ", и дескриптор этого файла передается программе cat под номером 1 ( стандартный вывод ). Делается это очень просто. В лекции 5 было рассказано о том, как запускаются команды из оболочки. В частности, после выполнения fork() появляется два одинаковых процесса, один из которых - дочерний - должен запустить вместо себя команду (выполнить exec() ). Так вот, перед этим он закрывает стандартный вывод (дескриптор 1 освобождается) и открывает файл (с ним связывается первый свободный дескриптор, т. е. 1 ), а запускаемой команде ничего знать и не надо: ее стандартный вывод уже подменен. Эта операция называется перенаправлением стандартного вывода. В том случае, если файл уже существует, shell запишет его заново, полностью уничтожив все, что в нем содержалось до этого. Поэтому Мефодию, чтобы продолжить записывать данные в textfile, потребуется другая операция - " >> ":

[methody@localhost methody]$ cat >> textfile 
Пример 1.
^D
[methody@localhost methody]$ cat textfile 
Это файл для примеров.
Пример 1.
[methody@localhost methody]$
Пример 7.3. Недеструктивное перенаправление стандартного вывода

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

Cтандартный вывод (standard output, stdout) - это поток данных, открываемый системой для каждого процесса в момент его запуска и предназначенный для данных, выводимых процессом.

Стандартный ввод

Аналогичным образом для передачи данных на вход программе может быть использован стандартный ввод (сокращенно - stdin). При работе с командной строкой стандартный ввод - это символы, вводимые пользователем с клавиатуры. Стандартный ввод можно перенаправить при помощи командной оболочки, подав на него данные из некоторого файла. Символ " < " служит для перенаправления содержимого файла на стандартный ввод программе. Например, если вызвать утилиту sort без параметра, она будет читать строки со стандартного ввода. Команда " sort < имя_файла " подаст на ввод sort данные из файла:

[methody@localhost methody]$ sort < textfile 
Пример 1.
Это файл для примеров.
[methody@localhost methody]$
Пример 7.4. Перенаправление стандартного ввода из файла

Результат действия этой команды аналогичен команде sort textfile - разница лишь в том, что когда используется " < ", sort получает данные со стандартного ввода ничего не зная о файле " textfile ", откуда они поступают. Механизм работы shell в данном случае тот же, что и при перенаправлении вывода: shell читает данные из файла " textfile ", запускает утилиту sort и передает ей на стандартный ввод содержимое файла.

Необходимо помнить, что операция " > " деструктивна: она всегда создает файл нулевой длины. Поэтому для, допустим, сортировки данных в файле надо применять последовательно sort < файл > новый_файл и mv новый_файл файл. Команда вида команда < файл > тот_же_файл просто урежет его до нулевой длины!

Стандартный ввод (standard input, stdin) - поток данных, открываемый системой для каждого процесса в момент его запуска и предназначенный для ввода данных.

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Аягоз Имансакипова
Аягоз Имансакипова
Тимур Булатов
Тимур Булатов

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