Разбираемся, как устроены STDIN, STDOUT, STDERR и файловые дескрипторы на примере Linux и Python. Что происходит, когда вы вызываете print()? Ответ — на трёх уровнях абстракции: ядро, C и Python.
029 открытий143 показов
Дисклеймер
У меня нет цели раскрыть данную тему целиком и очень точно во всех технических нюансах, так как я не являюсь системным програмистом или одареным гением.
Цель в том, чтобы подтолкнуть разработчиков к мысли, что все не так просто, и возможно, после этой статьи они смогут самостоятельно ознакомиться со всеми техническими тонкостями до той глубины, которая им нужна.
При программировании на C или Python мы часто сталкиваемся с выводом и вводом данных в программу. Это происходит даже тогда, когда программисты начинают изучать новый язык и пишут свою первую программу Hello World!
.
Вот только использование функционала сопряжено с тем, что разработчики ЯП предоставляют нам удобный уровень абстракции, чтобы не задумываться, как это работает на самом деле. И поэтому для многих не всегда понятна внутренняя “кухня” данных процессов. Дальнейшее рассмотрение функционала будет на примере ОС “Ubuntu”.
Погрузимся в детали работы
Давайте попробуем поработать с дескрипторами в Shell, непосредственно в самом Linux.
- С помощью команды cat можно читать данные из файла(
cat file.txt
) либо из клавиатуры(cat
). - С помощью команды echo можно отправлять данные в консоль для отображения(echo “Hello”), либо отправить данные в файл(
echo "Hello" > file.txt
). - Если вдруг вы пытаетесь найти несуществующий файл, возникнет ошибка в консоли(
ls nofile.txt
). Еще ее можно перенаправить в файл(ls nofile.txt 2> error.txt
). Обратите внимание, что здесь конкретно мы указываем, из какого файлового дескриптора брать информацию.
Когда вы запускаете какой-либо Python процесс, создаются сразу три файловых дескриптора — для ввода и вывода данных.
Анонсирована Аврора SDK для ARM-процессоров Apple Silicontproger.ru
Ниже прикреплен Python скрипт. Прежде чем запустить, давайте его разберем:
- Для получения данных извне мы можем использовать функцию input. О чем нам говорит строка документация функции: “Read a string from standard input.”
- Функция print по умолчанию отправляет данные в stdout, так как аргумент функции file по умолчанию — sys.stdout.
- Изменить поток ввода на stderr возможно, явно передав объект из модуля sys, в аргумент file функции print. Сообщение об ошибке будет красным, прям так же, как и при ошибке, потому что они автоматически отправляются в поток STDERR уже на уровне интерпретатора.
# main.py import sys input_command = input("Enter your commandn") # STDIN print(f"Your command: {input_command}") # STDOUT print("Your command raising an exception: Some exception.", file=sys.stderr) # STDERR
Запуск скрипта
Теперь давайте попробуем запустить данный скрипт и найти наши файловые дескрипторы.
Сначала необходимо найти PID данного Python-процесса. Для этого открываем терминал и пишем команду ps aux | grep main.py
.
Нас интересует вторая колонка, где указан PID. Для того чтобы получить файловые дескрипторы, необходимо ввести команду lsof -p YOUR_PID | grep FIFO
.
Почему именно файловые дескрипторы используются для ввода и вывода данных?
Идеология Linux “Everything is a file“ говорит, что все есть файл. А для того чтобы работать унифицировано со всеми компонентами системы, были и введены дескрипторы.
Соответственно, это упрощает разработку программ, делает код компактным, переиспользуемым и предсказуемым.
Что на самом деле значит STDIN, STDOUT, STDERR и файловые дескрипторы?
Когда начинаешь размышлять про это все, можно и запутаться. Что это числа, потоки, файлы? Сначала разберемся с понятиями.
File Descriptor — Целое число, которое служит идентификатором для открытого файла(т.к. почти всё в Unix-подобных системах это файлы).
STDIN — Стандартный поток ввода, принадлежащий файловому дескриптору 0. Получает данные от клавиатуры или из файла/потока.
STDOUT — Стандартный поток вывода, принадлежащий файловому дескриптору 1. Отправляет данные на экран или в файл.
STDERR — Стандартный поток ошибок, принадлежащий файловому дескриптору 2. Отправляет данные на экран или в файл.
Рассмотрим на разных уровнях абстракции.
Уровень ядра / Низкий уровень абстракции
В операционных системах Unix-подобного типа всё есть файл, включая ввод и вывод, терминал, сетевые сокеты и тп.
Болевые точки Django в современной разработкеtproger.ru
Открывая файл, делая GET запрос, получая данные из БД, разработчики создают целочисленные идентификаторы т.е. файловые дескрипторы.
То есть устройство, сокет, канал, консоль, и т.п. — это файлы, а значит они должны иметь интерфейс получения/вывода данных. Это своего рода абстракция.
Вот пример, как можно перенаправлять данные из одного дескриптора в другой: echo "An error occurred: Some exception." 1>&2
Уровень C / Средний уровень абстракции
Удобным способом взаимодействия с вводом/выводом выступают потоки на уровне С. На этом уровне и написан Python интерпретатор.
Заметьте, тут уже нет и слов про дескрипторы, так как это абстракция над ними. Пример в C: fprintf(stderr, "An error occurred: Some exceptionn");
Уровень Python / Высокий уровень абстракции
В Python ввод/вывод представлены как TextIO объекты. Пример в Python:
import sys print("An error occurred: Some exception.", file=sys.stderr)
Резюмируем: На уровне ОС STDIN, STDOUT, STDERR — это файлы, идентифицируются по дескрипторам. На уровне С STDIN, STDOUT, STDERR — это потоки. На уровне Python STDIN, STDOUT, STDERR — это TextIO объекты.
Понимание STDIN, STDOUT, STDERR и файловых дескрипторов критично для системного разработчика, который работает с консольными утилитами либо скриптами на уровне ОС. Поскольку на уровне системы нет синтаксического «сахара», который есть в ЯП. А прикладным программистам это необходимо знать для общей технической грамотности. Также это будет полезно знать, чтобы правильно настроить логирование приложения.
Если вам интересны такие темы — у меня есть канал, где я пишу о backend разработке.