Работа с текстовыми данными
Ввод и вывод
Любая программа - это автомат, предназначенный для обработки данных: получая на входе одну информацию, они в результате работы выдают другую. Хотя входящая и/или выходящая информация может быть и нулевой, т. е. попросту отсутствовать. Те данные, которые передаются программе для обработки - это ее ввод, то, что она выдает в результате работы - вывод. Организация ввода и вывода для каждой программы - это задача операционной системы.
Каждая программа работает с данными определенного типа: текстовыми, графическими, звуковыми и т. п. Как, наверное, уже стало понятно из предыдущих лекций, основной интерфейс управления системой в 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) - поток данных, открываемый системой для каждого процесса в момент его запуска и предназначенный для ввода данных.