Брайан Керниган - UNIX — универсальная среда программирования
Хорошим введением для совсем "зеленых" новичков и непрограммистов представляется книга Э. и Н. Ломато "A UNIX Primer" (Prentice-Hall, 1983).
Глава 2
Файловая система
Все, с чем работает система UNIX, она воспринимает в виде файла. Это не такое уж упрощение, как может показаться на первый взгляд. Когда разрабатывалась первая версия системы, даже прежде, чем ей дали имя, все усилия сосредоточились на создании структуры файловой системы, которая должна была быть простой и удобной в использовании. Файловая система — ключевое звено, обеспечившее успешное применение UNIX. Это наилучший пример философии "прекрасное в малом", показывающий, какой мощи можно достичь реализацией нескольких хорошо продуманных идей.
Для описания команд и их взаимодействия нужно хорошо знать структуру и внешние связи файловой системы. В этой главе излагается большинство вопросов, связанных с файловой системой, — понятие файла и его представление, каталоги и иерархия файловой системы, права доступа, индексный дескриптор (внутреннее представление файла в системе) и файлы устройств. Поскольку основная работа в системе связана с манипулированием файлами, существует множество команд для анализа и модификации файла; здесь вводятся наиболее употребительные команды.
2.1 Основные сведения о файлах
Файл представляет собой последовательность байтов. (Байт — небольшая порция информации, обычно размером в восемь бит. Для наших целей можно считать байт синонимом слова "символ".) Никаких ограничений по структуре системой на файл не накладывается, и никакого смысла не приписывается его содержимому: смысл байтов зависит исключительно от программ, обрабатывающих файл. Более того, как мы увидим позднее, это верно не только для файлов, хранящихся на дисках, но и для файлов, представляющих периферийные устройства. Записи на магнитных лентах, почта, символы, вводимые с клавиатуры, вывод на печатающее устройство, данные, передаваемые по конвейеру — каждый из этих файлов система и входящие в нее программы воспринимают просто как последовательность байтов.
Лучше всего познакомиться с файлами экспериментальным путем, так что начнем с создания небольшого файла:
$ ed а
now is the time,
for all good people
.
w junk
36
q
$ls -l
-rw-r--r-- 1 you 26 Sep 27 06:11 junk
$
Здесь junk — это файл из 36 байт, т.е. 36 символов, которые вы ввели (не считая, конечно, символов, введенных при коррекции ошибок). Команда cat показывает содержимое файла в следующем виде:
$ cat junk
now is the time
for all good people
$
Команда od ("octal dump" — восьмеричный дамп) выдает "изображение" всех байтов файла:
$ od -с junk
0000000 n o w i s t h e t i m e n
0000020 f o r a l l g o o d p e o
0000040 p l e n
0000044
$
Флаг -с означает, что следует интерпретировать байты как символы. Если добавить флаг -b, то можно, кроме того, показать байты как восьмеричные числа.[5]
$ od -cb junk
0000000 n o w i s t h e t i m e n
156 157 167 040 151 163 040 164 150 145 040 164 151 155 145 012
0000020 f o r a l l g o o d p e o
146 157 162 040 141 154 154 040 147 157 157 144 040 160 145 157
0000040 d l e n
160 154 145 012
0000044 $
Семизначные числа в колонке слева показывают место в файле, т.е. порядковый номер следующего изображаемого символа в восьмеричной форме. Между прочим, приоритет восьмеричных чисел — это пережиток времен PDP-11, когда восьмеричной нотации отдавалось предпочтение. Для других машин больше подходит шестнадцатеричная нотация; флаг -х предписывает команде od печатать информацию в шестнадцатеричной форме.
Обратите внимание на то, что после каждой строки идет символ с восьмеричным значением 012. Это символ перевода строки для ASCII; система помещает его во входной поток, когда вы нажимаете клавишу RETURN. По соглашению, заимствованному из языка Си, символ перевода строки изображается как n, что лишь облегчает чтение. Такого соглашения придерживаются только программы типа od; в файле же хранится единственный байт 012.
Перевод строки — наиболее типичный пример специального символа. Другими специальными символами, связанными с некоторыми операциями управления терминалом, являются символы: шаг назад (восьмеричное значение 010 изображается как b), табуляция (011, t), возврат каретки (015, r).
Важно в каждом случае различать, в каком виде символ хранится в файле и как он интерпретируется в той или иной ситуации. Например, когда вы вводите с клавиатуры символ "шаг назад" (предполагая, что это ваш символ стирания), система воспринимает его как требование уничтожить символ, введенный перед ним. Оба символа — и стираемый, и "шаг назад" — на терминале исчезают, а курсор возвращается на одну позицию назад.
Если ввести последовательность
←
(т.е. символ и вслед за ним "шаг назад"), то ядро в этом случае "считает", что вы действительно хотите ввести символ ←, поэтому исчезает, а в вашем файле появляется байт 010. Когда "шаг назад" отражается на терминале, происходит возврат курсора, так что он указывает на символ .
При выводе файла, содержащего символ ←, он передается на терминал без обработки, что опять приводит к передвижке курсора на одну позицию назад. Если воспользоваться командой od, чтобы вывести файл, содержащий символ ←, он появится как байт со значением 010 или, если указан флаг -с, как b.
Аналогичную ситуацию мы имеем и с символом табуляции: при вводе он отражается на терминале и посылается программе, осуществляющей ввод; при выводе символ табуляции просто передается на терминал и интерпретируется. Однако в отличие от предыдущего случая здесь можно указать ядру, что вы хотите получить интерпретацию табуляции при выводе; тогда вместо изображения каждого символа табуляции будет выдаваться нужное число пробелов, чтобы перейти к следующей позиции табуляции. Позиции табуляции установлены в столбцах 9, 17, 25 и т.д. Команда
$ stty = tabs
приводит к замене символов табуляции пробелами при выводе на терминал см. описание stty(1).
Обработка символа RETURN аналогична рассмотренной выше. Ядро отображает RETURN на терминале как "возврат каретки" и "конец строки", но во входной поток попадает только "перевод строки". При выводе этот символ вновь заменяется символами возврата каретки и конца строки.
Подход системы UNIX к представлению управляющей информации нетрадиционен, особенно использование символа перевода строки для завершения строки (в качестве конца посылки). Многие системы вместо этого трактуют каждую строку как "запись", содержащую не только введенные данные, но и счетчик числа символов в строке (специального символа конца строки нет). В других системах каждая строка завершается символами возврата каретки и перевода строки, поскольку такая последовательность необходима для вывода на большинство терминалов. (Слово "linefeed" завершение строки, синоним перевода строки, поэтому такую последовательность часто называют "CRLF", что невозможно произнести.)
Система UNIX не делает ни того, ни другого: нет записей и счетчиков, к тому же ни в одном файле нет никаких байтов, которые бы вы или ваша программа не поместили туда. Символ перевода строки преобразуется в два символа возврата каретки и перевода строки при выводе на терминал, но программы должны иметь дело с одним символом перевода строки, поскольку это все, что они могут "увидеть". В большинстве случаев подобная простая схема является оптимальной. Если необходима более сложная структура, ее легко построить на базе этой, тогда как получить простое из сложного значительно трудней.
Поскольку завершение строки обозначается символом перевода строки, можно ожидать, что и файл завершается другим специальным символом, скажем е как сокращение "end of file" конец файла. Но, посмотрев на вывод программы od, вы не увидите никакого специального символа в конце файла он просто кончается. Вместо того чтобы использовать специальный символ, система отмечает конец файла сообщением о том, что данных в файле больше нет. Ядро запоминает длину файла, поэтому программа встречает конец файла после обработки всех составляющих файл байтов.
Программы выбирают данные из файла с помощью системного обращения с именем read (подпрограмма в ядре). При каждом обращении к read читается следующая часть файла, например очередная введенная строка. Подпрограмма read также сообщает число прочитанных байтов файла, поэтому конец файла обнаруживается, когда она сообщает: "прочитано 0 байт". Если какие-либо байты оставались в файле, то подпрограмма read выдала хотя бы часть их. На самом деле, отказ от ввода байта со специальным значением "конец файла" вполне оправдан, поскольку, как отмечалось ранее, смысл содержимого байта зависит от интерпретации файла. Но все файлы имеют конец, и поэтому их следует читать с помощью подпрограммы read, а возврат нуля это зависящий от интерпретации способ представления конца файла без использования специальных символов.