Файл: Учебнометодическое пособие Томск 2016 2 удк 004. 451(075. 8) Ббк 32. 973. 2018. 2я73 к 754 Рецензенты.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 25.10.2023
Просмотров: 281
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
a – добавление текста после текущей строки;
i– добавление текста перед текущей строкой; c – замена текущей строки тек- стом новой строки (строк); d– удаление текущей строки. Пример:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
$p ed
that I have created 4 new lines польз.
a
I will now enter two new lines of text to see if it is accepted.
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Команда редактора $pтребует от него вывода на экран последней строки файла. Поэтому две строки, набранные в примере пользователем, добавляются в конец файла. Пример добавления текста перед текущей строкой:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p ed
Once I have completed it I shall find польз.
i
I am now inserting two lines of text to demonstrate how it works.
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример замещения текста текущей строки текстом другой строки:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p ed
Once I have completed it I shall find польз.
c
After completion, I shall find.
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
218
В данном примере текущая строка заменена текстом одной строки. На самом деле число новых строк может быть любым. В следующем примере про- изводится удаление двух строк:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p ed
I am now inserting two lines of польз.
d польз.
d
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Одной командой можно удалить целую группу строк. В следующем при- мере производится удаление всего содержимого файла:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
1,$d польз.
1,$p ed
?
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Если текст файла достаточно большой, рекомендуется выполнять проме- жуточное запоминание файла на диске, используя команду w. Выполнив ее, ре- дактор выведет на экран новую длину файла.
Выход из редактора edв UNIX выполняет команда редактора – q. Пример:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
q
UNIX
$
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните создание нового текстового файла, а затем вый- дите из редактора в UNIX. Далее скорректируйте этот файл, доба- вив новые строки в конец, в начало и в середину файла. Затем уда- лите некоторые строки и проверьте результаты редактирования с помощью вывода файла на экран.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
219
8.1.4 Задание
Для успешной сдачи работы требуется выполнить наизусть следующие операции:
1) создать два трехуровневых поддерева каталогов и поместить в один из каталогов четыре текстовых файла, два из которых имеют в своем имени одинаковую символьную последовательность, называемую да- лее «словом»;
2) с помощью ed отредактировать текстовый файл;
3) вывести файл на экран;
4) выполнить добавление текста в начало, в середину и в конец файла;
5) вывести файл на экран;
6) произвести переименование файла;
7) выполнить копирование файла (исходный файл и файл-копия должны располагаться в разных каталогах);
8) поместить в любой другой каталог скрипт, имеющий два входных па- раметра: имя каталога и набор символов. Скрипт выполняет действия:
вывод на экран перечня файлов, «дочерних» к заданному каталогу, которые имеют в своем имени заданный набор символов;
уничтожение всех остальных файлов заданного каталога;
любые другие действия (по вашему желанию);
9) создать свой инициализационный скрипт, выполняющий действия:
здоровается;
«переделывает» приглашения shell;
запускает вложенный скрипт, созданный в (8), задав ему в каче- стве параметров каталог и «слово» из (1);
любые другие действия (по вашему желанию);
10) выйти из UNIX, а затем войти вновь с целью демонстрации результа- тов выполнения инициализационного скрипта.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Примечание 1. При выполнении задания разрешается ис- пользовать те средства shell, которые рассмотрены в данной ра- боте. В частности нельзя применять управляющие операторы (рас- сматриваются в следующей работе).
Примечание 2. Для избирательного удаления файлов в (8) рекомендуется использовать команду rm с флагом -i, предвари- тельно установив права доступа к файлам. При этом для обеспече-
220 ния автоматического выполнения rm ее стандартный ввод должен быть переключен на вспомогательный файл, содержащий любой символ кроме «y».
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.2 Лабораторная работа № 2.
Управляющие операторы командного языка
8.2.1 Цель работы
Целью выполнения настоящей лабораторной работы является развитие навыков программирования на языке shell путем использования в скриптах управляющих операторов if, case, for, while, until.
8.2.2 Подготовка к выполнению работы
В начале выполнения данной работы обязательно следует изучить следу- ющие вопросы из теоретической части пособия (пп. 2.5.5):
1) оператор двухальтернативного выбора if;
2) оператор многоальтернативного выбора case;
3) оператор цикла с перечислением for;
4) оператор цикла с условием while;
5) оператор цикла с инверсным условием until.
В процессе изучения перечисленных вопросов обязательно следует вы- полнить примеры, приведенные в пособии.
8.2.3 Задание
Требуется разработать программу на языке shell (без использования команды find), выполняющую поиск в заданном поддереве файловой струк- туры всех файлов, имена которых отвечают заданному шаблону. Результатом работы программы является перечень имен искомых файлов на экране.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Примечание. Программа состоит из двух скриптов. Главный скрипт выполняет вывод на экран приглашения ввести с клавиату- ры имя-путь начального каталога и шаблон поиска. Далее он вы- полняет ввод этих данных с клавиатуры и выводит на экран пере- чень искомых файлов в начальном каталоге поиска (если они там
221 есть). Затем он вызывает для каждого подкаталога вложенный скрипт, передав ему два входных параметра: 1) относительное имя подкаталога; 2) шаблон поиска.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Вложенный скрипт выполняет поиск в заданном каталоге искомых фай- лов, а для каждого подкаталога вызывает точно такой же скрипт. (При выпол- нении любого скрипта запускается новый экземпляр shell, поэтому рекур- сивное выполнение скриптов не приводит к каким-либо трудностям.)
8.3 Лабораторная работа № 3.
Операции с файлами в программе на языке Си
8.3.1 Цель работы
Целью выполнения настоящей лабораторной работы является получение начальных навыков использования системных вызовов UNIX в программах на языке Си. Данные вызовы выполняют основные операции с файлами: откры- тие и создание, чтение и запись, закрытие и уничтожение.
8.3.2 Подготовка к выполнению работы
В начале выполнения данной работы рекомендуется изучить следующие вопросы из теоретической части пособия:
1) логические файлы (пп. 7.1.1) [1, 2];
2) открытие файла (пп. 7.1.2) [1, 2];
3) создание файла (пп. 7.1.3) [1, 2];
4) понятие файлового указателя (пп. 7.1.2) [1, 2] и его перемещение
(пп. 7.1.3) [1, 2];
5) чтение из файла и запись в него (пп. 7.1.3) [1, 2];
6) закрытие и уничтожение файла (пп. 7.1.3) [1, 2].
8.3.3 Введение
Разработку программ начнем с программирования операций с файлами.
Для этого программа на Си имеет две основные возможности. Первая из них предполагает непосредственное обращение из программы к ядру ОС с помо- щью системных вызовов. Вторая возможность реализуется путем вызова функ- ций стандартной библиотеки ввода-вывода. Эти функции предоставляют при-
222 кладной программе более удобный интерфейс, освобождая ее, в частности, от собственной буферизации файловых операций. Но основную работу с файлами по-прежнему выполняют системные вызовы, которые теперь делаются не из прикладной программы, а из функций стандартной библиотеки. Не допускается использовать оба перечисленных способа для работы с одним и тем же файлом в одной и той же программе.
Одним из различий двух названных подходов, учитываемых при про- граммировании, является способ задания логического имени файла в програм- ме. В отличие от имени физического файла, которое уникально для всей систе- мы (это или имя-путь или относительное имя – смещение относительно текущего каталога) логическое имя файла обладает уникальностью только в пределах данного процесса. Если для работы с данным файлом в программе применяются системные вызовы, то в качестве логического имени файла ис- пользуется его номер, уникальный для данного процесса.
Если для работы с файлом в программе применяются функции стандарт- ной библиотеки, то в качестве логического имени файла используется указатель на таблицу (структуру), используемую этими функциями для работы с файлом.
Данная структура имеет тип FILE. Одно из ее полей содержит номер файла, позволяя тем самым стандартным функциям осуществлять системные вызовы.
(Полезно заметить, что аналогичные структуры применяются для работы с файлами и в языке Паскаль.)
8.3.4 Открытие существующего файла
Результатом выполнения операции открытия файла является или логиче- ское имя файла, или признак ошибки.
Системные вызовы. Открытие файла выполняет системный вызов open
. Его описание: int open(const char *pathname, int flags, [mode_t mode]);
Данный вызов возвращает в программу или номер открытого файла (не- отрицательное целое число) или –1 (признак ошибки). Ошибка может произой- ти, например, если файл не существует.
Первый параметр (pathname) является указателем на символьную стро- ку, содержащую имя физического файла. В качестве этого имени здесь, а также во всех приводимых ниже системных вызовах и в стандартных функциях, мож- но использовать или имя-путь файла, или его относительное имя.
223
Второй параметр (flags) вызова open определяет запрашиваемый ме- тод доступа к файлу. Этот параметр принимает одно из значений, заданных константами в заголовочном файле <fcntl.h>. (Как и большинство заголо- вочных файлов, <fcntl.h> находится в каталоге /usr/include и может быть включен в программу с помощью директивы #include.)
Три особо интересных константы:
1) O_RDONLY – открыть файл только для чтения;
2) O_WRONLY– открыть файл только для записи;
3) O_RDWR – открыть файл для чтения и для записи.
Третий параметр (mode) необязательный (об этом свидетельствуют квад- ратные скобки). При открытии существующего файла он не используется.
Пример программы:
#include /* Для вызова exit */
#include char *namefile=”abc”;/* Имя файла */ main()
{ int fil;
/* Открыть файл для чтения-записи */ if ((fil = open(namefile, O_RDWR)) = = -1)
{ printf(“Невозможно открыть %s\n”, namefile); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
}
Данная программа не выполняет никакой полезной работы, а просто де- монстрирует некоторые важные моменты. Во-первых, всякая файловая опера- ция может завершиться с ошибкой. Поэтому такая возможность должна быть обязательно предусмотрена в программе. Во-вторых, выход из программы
(а точнее – завершение соответствующего программного процесса) выполняет системный вызов exit, единственный параметр которого содержит код завер- шения процесса (0 – успешно; –1 – с ошибкой). Прототип этого системного вызова содержится в заголовочном файле <stdlib.h>.
224
Следующая программа отличается от предыдущей только тем, что имя открываемого файла задается не в тексте программы, а из командной строки shell в качестве параметра запускаемого файла. Допустим, что готовая про- грамма помещена в файл open_file. Тогда для ее запуска можно использо- вать командную строку:
$ open_file abc
Текст программы:
#include /* Для вызова exit */
#include main(int argc, char *argv[ ])
{ int fil; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
}
/* Открыть файл для чтения-записи */ if ((fil = open(argv[1], O_RDWR)) = = -1)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
} shell запускает главную подпрограмму main, присвоив ее аргументу argc значение количества параметров в списке argv, а каждому элементу массива argv значение соответствующего параметра из командной строки. В примере argc равно 2, элемент argv[0] содержит строку символов
«open_file» (имя программы условно является нулевым параметром), argv[1] – строку символов «abc». В начале своего выполнения программа проверяет, правильное ли число параметров ей было передано при запуске.
Функции стандартной библиотеки. Открытие файла выполняет стан- дартная функция fopen. Следующая программа выполняет то же самое, что и программа в предыдущем примере – открывает файл, имя которого пользова- тель набирает в командной строке в качестве параметра запускаемого файла:
225
#include /* Для вызова exit */
#include /* Содержит определения FILE,
fopen и т. д.*/ main(int argc, char *argv[])
{
FILE *stream; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
/* Открыть файл для чтения-записи */
} if ((stream = fopen(argv[1], “r+”)) = = NULL)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
}
Первый параметр функции fopen – указатель на имя открываемого фи- зического файла. Второй параметр – символьная строка, которая определяет за- прашиваемый метод доступа к файлу: «r» – файл открывается только для чте- ния; «a» – файл открывается только для записи; «r+» – для чтения и записи.
В случае успеха функция fopen заполняет для файла структуру FILE и воз- вращает в переменной stream ее адрес. В случае ошибки возвращается указа- тель NULL.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните приведенные выше программы.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.5 Создание файла
Результатом выполнения операции создания файла является или логиче- ское имя файла или признак ошибки.
Системные вызовы. Для создания файла может быть использован один из двух системных вызовов: open и creat. Вызов creatпредставляет собой традиционный способ создания файла. Его описание:
226 int creat(const char *pathname, mode_t mode);
Первый параметр (pathname) является указателем на символьную стро- ку, содержащую имя физического файла. Параметр mode задает права доступа к файлу. Обычно это 3-значное восьмеричное число, старшая цифра которого определяет права доступа владельца файла, средняя цифра – владельца-группы, а младшая цифра задает права доступа всех остальных пользователей. Напри- мер, восьмеричное число (в Си восьмеричное число начинается с нуля) 0764 за- дает право доступа rwx для владельца, rw – для владельца-группы, r – для всех остальных пользователей. Пример использования вызова creat: fil = creat(“/tmp/newfile”, 0764);
В случае успешного завершения системный вызов creat возвращает но- мер нового файла, открытого на запись. А в случае ошибки возвращает значе- ние –1. Для того чтобы только что созданный файл был открыт для чтения, его необходимо сначала закрыть, а затем открыть вновь.
Если файл, заданный в качестве первого параметра creat уже существу- ет, то, во-первых, второй параметр вызова creat игнорируется. А во-вторых, данный файл усекается до нулевой длины и, следовательно, прежняя информа- ция файла уничтожается.
Рассмотрим применение для создания файла системного вызова open, который предоставляет больше возможностей по сравнению с вызовом creat.
Для этого, во-первых, в состав второго операнда вызова open обязательно должна быть включена константа O_CREAT. Во-вторых, данный вызов должен иметь третий операнд – mode, аналогичный операнду вызова creat.
Одним из отличий создания файла при помощи open является то, что но- вый файл сразу же оказывается открытым заданным способом. Для этого в со- став второго операнда кроме O_CREAT включается константа O_RDONLY,
O_WRONLY или O_RDWR. Естественно, что запрашиваемый доступ к файлу не должен выходить за рамки прав доступа, задаваемых параметром mode.
Иначе вызов open возвратит признак ошибки. Например, следующий вызов предназначен для создания файла, а также для его открытия на чтение и запись: fil = open(“/tmp/newfile”, O_RDWR| O_CREAT, 0760);
Третья константа, включаемая в состав второго операнда вызова open, определяет поведение этого вызова, если файл с заданным именем уже суще- ствует. В частности, если данная константа опущена, то существующий файл
227 будет открыт на запись (если позволяют права доступа к нему), а параметр mode игнорируется.
Если в состав второго операнда включена константа O_EXCL (exclusive – исключительный), то в случае существования файла вызов open завершится с ошибкой. Пример такого вызова: fil = open(“abc”, O_WRONLY | O_CREAT| O_EXCL, 0760);
Если в состав второго операнда включена константа O_TRUNC, то в слу- чае существования файла он будет усечен до нулевого размера (если позволяют права доступа). Пример такого вызова: fil = open(“abc”, O_WRONLY | O_CREAT| O_TRUNC, 0760);
Функции стандартной библиотеки. Для создания файла используется описанная ранее функция fopen, в качестве второго параметра которой задает- ся строка w или a. Каждая из этих строк приводит к созданию нового файла, от- крытого на запись. Различие между ними проявляется в том случае, если уже существует файл с заданным именем. Строка w приводит к усечению этого файла до нулевой длины, а строка a ограничивается открытием существующего файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Разработайте программу создания файла, получающую имя файла из командной строки. После выполнения программы про- верьте наличие созданного файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.6 Указатель файла
После того как файл открыт любым из изложенных выше способов, про- граммный процесс может выполнять информационный обмен с ним, то есть выполнять или чтение данных из файла, или запись данных в файл. Для каждо- го открытого файла ядро содержит отдельную переменную, называемую указа- телем файла, которая содержит номер того байта файла, начиная с которого бу- дет выполняться последующее чтение файла или запись в него. Поэтому прежде чем выполнять операцию чтения (записи) файла, необходимо четко представлять себе, номер какого байта находится в указателе файла. Например, в результате обычного открытия файла его указатель содержит 0. Если же во второй операнд вызова open добавить константу O_APPEND, то указатель фай- ла будет установлен на его конец.
228
Системные вызовы. Системный вызов lseek позволяет установить указатель файла на любую позицию. Его описание:
#include
#include off_t lseek(int fil, off_t offset, int whence);
При успешном завершении вызов lseek возвращает новое значение ука- зателя файла. В случае ошибки: –1. Первый параметр (fil) – номер файла, от- крытого ранее. Второй параметр (offset) задает требуемое смещение относи- тельно той позиции указателя файла, которая задается третьим параметром
(whence), который можно задать в виде одной из следующих трех констант, определенных в файле <unistd.h>:
SEEK_SET – смещение прибавляется к началу файла;
SEEK_CUR – смещение прибавляется к текущему значению указателя;
SEEK_END – смещение прибавляется к концу файла.
Несмотря на то что тип смещения и тип значения указателя файла один и тот же – off_t (он определен в файле <sys/types.h>), область допусти- мых значений для каждого из них различна. В отличие от указателя файла, принимающего лишь неотрицательные целые значения, смещение может быть и отрицательным.
Вообще говоря, вызов lseekвыдаст признак ошибки для существующе- го файла только в том случае, если мы попытаемся установить указатель файла на позицию, предшествующую началу файла. И наоборот, операция установки указателя на позицию, расположенную далее конца файла, приведет к появле- нию в файле «дыры», заполненной нулями.
Пример использования вызова lseek: off_t newpos; newpos = lseek(fil, (off_t)-16, SEEK_END);
В данном примере новое значение указателя файла будет на 16 меньше, чем значение, соответствующее концу файла. Обратите внимание, что для чис- ла (–16), задающего смещение, используется явное задание типа (off_t).
Интересно отметить, что вызов lseek можно использовать для опреде- ления длины файла. Например, пусть переменная filsize содержит длину файла, тогда: off_t filsize;
229 int fil; filsize = lseek(fil, (off_t) 0, SEEK_END);
Функции стандартной библиотеки. Установку указателя файла выпол- няет стандартная функция fseek. Ее описание:
#include int fseek(FILE *stream, long offset, int whence);
Функция возвращает ненулевое значение только в случае ошибки. Пер- вый параметр (stream) идентифицирует файл. Назначение двух других пара- метров аналогично вызову seek.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Создайте с помощью текстового редактора небольшой файл, а затем с помощью своей программы образуйте в этом файле «ды- ру», наличие которой проверьте с помощью текстового редактора
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.7 Чтение из файла
После того как указатель файла установлен в требуемое место файла, можно выполнить чтение из этого файла требуемого числа байтов.
Системные вызовы. Чтение из файла выполняет системный вызов read.
Его описание:
#include ssize_t read(int fil, void *buffer, size_t n);
Данный вызов выполняет чтение из файла, номер которого задается пер- вым параметром (fil), такого числа байтов, которое задается третьим пара- метром (n). Считанные байты помещаются в буфер, указатель на который зада- ется вторым параметром (buffer). Буфер представляет собой массив, элементы которого имеют произвольный тип (void). (Чаще всего буфер оформляется как массив символьного типа.)
Если выполнение readзавершилось успешно, то он возвращает число байтов, считанных из файла. Это число равно n или меньше его. Второе выпол- няется тогда, когда мы выполняем чтение из конечной части файла, содержа- щей меньше символов, чем запрашивается. Если после этого опять выполнить read
, то он возвратит число 0, означающее, что достигнут конец файла.
В случае ошибки read возвращает –1.
230
Одним из результатов выполнения read является перемещение указателя файла на первый, несчитанный байт файла или на его конец.
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · ·
Следующая программа выполняет подсчет байтов в файле, имя которого передается из командной строки:
#include /* Для вызова exit */
#include
#include
#define BUFSIZE 512 main(int argc, char *argv[])
{ int fil; char buffer[BUFSIZE]; ssize_t nread; long total = 0; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
}
/* Открыть файл для чтения */ if ((fil = open(argv[1], O_RDONLY)) = = -1)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
}
/* Повторять до конца файла */ while((nread = read(fil, buffer, BUFSIZE)) >0) total=total+nread; printf(“total = %ld\n”, total); exit(0);
}
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Поясним, почему длина буфера взята равной 512 байтам. Дело в том, что каждый системный вызов readинициирует считывание с диска блока байтов.
231
Величина этого блока есть число 512, умноженное на степень двойки, и зависит от системы. Если считывать данные с диска небольшими порциями (по не- сколько байтов), то никаких изменений в результатах работы программы не бу- дет. Единственное – резко возрастет время выполнения программы, так как, во- первых, чтение каждой порции байтов требует считывания в лучшем случае из дискового кэша, а в худшем – с диска целого блока. А во-вторых, большое ко- личество системных вызовов приводит к такому же числу переключений ЦП из режима «задача» в режим «ядро» и обратно, что требует заметных затрат вре- мени ЦП.
Функции стандартной библиотеки. Применение для чтения из файла стандартных библиотечных функций позволяет не заботиться об эффективно- сти информационного обмена. Так как, во-первых, эти функции занимаются буферизацией обмена с файлом. Поэтому запрос из программы очередной пор- ции байтов не приводит неизбежно к считыванию блока файла. Если эта порция находится в том блоке, который был считан последним с диска, то требуемые байты стандартная функция находит в своем буфере, откуда она и копирует их в буфер программы. Во-вторых, функции стандартной библиотеки выполняют- ся в режиме «задача» и поэтому их вызов не приводит к переключению процес- сора.
Примером стандартной функции чтения файла является функция чтения символа getc. Ее описание:
#include int getc(FILE *istream);
Данная функция возвращает или код символа или число –1, которое означает «конец файла» (EOF). Единственный параметр функции (istream) представляет собой указатель на структуру FILE. Этот параметр должен быть получен в результате открытия файла в программе (до вызова функции getc).
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните приведенную выше программу подсчета симво- лов.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.8 Запись в файл
После того как указатель файла установлен в требуемое место файла, можно выполнить запись в файл требуемого числа байтов. Если указатель фай- ла был установлен на конец файла, то запись будет выполняться на свободное
232 место за счет увеличения длины файла. Иначе – запись выполняется «поверх» прежнего содержимого файла.
Системные вызовы. Запись в файл выполняет системный вызов write.
Его описание:
#include ssize_t write(int fil, void *buffer, size_t n);
Данный вызов выполняет запись в файл, номер которого задается первым параметром (fil), такого числа байтов, которое задается третьим параметром
(n). Запись в файл выполняется из буфера, указатель на который задается вто- рым параметром (buffer). Буфер представляет собой массив, элементы кото- рого имеют произвольный тип (void).
Если выполнение write завершилось успешно, то он возвращает число байтов, записанных в файл. Почти всегда это число равно n. В случае ошибки write возвращает –1.
Одним из результатов выполнения write является перемещение указа- теля файла на первый байт после записанного участка файла.
В качестве примера приведем фрагмент программы, выполняющий за- пись содержимого буфера outbuf, имеющего длину OBSIZE, в файл с именем abc: fil = open(“abc”, O_RDWR| O_APPEND); write(fil, outbuf, OBSIZE);
Функции стандартной библиотеки. Примером стандартной функции записи в файл является функция записи символа putc. Ее описание:
#include int putc(int c, FILE *ostream);
При успешном завершении данная функция возвращает код символа
(совпадает с параметром c), а в случае ошибки – число –1. Параметр ostream аналогичен параметру istreamдля функции getc.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Создайте программу, выполняющую добавление символов в конец текстового файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
233
8.3.9 Закрытие и уничтожение файла
После того как программа завершила работу с файлом, этот файл жела- тельно закрыть, а если файл в будущем не понадобится, то его желательно уни- чтожить. Каждая из этих операций (особенно уничтожение) освобождает ре- сурсы, использовавшиеся для работы с файлом. При завершении процесса все файлы, открытые им, закрываются автоматически.
Системные вызовы. Закрытие файла выполняет системный вызов close. Его описание:
#include int close(int fil);
В случае успешного завершения вызов close возвращает 0. В случае ошибки: –1. Единственный параметр вызова – номер закрываемого файла.
Для уничтожения файла может быть использован вызов unlink. Его описание:
#include int unlink(const char *pathname);
В случае успешного завершения вызов возвращает 0, иначе –1. Един- ственный параметр (pathname) есть указатель на имя файла.
Функции стандартной библиотеки. Закрытие файлавыполняет стан- дартная функция fclose. Ее описание:
#include int fclose(FILE *stream);
Функция возвращает значение 0 при успешном завершении. В случае ошибки: –1.
Для уничтожения файла вполне достаточно системного вызова unlink, и соответствующая стандартная функция не существует.
8.3.10 Задание
Требуется разработать одну из следующих программ:
1) копирование файла (имя старого и нового файла передаются в ко- мандной строке), используя системные вызовы;
2) копирование файла (имя старого и нового файла передаются в ко- мандной строке), используя стандартные функции;
234 3) вывод на экран содержимого текстового файла, имя которого задается в командной строке, используя системные вызовы;
4) вывод на экран содержимого текстового файла, имя которого задается в командной строке, используя стандартные функции;
5) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы;
6) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции;
7) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы и вывод его на экран, используя системные вызовы;
8) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции и вывод его на экран, используя системные вызовы;
9) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы и вывод его на экран, используя стандартные функции;
10) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции и вывод его на экран, используя стандартные функции.
8.4 Лабораторная работа № 4. Обработка сигналов
8.4.1 Цель работы
Целью выполнения настоящей лабораторной работы является получение навыков программного управления процессами с помощью сигналов.
8.4.2 Подготовка к выполнению работы
В начале выполнения данной работы следует ознакомиться со следующи- ми вопросами из теоретической части пособия:
1) синхронизация процессов с помощью сигналов (пп. 3.4.1) [1, 2] – по- нятие сигнала; типы сигналов; выдача сигнала процессом;
2) терминальное управление процессами (пп. 3.4.2) [1, 2] – управляющий терминал; сеанс; группа процессов; получение информации о процес-
i– добавление текста перед текущей строкой; c – замена текущей строки тек- стом новой строки (строк); d– удаление текущей строки. Пример:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
$p
that I have created 4 new lines польз.
a
I will now enter two new lines of
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Команда редактора $pтребует от него вывода на экран последней строки файла. Поэтому две строки, набранные в примере пользователем, добавляются в конец файла. Пример добавления текста перед текущей строкой:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p
Once I have completed it I shall find польз.
i
I am now inserting two lines of
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример замещения текста текущей строки текстом другой строки:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p
Once I have completed it I shall find польз.
c
After completion, I shall find.
.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
218
В данном примере текущая строка заменена текстом одной строки. На самом деле число новых строк может быть любым. В следующем примере про- изводится удаление двух строк:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
3p
I am now inserting two lines of польз.
d
d
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Одной командой можно удалить целую группу строк. В следующем при- мере производится удаление всего содержимого файла:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
1,$d
1,$p
?
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Если текст файла достаточно большой, рекомендуется выполнять проме- жуточное запоминание файла на диске, используя команду w. Выполнив ее, ре- дактор выведет на экран новую длину файла.
Выход из редактора edв UNIX выполняет команда редактора – q. Пример:
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · · польз.
q
UNIX
$
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните создание нового текстового файла, а затем вый- дите из редактора в UNIX. Далее скорректируйте этот файл, доба- вив новые строки в конец, в начало и в середину файла. Затем уда- лите некоторые строки и проверьте результаты редактирования с помощью вывода файла на экран.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
219
8.1.4 Задание
Для успешной сдачи работы требуется выполнить наизусть следующие операции:
1) создать два трехуровневых поддерева каталогов и поместить в один из каталогов четыре текстовых файла, два из которых имеют в своем имени одинаковую символьную последовательность, называемую да- лее «словом»;
2) с помощью ed отредактировать текстовый файл;
3) вывести файл на экран;
4) выполнить добавление текста в начало, в середину и в конец файла;
5) вывести файл на экран;
6) произвести переименование файла;
7) выполнить копирование файла (исходный файл и файл-копия должны располагаться в разных каталогах);
8) поместить в любой другой каталог скрипт, имеющий два входных па- раметра: имя каталога и набор символов. Скрипт выполняет действия:
вывод на экран перечня файлов, «дочерних» к заданному каталогу, которые имеют в своем имени заданный набор символов;
уничтожение всех остальных файлов заданного каталога;
любые другие действия (по вашему желанию);
9) создать свой инициализационный скрипт, выполняющий действия:
здоровается;
«переделывает» приглашения shell;
запускает вложенный скрипт, созданный в (8), задав ему в каче- стве параметров каталог и «слово» из (1);
любые другие действия (по вашему желанию);
10) выйти из UNIX, а затем войти вновь с целью демонстрации результа- тов выполнения инициализационного скрипта.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Примечание 1. При выполнении задания разрешается ис- пользовать те средства shell, которые рассмотрены в данной ра- боте. В частности нельзя применять управляющие операторы (рас- сматриваются в следующей работе).
Примечание 2. Для избирательного удаления файлов в (8) рекомендуется использовать команду rm с флагом -i, предвари- тельно установив права доступа к файлам. При этом для обеспече-
220 ния автоматического выполнения rm ее стандартный ввод должен быть переключен на вспомогательный файл, содержащий любой символ кроме «y».
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.2 Лабораторная работа № 2.
Управляющие операторы командного языка
8.2.1 Цель работы
Целью выполнения настоящей лабораторной работы является развитие навыков программирования на языке shell путем использования в скриптах управляющих операторов if, case, for, while, until.
8.2.2 Подготовка к выполнению работы
В начале выполнения данной работы обязательно следует изучить следу- ющие вопросы из теоретической части пособия (пп. 2.5.5):
1) оператор двухальтернативного выбора if;
2) оператор многоальтернативного выбора case;
3) оператор цикла с перечислением for;
4) оператор цикла с условием while;
5) оператор цикла с инверсным условием until.
В процессе изучения перечисленных вопросов обязательно следует вы- полнить примеры, приведенные в пособии.
8.2.3 Задание
Требуется разработать программу на языке shell (без использования команды find), выполняющую поиск в заданном поддереве файловой струк- туры всех файлов, имена которых отвечают заданному шаблону. Результатом работы программы является перечень имен искомых файлов на экране.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Примечание. Программа состоит из двух скриптов. Главный скрипт выполняет вывод на экран приглашения ввести с клавиату- ры имя-путь начального каталога и шаблон поиска. Далее он вы- полняет ввод этих данных с клавиатуры и выводит на экран пере- чень искомых файлов в начальном каталоге поиска (если они там
221 есть). Затем он вызывает для каждого подкаталога вложенный скрипт, передав ему два входных параметра: 1) относительное имя подкаталога; 2) шаблон поиска.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Вложенный скрипт выполняет поиск в заданном каталоге искомых фай- лов, а для каждого подкаталога вызывает точно такой же скрипт. (При выпол- нении любого скрипта запускается новый экземпляр shell, поэтому рекур- сивное выполнение скриптов не приводит к каким-либо трудностям.)
8.3 Лабораторная работа № 3.
Операции с файлами в программе на языке Си
8.3.1 Цель работы
Целью выполнения настоящей лабораторной работы является получение начальных навыков использования системных вызовов UNIX в программах на языке Си. Данные вызовы выполняют основные операции с файлами: откры- тие и создание, чтение и запись, закрытие и уничтожение.
8.3.2 Подготовка к выполнению работы
В начале выполнения данной работы рекомендуется изучить следующие вопросы из теоретической части пособия:
1) логические файлы (пп. 7.1.1) [1, 2];
2) открытие файла (пп. 7.1.2) [1, 2];
3) создание файла (пп. 7.1.3) [1, 2];
4) понятие файлового указателя (пп. 7.1.2) [1, 2] и его перемещение
(пп. 7.1.3) [1, 2];
5) чтение из файла и запись в него (пп. 7.1.3) [1, 2];
6) закрытие и уничтожение файла (пп. 7.1.3) [1, 2].
8.3.3 Введение
Разработку программ начнем с программирования операций с файлами.
Для этого программа на Си имеет две основные возможности. Первая из них предполагает непосредственное обращение из программы к ядру ОС с помо- щью системных вызовов. Вторая возможность реализуется путем вызова функ- ций стандартной библиотеки ввода-вывода. Эти функции предоставляют при-
222 кладной программе более удобный интерфейс, освобождая ее, в частности, от собственной буферизации файловых операций. Но основную работу с файлами по-прежнему выполняют системные вызовы, которые теперь делаются не из прикладной программы, а из функций стандартной библиотеки. Не допускается использовать оба перечисленных способа для работы с одним и тем же файлом в одной и той же программе.
Одним из различий двух названных подходов, учитываемых при про- граммировании, является способ задания логического имени файла в програм- ме. В отличие от имени физического файла, которое уникально для всей систе- мы (это или имя-путь или относительное имя – смещение относительно текущего каталога) логическое имя файла обладает уникальностью только в пределах данного процесса. Если для работы с данным файлом в программе применяются системные вызовы, то в качестве логического имени файла ис- пользуется его номер, уникальный для данного процесса.
Если для работы с файлом в программе применяются функции стандарт- ной библиотеки, то в качестве логического имени файла используется указатель на таблицу (структуру), используемую этими функциями для работы с файлом.
Данная структура имеет тип FILE. Одно из ее полей содержит номер файла, позволяя тем самым стандартным функциям осуществлять системные вызовы.
(Полезно заметить, что аналогичные структуры применяются для работы с файлами и в языке Паскаль.)
8.3.4 Открытие существующего файла
Результатом выполнения операции открытия файла является или логиче- ское имя файла, или признак ошибки.
Системные вызовы. Открытие файла выполняет системный вызов open
. Его описание: int open(const char *pathname, int flags, [mode_t mode]);
Данный вызов возвращает в программу или номер открытого файла (не- отрицательное целое число) или –1 (признак ошибки). Ошибка может произой- ти, например, если файл не существует.
Первый параметр (pathname) является указателем на символьную стро- ку, содержащую имя физического файла. В качестве этого имени здесь, а также во всех приводимых ниже системных вызовах и в стандартных функциях, мож- но использовать или имя-путь файла, или его относительное имя.
223
Второй параметр (flags) вызова open определяет запрашиваемый ме- тод доступа к файлу. Этот параметр принимает одно из значений, заданных константами в заголовочном файле <fcntl.h>. (Как и большинство заголо- вочных файлов, <fcntl.h> находится в каталоге /usr/include и может быть включен в программу с помощью директивы #include
Три особо интересных константы:
1) O_RDONLY – открыть файл только для чтения;
2) O_WRONLY– открыть файл только для записи;
3) O_RDWR – открыть файл для чтения и для записи.
Третий параметр (mode) необязательный (об этом свидетельствуют квад- ратные скобки). При открытии существующего файла он не используется.
Пример программы:
#include
#include
{ int fil;
/* Открыть файл для чтения-записи */ if ((fil = open(namefile, O_RDWR)) = = -1)
{ printf(“Невозможно открыть %s\n”, namefile); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
}
Данная программа не выполняет никакой полезной работы, а просто де- монстрирует некоторые важные моменты. Во-первых, всякая файловая опера- ция может завершиться с ошибкой. Поэтому такая возможность должна быть обязательно предусмотрена в программе. Во-вторых, выход из программы
(а точнее – завершение соответствующего программного процесса) выполняет системный вызов exit, единственный параметр которого содержит код завер- шения процесса (0 – успешно; –1 – с ошибкой). Прототип этого системного вызова содержится в заголовочном файле <stdlib.h>.
224
Следующая программа отличается от предыдущей только тем, что имя открываемого файла задается не в тексте программы, а из командной строки shell в качестве параметра запускаемого файла. Допустим, что готовая про- грамма помещена в файл open_file. Тогда для ее запуска можно использо- вать командную строку:
$ open_file abc
Текст программы:
#include
#include
{ int fil; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
}
/* Открыть файл для чтения-записи */ if ((fil = open(argv[1], O_RDWR)) = = -1)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
} shell запускает главную подпрограмму main, присвоив ее аргументу argc значение количества параметров в списке argv, а каждому элементу массива argv значение соответствующего параметра из командной строки. В примере argc равно 2, элемент argv[0] содержит строку символов
«open_file» (имя программы условно является нулевым параметром), argv[1] – строку символов «abc». В начале своего выполнения программа проверяет, правильное ли число параметров ей было передано при запуске.
Функции стандартной библиотеки. Открытие файла выполняет стан- дартная функция fopen. Следующая программа выполняет то же самое, что и программа в предыдущем примере – открывает файл, имя которого пользова- тель набирает в командной строке в качестве параметра запускаемого файла:
225
#include
#include
fopen и т. д.*/ main(int argc, char *argv[])
{
FILE *stream; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
/* Открыть файл для чтения-записи */
} if ((stream = fopen(argv[1], “r+”)) = = NULL)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
} exit(0); /* Нормальный выход */
}
Первый параметр функции fopen – указатель на имя открываемого фи- зического файла. Второй параметр – символьная строка, которая определяет за- прашиваемый метод доступа к файлу: «r» – файл открывается только для чте- ния; «a» – файл открывается только для записи; «r+» – для чтения и записи.
В случае успеха функция fopen заполняет для файла структуру FILE и воз- вращает в переменной stream ее адрес. В случае ошибки возвращается указа- тель NULL.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните приведенные выше программы.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.5 Создание файла
Результатом выполнения операции создания файла является или логиче- ское имя файла или признак ошибки.
Системные вызовы. Для создания файла может быть использован один из двух системных вызовов: open и creat. Вызов creatпредставляет собой традиционный способ создания файла. Его описание:
226 int creat(const char *pathname, mode_t mode);
Первый параметр (pathname) является указателем на символьную стро- ку, содержащую имя физического файла. Параметр mode задает права доступа к файлу. Обычно это 3-значное восьмеричное число, старшая цифра которого определяет права доступа владельца файла, средняя цифра – владельца-группы, а младшая цифра задает права доступа всех остальных пользователей. Напри- мер, восьмеричное число (в Си восьмеричное число начинается с нуля) 0764 за- дает право доступа rwx для владельца, rw – для владельца-группы, r – для всех остальных пользователей. Пример использования вызова creat: fil = creat(“/tmp/newfile”, 0764);
В случае успешного завершения системный вызов creat возвращает но- мер нового файла, открытого на запись. А в случае ошибки возвращает значе- ние –1. Для того чтобы только что созданный файл был открыт для чтения, его необходимо сначала закрыть, а затем открыть вновь.
Если файл, заданный в качестве первого параметра creat уже существу- ет, то, во-первых, второй параметр вызова creat игнорируется. А во-вторых, данный файл усекается до нулевой длины и, следовательно, прежняя информа- ция файла уничтожается.
Рассмотрим применение для создания файла системного вызова open, который предоставляет больше возможностей по сравнению с вызовом creat.
Для этого, во-первых, в состав второго операнда вызова open обязательно должна быть включена константа O_CREAT. Во-вторых, данный вызов должен иметь третий операнд – mode, аналогичный операнду вызова creat.
Одним из отличий создания файла при помощи open является то, что но- вый файл сразу же оказывается открытым заданным способом. Для этого в со- став второго операнда кроме O_CREAT включается константа O_RDONLY,
O_WRONLY или O_RDWR. Естественно, что запрашиваемый доступ к файлу не должен выходить за рамки прав доступа, задаваемых параметром mode.
Иначе вызов open возвратит признак ошибки. Например, следующий вызов предназначен для создания файла, а также для его открытия на чтение и запись: fil = open(“/tmp/newfile”, O_RDWR| O_CREAT, 0760);
Третья константа, включаемая в состав второго операнда вызова open, определяет поведение этого вызова, если файл с заданным именем уже суще- ствует. В частности, если данная константа опущена, то существующий файл
227 будет открыт на запись (если позволяют права доступа к нему), а параметр mode игнорируется.
Если в состав второго операнда включена константа O_EXCL (exclusive – исключительный), то в случае существования файла вызов open завершится с ошибкой. Пример такого вызова: fil = open(“abc”, O_WRONLY | O_CREAT| O_EXCL, 0760);
Если в состав второго операнда включена константа O_TRUNC, то в слу- чае существования файла он будет усечен до нулевого размера (если позволяют права доступа). Пример такого вызова: fil = open(“abc”, O_WRONLY | O_CREAT| O_TRUNC, 0760);
1 ... 15 16 17 18 19 20 21 22 23
Функции стандартной библиотеки. Для создания файла используется описанная ранее функция fopen, в качестве второго параметра которой задает- ся строка w или a. Каждая из этих строк приводит к созданию нового файла, от- крытого на запись. Различие между ними проявляется в том случае, если уже существует файл с заданным именем. Строка w приводит к усечению этого файла до нулевой длины, а строка a ограничивается открытием существующего файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Разработайте программу создания файла, получающую имя файла из командной строки. После выполнения программы про- верьте наличие созданного файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.6 Указатель файла
После того как файл открыт любым из изложенных выше способов, про- граммный процесс может выполнять информационный обмен с ним, то есть выполнять или чтение данных из файла, или запись данных в файл. Для каждо- го открытого файла ядро содержит отдельную переменную, называемую указа- телем файла, которая содержит номер того байта файла, начиная с которого бу- дет выполняться последующее чтение файла или запись в него. Поэтому прежде чем выполнять операцию чтения (записи) файла, необходимо четко представлять себе, номер какого байта находится в указателе файла. Например, в результате обычного открытия файла его указатель содержит 0. Если же во второй операнд вызова open добавить константу O_APPEND, то указатель фай- ла будет установлен на его конец.
228
Системные вызовы. Системный вызов lseek позволяет установить указатель файла на любую позицию. Его описание:
#include
#include
При успешном завершении вызов lseek возвращает новое значение ука- зателя файла. В случае ошибки: –1. Первый параметр (fil) – номер файла, от- крытого ранее. Второй параметр (offset) задает требуемое смещение относи- тельно той позиции указателя файла, которая задается третьим параметром
(whence), который можно задать в виде одной из следующих трех констант, определенных в файле <unistd.h>:
SEEK_SET – смещение прибавляется к началу файла;
SEEK_CUR – смещение прибавляется к текущему значению указателя;
SEEK_END – смещение прибавляется к концу файла.
Несмотря на то что тип смещения и тип значения указателя файла один и тот же – off_t (он определен в файле <sys/types.h>), область допусти- мых значений для каждого из них различна. В отличие от указателя файла, принимающего лишь неотрицательные целые значения, смещение может быть и отрицательным.
Вообще говоря, вызов lseekвыдаст признак ошибки для существующе- го файла только в том случае, если мы попытаемся установить указатель файла на позицию, предшествующую началу файла. И наоборот, операция установки указателя на позицию, расположенную далее конца файла, приведет к появле- нию в файле «дыры», заполненной нулями.
Пример использования вызова lseek: off_t newpos; newpos = lseek(fil, (off_t)-16, SEEK_END);
В данном примере новое значение указателя файла будет на 16 меньше, чем значение, соответствующее концу файла. Обратите внимание, что для чис- ла (–16), задающего смещение, используется явное задание типа (off_t).
Интересно отметить, что вызов lseek можно использовать для опреде- ления длины файла. Например, пусть переменная filsize содержит длину файла, тогда: off_t filsize;
229 int fil; filsize = lseek(fil, (off_t) 0, SEEK_END);
Функции стандартной библиотеки. Установку указателя файла выпол- няет стандартная функция fseek. Ее описание:
#include
Функция возвращает ненулевое значение только в случае ошибки. Пер- вый параметр (stream) идентифицирует файл. Назначение двух других пара- метров аналогично вызову seek.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Создайте с помощью текстового редактора небольшой файл, а затем с помощью своей программы образуйте в этом файле «ды- ру», наличие которой проверьте с помощью текстового редактора
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.7 Чтение из файла
После того как указатель файла установлен в требуемое место файла, можно выполнить чтение из этого файла требуемого числа байтов.
Системные вызовы. Чтение из файла выполняет системный вызов read.
Его описание:
#include
Данный вызов выполняет чтение из файла, номер которого задается пер- вым параметром (fil), такого числа байтов, которое задается третьим пара- метром (n). Считанные байты помещаются в буфер, указатель на который зада- ется вторым параметром (buffer). Буфер представляет собой массив, элементы которого имеют произвольный тип (void). (Чаще всего буфер оформляется как массив символьного типа.)
Если выполнение readзавершилось успешно, то он возвращает число байтов, считанных из файла. Это число равно n или меньше его. Второе выпол- няется тогда, когда мы выполняем чтение из конечной части файла, содержа- щей меньше символов, чем запрашивается. Если после этого опять выполнить read
, то он возвратит число 0, означающее, что достигнут конец файла.
В случае ошибки read возвращает –1.
230
Одним из результатов выполнения read является перемещение указателя файла на первый, несчитанный байт файла или на его конец.
· · · · · · · · · · · · · · · · · · · · · · · · · ·
Пример
· · · · · · · · · · · · · · · · · · · · · · · ·
Следующая программа выполняет подсчет байтов в файле, имя которого передается из командной строки:
#include
#include
#include
#define BUFSIZE 512 main(int argc, char *argv[])
{ int fil; char buffer[BUFSIZE]; ssize_t nread; long total = 0; if (argc !=2)
{ printf(“Need 1 argument for open\n”); exit(1); /* Выход по ошибке */
}
/* Открыть файл для чтения */ if ((fil = open(argv[1], O_RDONLY)) = = -1)
{ printf(“Cannot open file %s\n”, argv[1]); exit(1); /* Выход по ошибке */
}
/* Повторять до конца файла */ while((nread = read(fil, buffer, BUFSIZE)) >0) total=total+nread; printf(“total = %ld\n”, total); exit(0);
}
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Поясним, почему длина буфера взята равной 512 байтам. Дело в том, что каждый системный вызов readинициирует считывание с диска блока байтов.
231
Величина этого блока есть число 512, умноженное на степень двойки, и зависит от системы. Если считывать данные с диска небольшими порциями (по не- сколько байтов), то никаких изменений в результатах работы программы не бу- дет. Единственное – резко возрастет время выполнения программы, так как, во- первых, чтение каждой порции байтов требует считывания в лучшем случае из дискового кэша, а в худшем – с диска целого блока. А во-вторых, большое ко- личество системных вызовов приводит к такому же числу переключений ЦП из режима «задача» в режим «ядро» и обратно, что требует заметных затрат вре- мени ЦП.
Функции стандартной библиотеки. Применение для чтения из файла стандартных библиотечных функций позволяет не заботиться об эффективно- сти информационного обмена. Так как, во-первых, эти функции занимаются буферизацией обмена с файлом. Поэтому запрос из программы очередной пор- ции байтов не приводит неизбежно к считыванию блока файла. Если эта порция находится в том блоке, который был считан последним с диска, то требуемые байты стандартная функция находит в своем буфере, откуда она и копирует их в буфер программы. Во-вторых, функции стандартной библиотеки выполняют- ся в режиме «задача» и поэтому их вызов не приводит к переключению процес- сора.
Примером стандартной функции чтения файла является функция чтения символа getc. Ее описание:
#include
Данная функция возвращает или код символа или число –1, которое означает «конец файла» (EOF). Единственный параметр функции (istream) представляет собой указатель на структуру FILE. Этот параметр должен быть получен в результате открытия файла в программе (до вызова функции getc).
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Выполните приведенную выше программу подсчета симво- лов.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
8.3.8 Запись в файл
После того как указатель файла установлен в требуемое место файла, можно выполнить запись в файл требуемого числа байтов. Если указатель фай- ла был установлен на конец файла, то запись будет выполняться на свободное
232 место за счет увеличения длины файла. Иначе – запись выполняется «поверх» прежнего содержимого файла.
Системные вызовы. Запись в файл выполняет системный вызов write.
Его описание:
#include
Данный вызов выполняет запись в файл, номер которого задается первым параметром (fil), такого числа байтов, которое задается третьим параметром
(n). Запись в файл выполняется из буфера, указатель на который задается вто- рым параметром (buffer). Буфер представляет собой массив, элементы кото- рого имеют произвольный тип (void).
Если выполнение write завершилось успешно, то он возвращает число байтов, записанных в файл. Почти всегда это число равно n. В случае ошибки write возвращает –1.
Одним из результатов выполнения write является перемещение указа- теля файла на первый байт после записанного участка файла.
В качестве примера приведем фрагмент программы, выполняющий за- пись содержимого буфера outbuf, имеющего длину OBSIZE, в файл с именем abc: fil = open(“abc”, O_RDWR| O_APPEND); write(fil, outbuf, OBSIZE);
Функции стандартной библиотеки. Примером стандартной функции записи в файл является функция записи символа putc. Ее описание:
#include
При успешном завершении данная функция возвращает код символа
(совпадает с параметром c), а в случае ошибки – число –1. Параметр ostream аналогичен параметру istreamдля функции getc.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Создайте программу, выполняющую добавление символов в конец текстового файла.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
233
8.3.9 Закрытие и уничтожение файла
После того как программа завершила работу с файлом, этот файл жела- тельно закрыть, а если файл в будущем не понадобится, то его желательно уни- чтожить. Каждая из этих операций (особенно уничтожение) освобождает ре- сурсы, использовавшиеся для работы с файлом. При завершении процесса все файлы, открытые им, закрываются автоматически.
Системные вызовы. Закрытие файла выполняет системный вызов close. Его описание:
#include
В случае успешного завершения вызов close возвращает 0. В случае ошибки: –1. Единственный параметр вызова – номер закрываемого файла.
Для уничтожения файла может быть использован вызов unlink. Его описание:
#include
В случае успешного завершения вызов возвращает 0, иначе –1. Един- ственный параметр (pathname) есть указатель на имя файла.
Функции стандартной библиотеки. Закрытие файлавыполняет стан- дартная функция fclose. Ее описание:
#include
Функция возвращает значение 0 при успешном завершении. В случае ошибки: –1.
Для уничтожения файла вполне достаточно системного вызова unlink, и соответствующая стандартная функция не существует.
8.3.10 Задание
Требуется разработать одну из следующих программ:
1) копирование файла (имя старого и нового файла передаются в ко- мандной строке), используя системные вызовы;
2) копирование файла (имя старого и нового файла передаются в ко- мандной строке), используя стандартные функции;
234 3) вывод на экран содержимого текстового файла, имя которого задается в командной строке, используя системные вызовы;
4) вывод на экран содержимого текстового файла, имя которого задается в командной строке, используя стандартные функции;
5) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы;
6) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции;
7) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы и вывод его на экран, используя системные вызовы;
8) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции и вывод его на экран, используя системные вызовы;
9) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя системные вызовы и вывод его на экран, используя стандартные функции;
10) ввод с клавиатуры содержимого текстового файла, имя которого зада- ется в командной строке, используя стандартные функции и вывод его на экран, используя стандартные функции.
8.4 Лабораторная работа № 4. Обработка сигналов
8.4.1 Цель работы
Целью выполнения настоящей лабораторной работы является получение навыков программного управления процессами с помощью сигналов.
8.4.2 Подготовка к выполнению работы
В начале выполнения данной работы следует ознакомиться со следующи- ми вопросами из теоретической части пособия:
1) синхронизация процессов с помощью сигналов (пп. 3.4.1) [1, 2] – по- нятие сигнала; типы сигналов; выдача сигнала процессом;
2) терминальное управление процессами (пп. 3.4.2) [1, 2] – управляющий терминал; сеанс; группа процессов; получение информации о процес-