Библиотека сайта rus-linux.net
Code Ninja: стандартные потоки ввода и вывода
Оригинал: Code Ninja: Stdin and stdout
Автор: Ben Everard
Дата публикации: 12 марта 2016 г.
Перевод: А.Панин
Дата перевода: 11 марта 2016 г.
Создавайте утилиты с интерфейсом командной строки, добавляя в свои программы на языке Python код для работы с командной оболочкой Linux.
Для чего это нужно?
- Вы поймете один из базовых принципов взаимодействия утилит с интерфейсом командной строки.
- Вы сможете разрабатывать собственные утилиты, работающие с командной оболочкой Linux.
- Вы научитесь использовать дополнительные возможности языка программирования Python.
Если вы использовали командную оболочку Linux для выполнения каких-либо сложных операций, а не базовых команд, вы наверняка знакомы с концепциями стандартных потоков ввода (stdin) и вывода (stdout). Это каналы, посредством которых командная оболочка организует передачу данных между утилитами, связываемыми с помощью символа программного канала (|). Примером может являться следующая команда:
dmesg | grep "USB"
Здесь первая утилита (dmesg) передает содержимое буфера сообщений ядра ОС в стандартный поток вывода (stdout). Символ | позволяет принимать данные из стандартного потока вывода (stdout) одной утилиты и отправлять их в стандартный поток ввода (stdin) другой утилиты. После этого утилита grep исследует все строки, полученные из стандартного потока ввода (stdin), и выводит лишь те строки, которые содержат подстроку "USB". Благодаря концепции стандартных потоков ввода и вывода нам удалось создать простейший инструмент для вывода сообщений ядра ОС, относящихся к подсистеме USB.
В соответствии с философией UNIX, программы должны быть максимально упрощены и работать со стандартными потоками ввода и вывода всегда, когда это возможно. Если они могут работать с этими потоками, они могут комбинироваться с другими программами UNIX различными способами. Благодаря данному подходу вы можете сконцентрировать свои усилия на разработке программы, которая будет решать одну задачу самым оптимальным способом. Вместо того, чтобы создавать программу с реализацией всех мыслимых функций, вы можете заняться реализацией ее основной функции с прицелом на то, что пользователь сможет использовать другие инструменты UNIX для получения доступа к дополнительным функциям путем связывания программ.
Давайте воспользуемся языком программирования Python для реализации стандартных инструментов UNIX, которые будут взаимодействовать посредством стандартных потоков ввода и вывода с возможностью их последующего связывания.
Начнем с аналога утилиты cat. Название данной утилиты расшифровывается как "catenate" ("связывание"). Если говорить максимально упрощенно, данная утилита используется для передачи содержимого одного или нескольких файлов в стандартный поток вывода. На языке программирования Python ее реализация поистине проста:
Все, что нам нужно для передачи каких-либо данных в стандартный поток вывода в случае использования языка программирования Python - это вывести их в терминал. В данном случае мы используем для этого функцию print(), которая является стандартным инструментом для вывода данных в терминал в Python 3. Она также может использоваться в Python 2, если код содержит первую строку с директивой import. Я осуществил вывод данных описанным способом для максимально последовательного изложения, ведь мы будем рассматривать некоторые дополнительные возможности функции print() немного позднее.
Каждая строка, читаемая из текстового файла, содержит символ перехода на новую строку в самом конце, поэтому мы используем параметр end="", чтобы функция ptint() не добавляла еще один дополнительный символ перехода на новую строку, из-за которого все выводимые строки будут разделяться пустыми строками.
Утилита GNU cat имеет гораздо больше возможностей, чем наш аналог, но при этом в ней используется тот же принцип чтения файлов и отправки их содержимого в стандартный поток вывода
Ввод и вывод
Мы можем получить аргументы, передаваемые утилите, из списка sys.argv. Это простой список, содержащий все параметры, переданные утилите с помощью командной оболочки Linux (обратитесь к разделу "Аргументы" для ознакомления с дополнительной информацией). В данном случае мы просто обходим все элементы упомянутого списка в рамках цикла, надеясь, что каждый из элементов является путем к файлу. Таким образом, следующая команда позволит передать содержимое файлов файл1 и файл2 в стандартный поток вывода (stdout) нашего приложения:
python cat.py файл1 файл2
Вы можете связать данную утилиту с одной из других утилит UNIX, такой, как утилита wc (расшифровывается как "word count" - "количество слов") аналогичным образом:
python cat.py /usr/share/dict/words | wc
Утилита wc выводит информацию о количестве строк, слов и символов в текстовом файле. Давайте реализуем и ее:
Как вы видите, получение входных данных из стандартного потока ввода (stdin) не связано с какими-либо трудностями. По сути, осуществляется построчное чтение точно таким же образом, как в случае с файлами.
Два созданных нами инструмента могут быть связаны друг с другом следующим образом:
python cat.py /usr/share/dict/words | python wc.py
На данный момент мы не реализовали никакого механизма обработки ошибок. Именно поэтому вы столкнетесь с исключением языка Python в том случае, если попытаетесь передать нашему аналогу утилиты cat путь к несуществующему файлу:
python cat.py /nosuchfile | python wc.py
При этом сообщение об ошибке, выведенное первой утилитой, не будет передано в стандартный поток ввода (stdin) второй утилите, что на первый взгляд может показаться немного странным. Вместо этого данное сообщение просто выводится в терминал. Это происходит по причине того, что сообщения об ошибках не выводятся с помощью стандартного потока вывода (stdout): вместо этого они выводятся с помощью отдельного потока, называемого стандартным потоком ошибок (stderr). Это означает, что программа может отправлять сообщения об ошибках без прерывания процесса вывода данных посредством стандартного потока вывода (stdout). Например, вы можете использовать утилиту cat.py для вывода содержимого нескольких файлов, причем один из этих файлов может отсутствовать на диске. Благодаря наличию стандартного потока ошибок (stderr), вы можете сообщить пользователю о проблеме, передавая при этом содержимое существующих файлов связанной утилите обычным способом. Давайте рассмотрим вариант модификации исходного кода нашего аналога утилиты cat для обработки подобных ошибок:
Как вы видите, все, что нужно сделать для вывода сообщения об ошибке посредством стандартного потока ошибок (stderr) - это использовать функцию print() с параметром file=sys.stderr, причем в остальном процедура ничем не отличается от записи в стандартный поток вывода (stdout).
При этом стоит помнить и о том, что данные из стандартного потока ошибок (stderr) не обязаны выводиться в терминал; например, вы можете перенаправить их в файл. Если же вы выполняете команды с помощью каких-либо средств автоматизации, таких, как at или cron, функция записи любых сообщений об ошибках в файл становится критически важной. Вы можете перенаправить рассматриваемый стандартный поток утилиты в файл с помощью следующей команды:
утилита 2> имя_файла
Все данные, которые будут переданы утилитой в стандартный поток вывода (stdout), будут выведены в терминал (вы также можете настроить передачу этих данных другим утилитам). Другим вариантом является сообщение для командной оболочки Bash о том, что необходимо отправлять данные из стандартного потока ошибок (stderr) в стандартный поток вывода (stdout). В результате стандартные потоки вывода и ошибок сформируют общий поток текстовых данных, который может быть передан любой связанной утилите:
утилита1 2>&1 | утилита2
Даже в том случае, если в качестве одного из аргументов нашего аналога утилиты cat будет использован путь к несуществующему файлу, она корректно обработает все существующие файлы
Аргументы
Для корректного взаимодействия с командной оболочкой Linux в рамках приложения должны использоваться стандартные потоки ввода (stdin), вывода (stdout) и ошибок (stderr). Еще одним аспектом взаимодействия с командной оболочкой Linux является корректная обработка аргументов и флагов. Например, в разработанном нами с использованием языка программирования Python сценарии с именем cat мы получали список путей к файлам, переданных в качестве аргументов. Мы осуществляли доступ к этому списку посредством переменной sys.argv. Данная переменная сама по себе является списком, содержащим все аргументы, переданные сценарию (первым аргументом является имя самого сценария; в рассмотренном примере мы обрабатывали все аргументы начиная со второго).
Однако, если ваша утилита принимает множество параметров, их разбор может оказаться слишком сложным. К счастью, для этой цели существует специальный модуль: argparse. Давайте рассмотрим простой пример его использования. Мы будем использовать простой аналог утилиты echo, который направляет вывод в поток стандартного вывода (stdout) за исключением тех случаев, когда присутствует флаг -stderr, в которых вывод отправляется в стандартный поток ошибок (stdrerr) (это всего лишь пример, стандартная утилита echo не поддерживает подобных флагов).
Мы реализуем обработку данного флага с помощью модуля argparser (этот модуль впервые появился в Python 2.7, поэтому приведенный ниже пример не будет работать в случае использования более старой версии интерпретатора). Благодаря данному модулю вам придется лишь указать имена поддерживаемых параметров, после чего модуль сам позаботится о разборе командной строки и установке значений соответствующих переменных для их последующего использования:
Вы можете проверить работоспособность описанного механизма с помощью следующих команд:
python echo.py --stderr "hello world" | wc python echo.py "hello world" | wc
Модуль argparser заботится даже о создании текста подсказки (на основе описаний приложения и параметров, переданных при создании объекта), поэтому вы можете выполнить следующую команду:
Вы можете достаточно гибко настраивать все аспекты работы модуля argparser, используя его замечательную документацию, доступную по адресу: https://docs.python.org/2/howto/argparse.html.