Стандартные потоки
Что касается файловой системы языка С, то в начале выполнения программы автоматически открываются три потока. Это stdin (стандартный поток ввода), stdout (стандартный поток вывода) и stderr (стандартный поток ошибок). Обычно эти потоки направляются к консоли, но в средах, которые поддерживают перенаправление ввода/вывода, они могут быть перенаправлены операционной системой на другое устройство. (Перенаправление ввода/вывода поддерживается, например, такими операционными системами, как Windows, DOS, UNIX и OS/2.)
Так как стандартные потоки являются указателями файлов, то они могут использоваться системой ввода/вывода языка С также для выполнения операций ввода/вывода на консоль. Например, putchar() может быть определена таким образом:
Вообще говоря, stdin используется для считывания с консоли, a stdout и stderr — для записи на консоль.
В роли указателей файлов потоки stdin , stdout и stderr можно применять в любой функции, где используется переменная типа FILE * . Например, для ввода строки с консоли можно написать примерно такой вызов fgets() :
И действительно, такое применение fgets() может оказаться достаточно полезным. Как уже говорилось в этой книге, при использовании gets() не исключена возможность, что массив, который используется для приема вводимых пользователем символов, будет переполнен. Это возможно потому, что gets() не проводит проверку на отсутствие нарушения границ. Полезной альтернативой gets() является функция fgets() с аргументом stdin , так как эта функция может ограничивать число читаемых символов и таким образом не допустить переполнения массива. Единственная проблема, связанная с fgets() , состоит в том, что она не удаляет символ новой строки (в то время как gets() удаляет!), поэтому его приходится удалять «вручную», как показано в следующей программе:
He забывайте, что stdin , stdout и stderr — это не переменные в обычном смысле, и им нельзя присваивать значение с помощью fopen() . Кроме того, именно потому, что в начале работы программы эти указатели файлов создаются автоматически, в конце работы они и закрываются автоматически. Так что и не пытайтесь самостоятельно их закрыть.
Связь с консольным вводом / выводом
В языке С консольный и файловый ввод/вывод не слишком отличаются друг от друга. Функции консольного ввода/вывода, описанные в главе 8, на самом деле направляют результаты своих операций на один из потоков — stdin или stdout , и по сути, каждая из них является специальной версией соответствующей файловой функции. Функции консольного ввода/вывода для того и существуют, чтобы было удобно именно программисту.
Как говорилось в предыдущем разделе, ввод/вывод на консоль можно выполнять с помощью любой файловой функции языка С. Однако для вас может быть сюрпризом, что, оказывается, операции ввода/вывода на дисковых файлах можно выполнять с помощью функции консольного ввода/вывода, например, printf() ! Дело в том, что все функции консольного ввода/вывода, о которых говорилось в главе 8, выполняют свои операции с потоками stdin и stdout . В средах, поддерживающих перенаправление ввода/вывода, это равносильно тому, что stdin или stdout могут быть перенаправлены на устройство, отличное от клавиатуры или экрана. Проанализируйте, например, следующую программу:
Предположим, что эта программа называется TEST. При ее нормальном выполнении на экран выводится подсказка, затем читается строка, введенная с клавиатуры, и, наконец, эта строка выводится на экран. Однако в средах, в которых поддерживается перенаправление ввода/вывода, один из потоков stdin или stdout (или оба одновременно) можно перенаправить в файл. Например, в среде DOS или Windows следующий запуск TEST
приводит к тому, что вывод этой программы будет записан в файл по имени OUTPUT. А следующий запуск TEST
направляет поток stdin в файл по имени INPUT, а поток стандартного вывода — в файл по имени OUTPUT.
Когда С-программа завершается, то все перенаправленные потоки возвращаются в состояния, которые были установлены по умолчанию.
Перенаправление стандартных потоков: функция freopen()
Для перенаправления стандартных потоков можно воспользоваться функцией freopen() . Эта функция связывает имеющийся поток с новым файлом. Так что она вполне может связать с новым файлом и стандартный поток. Вот прототип этой функции:
где имя_файла — это указатель на имя файла, который требуется связать с потоком, на который указывает указатель поток . Файл открывается в режиме режим ; этот параметр может принимать те же значения, что и соответствующий параметр функции fopen() . Если функция freopen() выполнилась успешно, то она возвращает поток , а если встретились ошибки, — то NULL .
В следующей программе показано использование функции freopen() для перенаправления стандартного потока вывода stdout в файл с именем OUTPUT.
Стандартные потоки вывода (stdout) Windows
С помощью переназначения устройств ввода/вывода одна программа может направить свой вывод на вход другой или перехватить вывод другой программы, используя его в качестве своих входных данных. Таким образом, имеется возможность передавать информацию от процесса к процессу при минимальных программных издержках.
Есть 3 файловых дескриптора: stdin — стандартный ввод, stdout — стандартный вывод и stderr — стандартный поток ошибок. В скриптах 1 означает stdout , а 2 — stderr .
Практически это означает, что для программ, которые используют стандартные входные и выходные устройства, операционная система позволяет:
- перенаправлять stdout в файл
- перенаправлять stderr в файл
- перенаправлять stdout в stderr
- перенаправлять stderr в stdout
- перенаправлять stderr и stdout в файл
- перенаправлять stderr и stdout в stdout
- перенаправлять stderr и stdout в stderr
- перенаправление stderr и stdout по конвейеру
Все вышесказанное является привычной обыденностью для любого пользователя любой nix системы, но в среде Windows, данные возможности применяются крайне редко, хотя на самом деле они там есть и всё практически идентично.
А теперь примеры:
1. Перенаправление стандартного потока программы в файл с заменой содержимого файла
при этом на экран ничего кроме ошибок не выводится, а все записывается в лог. Если остановить пинг, и запустить заново, предыдущий лог полностью затирается новым.
2. Перенаправление стандартного потока программы в файл с до записью содержимого лога
Тоже самое, но при прерывание пинга и начале нового, старое содержимое лога не затрется, а новое дописывается в конец
3. Перенаправление потока ошибок программы в фаил с заменой содержимого
при этом, стандартный поток программы пойдет на экран, а ошибки будут записаны в лог, с заменой содержимого.
4. То же самое, но с до записью содержимого лога.
5. Следующая конструкция позволяет перенаправить информацию между потоками (между стандартным потоком и потоком ошибок, или наоборот).
или с до записью лога
В данном примере стандартный поток ошибок пересылается в стандартный поток (конструкция 2>&1 ) а потом стандартный поток (уже с завернутым в него потоком ошибок) посылается в лог.
6. В этом примере все наоборот, стандартный поток, пересылается в поток ошибок и уже поток ошибок перенаправляется в лог:
или с до записью лога
7. По аналогии с Linux системами в Windows можно перенаправить весь или часть вывода программы в виртуальное устройство, а проще говоря слить в мусор.
Таким устройством является nul, и делать перенаправление в него можно используя все выше представленные комбинации. Например
В Linux есть еще одна конструкция перенаправления, а именно &>/var/log/log.txt , она перенаправляет ВСЕ без исключения потоки программы в указанное место, по сути являясь более коротким и более грамотным аналогом конструкции >log.txt 1>&2 . Но к сожалению в Windows это не работает.
А теперь давайте немного разберемся в прикладных различиях между работой данных методов. В нормальных приложениях все разбито на потоки, но у большинства виндовых утилит это не так, пинг например, пишет все в стандартный поток (на экран), поэтому для него конструкция вида 2> не имеет смысла. Но есть еще не виндовые утилиты, для примера возьмем curl (мой любимый).
Он разделяет 3 вида вывода, вывод полезной информации, вывод служебной информации и вывод ошибок. Если перенаправить вывод так: > или >> или 1> или 1>> то по завершению запроса отобразится служебная информация о запросе, а вся полезная информация уйдет в лог (это именно то, что уходит по конвейеру | ).
А теперь сделаем заведомо ошибочный запрос, изменив протокол http на http3 не меняя вывода в лог. В итоге мы получим ошибку на экране.
Изменим вывод в лог на один из этих: 2> или 2>> ошибка ранее выводившаяся на экран, попала в лог, и на экране ничего не будет (служебной информации нет, так как запрос произведен не был).
Вернемся к первому скриншоту на котором мы видим вывод служебной информации, по сути, не будь у курла ключа -s который подавляет вывод служебной информации, нам пришлось бы пользоваться конструкциями из пятого и шестого примеров.
И вывод был бы таким:
То есть, полная тишина, вся информация, как то полезный вывод, ошибки программы, служебная информация, все ушло в лог.
На данном скриншоте, конструкцией 2>&1 мы завернули поток ошибок в стандартный поток, а конструкцией > 5555.txt стандартный поток перенаправили в лог. Если вместо > 5555.txt использовать 2> 5555.txt , то есть перенаправить в лог стандартный поток ошибок, мы увидим весь вывод программы (и ошибки, и служебную информацию и полезный вывод) на экране. Конструкция 2>&1 имеет больший приоритет, а по ней уже все завернуто в стандартный поток.
Делать пример с заворотом стандартного потока в поток ошибок ( 1>&2 ) я не буду, ибо там все точно так же.
Лабораторная работа № 1. Ввод-вывод в стандартные файлы в ОС Windows (стр. 1 )
| Из за большого объема этот материал размещен на нескольких страницах: 1 2 3 4 5 |
Лабораторная работа № 1
Практически невозможно построить содержательную программу, которая бы не использовала ввод и вывод данных. И ввод и вывод данных в современных операционных системах требует применения внутренних средств, внутренних функций ОС. Все более-менее полноценные ОС в качестве средства доступа к внутренним функциям используют не программные прерывания (как это делала MS-DOS и подобные ей), а обращения к внутренним функциям ОС, оформленным с точки зрения доступа к ним как подпрограммы, вызываемые стандартными машинными командами.
Совершенно обязательной системной функцией, используемой абсолютно любой программой при ее завершении, является системная функция завершения. В операционной системе MS Windows эта функция имеет имя ExitProcess.
Функция завершения ExitProcess требует только одного аргумента, который задает значение кода возврата. Этот аргумент может принимать любое выбранное программистом значение. Вызов функции ExitProcess на языке Си описывается прототипом
где аргумент задает код возврата, который передается в программу, запустившую данную. В частности, если программа, делающая вызов ExitProcess, запускалась из командной строки, код возврата передается в командную оболочку операционной системы.
Даже для простейших программ необходимо иметь возможность ввода-вывода данных. Наиболее общим средством для этого служат стандартные файлы. Стандартный файл ввода после запуска программы, если он не был явно переназначен, берет данные с клавиатуры. Стандартный файл вывода автоматически назначается на экран.
Для использования системных функций ввода и вывода в программах необходимо иметь хэндл (handle), который обозначает условным логическим номером дескриптор (описатель) файла, необходимый для организации операций с файлом. В ОС MS Windows хэндлы стандартных файлов непосредственно недоступны, но для получения этих хэндлов, которые отличны от 0, 1 и 2, нужно выполнить специальные запросы к ОС. При использовании же стандартных файлов требуются лишь системные функции чтения из файла и записи в файл. В современных операционных системах эти функции сделаны универсальными и предназначены для работы не только со стандартными файлами, но и с обычными файлами, а также для иных операций ввода-вывода.
Прототип системной функции чтения из файла в MS Windows описывается на языке Си в следующем виде
BOOL WINAPI ReadFile(HANDLE hFile, LPVOID Buffer, DWORD len,
где все аргументы 4-байтовые, причем первый из них задает хэндл (для наших ближайших целей – хэндл файла), второй определяет место (буфер) для считываемых данных, третий задает число байтов, которые запрашиваются для ввода, четвертый – возвращаемый параметр – определяет после выполнения системной функции, сколько байтов было действительно прочитано. Последний аргумент имеет достаточно специальное значение и нашем изложении не будет использоваться, для чего его значение будет задаваться как NULL. Число прочитанных байтов может быть меньше числа запрошенных, если при чтении обнаружен конец файла.
Прототип системной функции записи в файл описывается в виде
BOOL WINAPI WriteFile(HANDLE hFile, LPCVOID Buffer, DWORD len,
где все аргументы также 4-байтовые, причем первый из них задает хэндл, второй указывает место (буфер) записываемых данных, третий задает сколько байтов следует записать при вводе, а четвертый – возвращаемый параметр – определяет сколько байтов было действительно записано. Последний аргумент также использоваться не будет, и его значение всегда будет полагаться NULL.
Для получения хэндлов стандартного файла ввода-вывода в MS Windows предназначена системная функция GetStdHandle, имеющая следующий прототип
где аргумент используется обычно в виде символической константы, определенной в заголовочном файле winbase.h следующими тремя значениями
#define STD_INPUT_HANDLE (DWORD)-10
#define STD_OUTPUT_HANDLE (DWORD)-11
#define STD_ERROR_HANDLE (DWORD)-12.
Функция GetStdHandle после запроса может вернуть требуемый хэндл или, в случае ошибки, специальное значение, символически задаваемое как INVALID_HANDLE_ERROR, описанное в том же заголовочном файле как
#define INVALID_HANDLE_VALUE (HANDLE)-1.
Возвращаемые значения функций ReadFile и WriteFile имеют тип BOOL и представляют собой логические значения правильности выполнения функции. Если это значение FALSE, то произошла ошибка. Код этой ошибки можно получить с помощью вспомогательной функции MS Windows, которая называется GetLastError. Эта функция не имеет аргументов и возвращает в качестве собственного значения код ошибки, который можно в дальнейшем анализировать.
Использование описанных выше системных функций иллюстрируется следующим примером:
HANDLE hstdin, hstdout;
WriteFile(hstdout, buffer, actlen,&actlen,0);
1. изучить системные функции стандартного ввода-вывода MS Windows.
2. составить программу с использованием изученных функций по указанию преподавателя.
Лабораторная работа № 2
БАЗОВЫЕ СРЕДСТВА ИСПОЛЬЗОВАНИЯ ФАЙЛОВОЙ СИСТЕМЫ
Для полноценного использования файловой системы необходимо иметь средства для работы с любым файлом, указанным внутри программы. Для выполнения чтения из файла или записи в него требуется значение хэндла, связанного с этим файлом. Хэндл файла может быть получен в ОС Windows системной функцией CreateFile. Работа с файлом завершается системной функцией CloseHandle.
Функция CreateFile предназначена и для собственно создания и, в частности, для открытия уже существующего файла. Заметим, что в MS Windows имеется два варианта функции создания и открытия файла, отличающихся последней дополнительной буквой А или W. Первый вариант отвечает использованию кодирования символов по стандарту ANSI, а второй – по стандарту UNICODE. Второй вариант задействует не один, а два байта на каждый символ. На данный момент будем использовать более консервативный вариант ANSI.
Функция CreateFile имеет 7 аргументов, первым из которых является имя открываемого файла, вторым – код желаемого доступа к файлу, третьим – код режима разделяемого использования файла, далее следует адрес атрибутов защиты файла (мы не будем использовать эти довольно не простые возможности и этот аргумент всегда будем полагать равным NULL, т. е. сообщать ОС об отсутствии информации о защите файла). Пятый аргумент задает поведение ОС при открытии файла (диспозицию), шестой – атрибуты файла, а последний имеет специальный характер и рассматриваться нами не будет (значение этого аргумента будем указывать как NULL). При удачном выполнении операции функция CreateFile возвращает значение хэндла файла, а при ошибке выдает вместо него значение, задаваемое символической константой INVALID_HANDLE_VALUE. На языке Си прототип функции CreateFileA записывается в виде
HANDLE CreateFile(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAttributes, HANDLE hTemplateFile);
где lpFileName задает указатель на имя файла, dwDesiredAccess – код желаемого доступа, dwShareMode – код режима разделения работы с файлом, lpSecurityAttributes – указатель на атрибут защиты файла, dwCreationDisposition – код действия над файлом во время выполнения данной функции, dwFlagsAttributes – флаги атрибутов, hTemplateFile – хэндл файла шаблона с расширенными атрибутами.
Параметр dwFlagsAttributes задает атрибут открываемого файла. В этом атрибуте используются отдельные биты. Обычный (нормальный) файл имеет атрибут, равный 0, файл, доступный только для чтения – атрибут 1, скрытый файл – атрибут, равный 2, системный файл – атрибут, равный 4. Чаще всего в качестве этого параметра можно использовать символическую константу FILE_ATTRIBUTE_NORMAL. Для кодирования доступа к открываемому файлу служат две символические константы GENERIC_READ и GENERIC_WRITE, задающих соответственно разрешение на чтение и запись в файл. Они могут использоваться совместно, путем объединения (операцией логического ИЛИ) в одном параметре dwDesiredAccess, или раздельно. Совместное использование файлов задается символическим константами FILE_SHARE_READ и FILE_SHARE_WRITE, которые также при необходимости можно комбинировать в одном параметре. Для задания действий с файлом служат символические константы CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING, OPEN_ALWAYS, TRUNCATE_EXISTING, которые нельзя комбинировать в одном параметре dwCreationDisposition, а следует использовать порознь. Константа CREATE_NEW приводит к тому, что если заданный файл уже существует, то функция возвращает ошибку. Константа CREATE_ALWAYS требует создания файла всегда, даже взамен существующего, при этом содержимое старого файла теряется. Константа OPEN_EXISTING требует открывать только существующий файл, если при этом файл с указанным именем не существует, то функция возвращает ошибку. Константа OPEN_ALWAYS приводит к тому, что существующий файл открывается, а если файл не существует, то он создается. Константа TRUNCATE_EXISTING приводит к следующим действиям: если файл существует, то он открывается, после чего длина файла устанавливается равной нулю, содержимое файла при этом теряется; если же файл не существовал, то функция возвращает ошибку.