Стандартные файловые дескрипторы линукс

Русские Блоги

Подробные файловые дескрипторы Linux

Заявление об авторском праве: Пожалуйста, укажите источник при перепечатке, спасибо https://blog.csdn.net/xlinsist/article/details/51147212

Overview

Важно понимать, как Linux обрабатывает ввод и вывод. Как только мы поймем принцип, мы сможем правильно и умело использовать сценарий, чтобы вывести содержимое в нужное место. Мы также можем лучше понять перенаправление ввода и перенаправление вывода.

Стандартные файловые дескрипторы Linux

Дескриптор файла сокращение описание
0 STDIN Стандартный ввод
1 STDOUT Стандартный вывод
2 STDERR Стандартный вывод ошибок

Системы Linux рассматривают все устройства как файлы, а Linux использует файловые дескрипторы для идентификации каждого файлового объекта. Фактически, мы можем представить, что дисплей и клавиатура нашего компьютера рассматриваются как файлы в системе Linux, и все они имеют соответствующие файловые дескрипторы, соответствующие им.

На самом деле наше взаимодействие с компьютером заключается в том, что я могу вводить некоторые инструкции, и это дает мне некоторую информацию. Тогда мы можем поставитьФайловый дескриптор 0 понимается как мой ввод при взаимодействии с компьютером, и этот ввод по умолчанию направлен на клавиатуру; Файловый дескриптор 1 понимается как вывод, когда я взаимодействую с компьютером, и этот вывод по умолчанию направляется на дисплей; Файловый дескриптор 2 понимается как выходная информация об ошибке компьютера, когда я взаимодействую с компьютером, и этот вывод по умолчанию соответствует местоположению, указанному файловым дескриптором 1;

Как я уже говорил выше, так как они по умолчанию, я могу их изменить. Следующая команда изменяет расположение стандартного вывода в файл xlinsist:

На этот раз, если я войду ls -al или ps Команда, наш терминал не будет ничего отображать. Теперь мы можем открыть новый терминал и посмотреть, есть ли в файле xlinsist содержимое, показанное в двух приведенных выше командах.Примечание: вы должны открыть новый терминал.

Таким же образом мы также можем изменить положение стандартного ввода. Во-первых, давайте посмотрим на то, что не изменилось:

То есть мы читаем xlinsist в пользовательскую переменную с клавиатуры. Это чтение требует моего ввода. Теперь я хочу изменить стандартную позицию стандартного ввода:

Как вы можете видеть из приведенной выше команды чтения, меня ни о чем не просили.

Разница между стандартным выводом ошибки и стандартным выводом заключается в том, что он выводится в случае ошибки команды. Это не так уж отличается, мы также можем изменить его вывод в любое место, которое мы хотим. Просто нам нужно изменить стандартный вывод с 1 на 2. Команда выглядит следующим образом:

  • Конечно, в дополнение к 0, 1, 2 мы можем выделить наши собственные файловые дескрипторы. Посмотрите на следующий пример:

Вышеприведенная команда очень интересна: сначала я указываю дескриптор файла 6 на тестовый файл. Поскольку в отличие от дескриптора 1, все выходные данные будут естественно искать его и видеть, направлен ли он на экран или в файл. Поэтому, когда мы хотим найти дескриптор 6, нам нужно использовать & для ссылки на него. Фактически, мы можем рассматривать дескриптор файла как ссылку на файл, который может указывать на любой файл (включая отображение). Процесс наведения — это процесс изменения местоположения по умолчанию. И используйте амперсанд, чтобы найти целевой файл, на который он указывает, и записать в него данные.

Если вы действительно понимаете вышеприведенные принципы, мы можем воспроизвести любое перенаправление ввода, перенаправление вывода, и это небольшой случай. Теперь давайте возьмем более сложный пример, чтобы помочь вам организовать ваши идеи. Сценарий выглядит следующим образом:

Давайте разберемся с вышеприведенной командой шаг за шагом: во-первых, дескриптор файла 1 по умолчанию указывает на монитор. Используйте &, чтобы найти целевой файл, на который указывает дескриптор файла 1, который является монитором. Таким образом, дескриптор файла 3 также указывает на отображение. Затем мы изменили файл, на который указывает дескриптор файла 1, в тестовый файл. Затем выходные данные двух команд echo будут естественно искать дескриптор файла 1, а затем он обнаруживает, что дескриптор файла 1 указывает на тестовый файл, поэтому он записывает выходные данные в тестовый файл. Наконец, мы используем &, чтобы найти целевой файл, на который указывает дескриптор файла 3, который является дисплеем, и затем мы модифицируем файл, на который указывает дескриптор файла 1, для отображения. Следовательно, последняя команда echo естественным образом найдет дескриптор файла 1 и выведет его на дисплей.

Весь процесс таков, пока вы понимаете их принципы, вы не будете чувствовать растерянность, независимо от того, как вы будете обрабатывать перенаправления в сценариях в будущем. Ниже я представлю некоторые команды оболочки, относящиеся к файловым дескрипторам, которые могут сделать вас еще более мощным.

Некоторые команды оболочки, связанные с файловыми дескрипторами

На следующем рисунке показано значение вышеперечисленных пунктов

Теперь я модифицирую стандартный вывод ошибок:

Файл / dev / null, это очень специальный файл, все, что вы пишете, будет очищено. Вы можете записать данные, чтобы попробовать эффект.

  1. Мы можем перенаправить стандартный вывод ошибок в / dev / null, тем самым отбрасывая сообщения об ошибках, которые мы не хотим сохранять
  2. Мы можем быстро удалить данные из существующих файлов без предварительного удаления файла при его создании. Команда выглядит следующим образом:

Linux использует каталог / tmp для хранения файлов, которые не нужно хранить постоянно. Большинство систем Linux автоматически удаляют все файлы в каталоге / tmp при запуске.

Доступны следующие команды:

tee команда-чтение из стандартного ввода, запись в стандартный вывод и файлы.

Разница между каналами и перенаправлениями

Канал — это выход одной программы как вход другой программы.
Перенаправление — это перенаправление вывода в файл или стандартный поток.

Источник

11.2.1. Файловые дескрипторы

11.2.1. Файловые дескрипторы

Когда процесс получает доступ к файлу (что обычно называют открытием файла), то ядро возвращает ему файловый дескриптор, который затем используется процессом для всех операций с файлом. Файловые дескрипторы — это маленькие положительные целые числа, которые служат индексами массива открытых файлов, создаваемого ядром для каждого процесса.

Первые три файловых дескриптора для процессов (0, 1 и 2) имеют стандартное назначение. Первый, 0, известен как стандартный ввод (stdin) и является местом, откуда программы должны получать свой интерактивный ввод. Файловый дескриптор 1 называется стандартным выводом (stdout), и большая часть вывода программ должна быть направлена в него. Сообщения об ошибках должны направляться в стандартный поток ошибок (stderr), который имеет файловый дескриптор 2. Стандартная библиотека С следует этим правилам, поэтому gets() и printf() используют stdin и stdout соответственно, и это соглашение дает возможность командным оболочкам правильно перенаправлять ввод и вывод процессов.

Заголовочный файл представляет макросы STDIN_FILENO, STDOUT_FILENO и STDERR_FILENO, которые вычисляются как файловые дескрипторы stdin, stdout и stderr соответственно. Использование этих символических имен делает код более читабельным.

Многие из файловых операций, которые манипулируют файловыми узлами inode, доступны в двух формах. Первая форма принимает в качестве аргумента имя файла. Ядро использует этот аргумент для поиска inode файла и выполняет соответствующую операцию над ним (обычно это включает следование символическим ссылкам). Вторая форма принимает файловый дескриптор в качестве аргумента и выполняет операцию над inode, на который он ссылается. Эти два набора системных вызовов используют похожие имена, но системные вызовы, работающие с файловыми дескрипторами, имеют префикс f. Например, системный вызов chmod() изменяет права доступа для файла, ссылка на который осуществляется по имени; fchmod() устанавливает права доступа к файлу, ссылаясь на него по указанному файловому дескриптору.

Чтобы меньше тратить слов, мы представим обе версии системных вызовов, если они существуют, а обсуждать будет только первую из их (та, которая использует имена файлов).

Читайте также

4.4.2.1. Отображение переменных FILE* на дескрипторы файлов

4.4.2.1. Отображение переменных FILE* на дескрипторы файлов Стандартные библиотечные функции ввода/вывода и переменные FILE* из , такие, как stdin, stdout и stderr, построены поверх основанных на дескрипторах файлов системных вызовах.Иногда полезно получить непосредственный

Наследуемые дескрипторы

Наследуемые дескрипторы Часто бывает так, что дочернему процессу требуется доступ к объекту, к которому можно обратиться через дескриптор, определенный в родительском процессе, и если этот дескриптор — наследуемый, то дочерний процесс может получить копию открытого

Абсолютные и самоопределяющиеся относительные дескрипторы безопасности

Абсолютные и самоопределяющиеся относительные дескрипторы безопасности Программа 15.5, позволяющая изменять ACL, удобна тем, что просто заменяет один дескриптор безопасности (SD) другим. В то же время, при замене существующих SD следует проявлять осторожность, поскольку они

11.2.1. Файловые дескрипторы

11.2.1. Файловые дескрипторы Когда процесс получает доступ к файлу (что обычно называют открытием файла), то ядро возвращает ему файловый дескриптор, который затем используется процессом для всех операций с файлом. Файловые дескрипторы — это маленькие положительные целые

Индексные дескрипторы

Индексные дескрипторы Индексный дескриптор, или inode, содержит информацию о файле, необходимую для обработки данных, т.е. метаданные файла. Каждый файл ассоциирован с одним inode, хотя может иметь несколько имен в файловой системе, каждое из которых указывает на один и тот же

Виртуальные индексные дескрипторы

Виртуальные индексные дескрипторы Дисковый файл обычно имеет связанную с структуру данных, называемую метаданными или inode, где хранятся основные характеристики данного файла и с помощью которой обеспечивается доступ к его данным. Одним из исключений из этого правила

2.5 Индексные дескрипторы

2.5 Индексные дескрипторы Файл имеет несколько атрибутов: имя, содержимое и служебную информацию (права доступа и даты модификации). Служебная информация размещается в индексном дескрипторе вместе с важной системной информацией, такой, как размер файла, место хранения

16.3. Индексные дескрипторы файлов

16.3. Индексные дескрипторы файлов Каждому файлу на диске соответствует один и только один индексный дескриптор файла, который идентифицируется своим порядковым номером — индексом файла. Это означает, что число файлов, которые могут быть созданы в файловой системе,

Файловые таблицы

Файловые таблицы Эта группа таблиц содержит информацию обо всех файлах, составляющих программный продукт. Большая часть этих файлов перечислена в таблице File. Таблица Directory не входит в эту группу, но, тем не менее, очень тесно связана с ней, так как отражает структуру

1.1.3. Дескрипторы вместо классов

1.1.3. Дескрипторы вместо классов Программируя в Delphi, мы быстро привыкаем к тому, что каждый объект реализуется экземпляром соответствующего класса. Например, кнопка реализуется экземпляром класса TButton, контекст устройства — классом TCanvas. Но когда создавались первые

Файловые менеджеры

Файловые менеджеры На протяжении всей книги уже не раз упоминалось о двух популярных файловых менеджерах – Konqueror из KDE и Nautilus из GNOME. Пользователям, работающим в консоли, можно предложить Midnight Commander (пакет mc). Две панели сине-белого цвета со строкой меню, расположенной

7.2.5. Дескрипторы файлов процесса

7.2.5. Дескрипторы файлов процесса Элемент fd файловой системы /proc — это подкаталог, в котором содержатся записи обо всех файлах, открытых процессом. Каждая запись представляет собой символическую ссылку на файл или устройство. Через эти ссылки можно осуществлять чтение и

6.5. Файловые структуры

6.5. Файловые структуры Файловая структура может быть одно– или многоуровневой. В одноуровневой структуре на носителе информации имена файлов образуют линейную последовательность, в многоуровневой, или иерархической, – древовидную структуру. Примером такой структуры

6.6. Файловые системы

6.6. Файловые системы 6.6.1. Назначение и функционирование файловой системы В операционных системах файловая система относится к основным понятиям и определяется как общая система, которая устанавливает правила присвоения имен файлам, хранение, организацию и обработку

Источник

Файл дескриптор в Linux с примерами

Однажды, на одном интервью меня спросили, что ты будешь делать, если обнаружишь неработающий сервис из-за того, что на диске закончилось место?

Конечно же я ответил, что посмотрю, чем занято это место и если возможно, то почищу место.
Тогда интервьюер спросил, а что если на разделе нет свободного места, но и файлов, которые бы занимали все место, ты тоже не видишь?

На это я сказал, что всегда можно посмотреть открытые файл дескрипторы, например командой lsof и понять какое приложение заняло все доступное место, а дальше можно действовать по обстоятельствам, в зависимости от того, нужны ли данные.

Интервьюер прервал меня на последнем слове, дополнив свой вопрос: «Предположим, что данные нам не нужны, это просто дебаг лог, но приложение не работает из-за того, что не может записать дебаг»?

«окей», — ответил я, «мы можем выключить дебаг в конфиге приложения и перезапустить его».
Интервьюер возразил: «Нет, приложение мы перезапустить не можем, у нас в памяти все еще хранятся важные данные, а к самому сервису подключены важные клиенты, которых мы не можем заставлять переподключаться заново».

«ну хорошо», сказал я, «если мы не можем перезапускать приложение и данные нам не важны, то мы можем просто очистить этот открытый файл через файл дескриптор, даже если мы его не видим в команде ls на файловой системе».

Интервьюер остался доволен, а я нет.

Тогда я подумал, почему человек, проверяющий мои знания, не копает глубже? А что, если данные все-таки важны? Что если мы не можем перезапускать процесс, и при этом этот процесс пишет на файловую систему в раздел, на котором нет свободного места? Что если мы не можем потерять не только уже записанные данные, но и те данные, что этот процесс пишет или пытается записать?

Тузик

В начале моей карьеры я пытался создать небольшое приложение, в котором нужно было хранить информацию о пользователях. И тогда я думал, а как мне сопоставить пользователя к его данным. Есть, например, у меня Иванов Иван Иваныч, и есть у него какие-то данные, но как их подружить? Я могу указать напрямую, что собака по имени «Тузик» принадлежит этому самому Ивану. Но что, если он сменит имя и вместо Ивана станет, например, Олей? Тогда получится, что наша Оля Ивановна Иванова больше не будет иметь собаки, а наш Тузик все еще будет принадлежать несуществующему Ивану. Решить эту проблему помогла база данных, которая каждому пользователю давала уникальный идентификатор (ID), и мой Тузик привязывался к этому ID, который, по сути, был просто порядковым номером. Таким образом хозяин у тузика был с ID под номером 2, и на какой-то момент времени под этим ID был Иван, а потом под этим же ID стала Оля. Проблема человечества и животноводства была практически решена.

Файл дескриптор

Проблема файла и программы, работающей с этим файлом, примерно такая же как нашей собаки и человека. Предположим я открыл файл под именем ivan.txt и начал в него записывать слово tuzik, но успел записать только первую букву «t» в файл, и этот файл был кем-то переименован, например в olya.txt. Но файл остался тем же самым, и я все еще хочу записать в него своего тузика. Каждый раз при открытии файла системным вызовом open в любом языке программирования я получаю уникальный ID, который указывает мне на файл, этот ID и есть файл дескриптор. И совершенно не важно, что и кто делает с этим файлом дальше, его могут удалить, его могут переименовать, ему могут поменять владельца или забрать права на чтение и запись, я все равно буду иметь к нему доступ, потому что на момент открытия файла у меня были права для его чтения и/или записи и я успел начать с ним работать, а значит должен продолжать это делать.

В Linux библиотека libc открывает для каждого запущенного приложения(процесса) 3 файл дескриптора, с номерами 0,1,2. Больше информации вы можете найти по ссылкам man stdio и man stdout

  • Файл дескриптор 0 называется STDIN и ассоциируется с вводом данных у приложения
  • Файл дескриптор 1 называется STDOUT и используется приложениями для вывода данных, например командами print
  • Файл дескриптор 2 называется STDERR и используется приложениями для вывода данных, сообщающих об ошибке

Если в вашей программе вы откроете какой-либо файл на чтение или запись, то скорее всего вы получите первый свободный ID и это будет номер 3.

Список файл дескрипторов можно посмотреть у любого процесса, если вы знаете его PID.

Например, откроем консоль с bash и посмотрим PID нашего процесса

Во второй консоли запустим

Файл дескриптор с номером 255 можете смело игнорировать в рамках данной статьи, он был открыт для своих нужд уже самим bash, а не прилинкованной библиотекой.

Сейчас все 3 файл дескриптора связаны с устройством псевдотерминала /dev/pts, но мы все равно можем ими манипулировать, например запустим во второй консоли

И в первой консоли мы увидим

Redirect и Pipe

Вы можете легко переопределить эти 3 файл дескриптора в любом процессе, в том числе и в bash, например через трубу(pipe), соединяющую два процесса, смотрим

Вы можете сами запустить эту команду с strace -f и увидеть, что происходит внутри, но я вкратце расскажу.

Наш родительский процесс bash с PID 15771 парсит нашу команду и понимает сколько именно команд мы хотим запустить, в нашем случае их две: cat и sleep. Bash знает что ему нужно создать два дочерних процесса, и объединить их одной трубой. Итого bash потребуется 2 дочерних процесса и один pipe.

Перед созданием дочерних процессов bash запускает системный вызов pipe и получает новые файл дескрипторы на временный буфер pipe, но этот буфер никак пока не связывает наши два дочерних процесса.

Для родительского процесса это выглядит так будто pipe уже есть, а дочерних процессов еще нет:

Затем с помощью системного вызова clone bash создает два дочерних процесса, и наши три процесса будут выглядеть так:

Не забываем, что clone клонирует процесс вместе со всеми файл дескрипторами, поэтому в родительском процессе и в дочерних они будут одинаковые. Задача родительского процесса с PID 15771 следить за дочерними процессами, поэтому он просто ждет ответ от дочерних.

Следовательно pipe ему не нужен, и он закрывает файл дескрипторы с номерами 3 и 4.

В первом дочернем процессе bash с PID 9004, системным вызовом dup2, меняет наш STDOUT файл дескриптор с номером 1 на файл дескриптор указывающий на pipe, в нашем случае это номер 3. Таким образом все, что первый дочерний процесс с PID 9004 будет писать в STDOUT, будет автоматически попадать в буфер pipe.

Во втором дочернем процессе с PID 9005 bash меняет с помощью dup2 файл дескриптор STDIN с номером 0. Теперь все, что будет читать наш второй bash с PID 9005, будет читать из pipe.

После этого в дочерних процессах так же закрываются файл дескрипторы с номерами 3 и 4, так как они более не используются.

Файл дескриптор 255 я намеренно игнорирую, он использует для внутренних нужд самого bash и в дочерних процессах будет также закрыт.

Далее в первом дочернем процессе с PID 9004 bash запускает с помощью системного вызова exec исполняемый файл, который мы указали в командной строке, в нашем случае это /usr/bin/cat.

Во втором дочернем процессе с PID 9005 bash запускает второй исполняемый файл, который мы указали, в нашем случае это /usr/bin/sleep.

Системный вызов exec не закрывает файл дескрипторы, если они не были открыты с флагом O_CLOEXEC во время выполнения вызова open. В нашем случае после запуска исполняемых файлов все текущие файл дескрипторы сохранятся.

Проверяем в консоли:

Как видите уникальный номер нашего pipe у нас в обоих процессах совпадает. Таким образом у нас есть связь между двумя разными процессами с одним родителем.

Для тех, кто не знаком с системными вызовами, которые использует bash, крайне рекомендую запустить команды через strace и посмотреть, что происходит внутри, например, так:

Вернемся к нашей проблеме с нехваткой места на диске и попыткой сохранить данные без перезапуска процесса. Напишем небольшую программу, которая будет записывать на диск примерно 1 мегабайт в секунду. При этом если по какой-либо причине мы не смогли записать данные на диск, мы будем просто игнорировать это и пытаться записать данные вновь через секунду. В примере я использую Python, вы можете использовать любой другой язык программирования.

Запустим программу и посмотрим на файл дескрипторы

Как видим у нас есть наши 3 стандартные файл дескрипторы и еще один, который мы открыли. Проверим размер файла:

данные пишутся, пробуем поменять права на файл:

Видим, что данные все еще пишутся, хотя наш пользователь не имеет права писать в файл. Попробуем его удалить:

Куда пишутся данные? И пишутся ли вообще? Проверяем:

Да, наш файл дескриптор все еще существует, и мы можем работать с этим файл дескриптором как с нашим старым файлом, мы можем его читать, очищать и копировать.

Смотрим на размер файла:

Размер файла 19923457. Пробуем очистить файл:

Как видим размер файла только увеличивается и наш транкейт не сработал. Обратимся к документации по системному вызову open. Если при открытии файла мы используем флаг O_APPEND, то при каждой записи операционная система проверяет размер файла и пишет данные в самый конец файла, причем делает это атомарно. Это позволяет нескольким тредам или процессам писать в один и тот же файл. Но в нашем коде мы не используем этот флаг. Мы можем увидеть другой размер файла в lsof после транкейт только если откроем файл для дозаписи, а значит в нашем коде вместо

мы должны поставить

Проверяем с «w» флагом

Программируем уже запущенный процесс

Часто программисты при создании и тестировании программы используют дебагеры (например GDB) или различные уровни логирования в приложении. Linux предоставляет возможность фактически писать и менять уже запущенную программу, например менять значения переменных, устанавливать breakpoint и тд и тп.

Возвращаясь к оригинальному вопросу с нехваткой места на диске для записи файла, попробуем сэмулировать проблему.

Создадим файл для нашего раздела, который мы подмонтируем как отдельный диск:

Создадим файловую систему:

Подмонтируем файловую систему:

Создаем директорию с нашим владельцем:

Откроем файл только на запись в нашей программе:

Ждем несколько секунд

Итак, мы получили проблему, описанную в начале этой статьи. Свободного места 0, занятого 100%.

Мы помним, что по условиям задачи мы пытаемся записать очень важные данные, которые нельзя потерять. И при этом нам нужно починить сервис без перезапуска процесса.

Допустим, у нас все же есть место на диске, но в другом разделе, например в /home.

Попробуем «перепрограммировать на лету» наш код.

Смотрим PID нашего процесса, который съел все место на диске:

Подключаемся к процессу через gdb

Смотрим открытые файл дескрипторы:

Смотрим информацию о файл дескрипторе с номером 3, который нас интересует

Помня о том, какой системный вызов делает Python (смотрите выше, где мы запускали strace и находили вызов open), обрабатывая наш код для открытия файла, мы делаем то же самое самостоятельно от имени нашего процесса, но биты O_WRONLY|O_CREAT|O_TRUNC нам нужно заменить на числовое значение. Для этого открываем исходники ядра, например тут и смотрим какие флаги за что отвечают

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Объединяем все значения в одно, получаем 00001101

Запускаем наш вызов из gdb

Итак мы получили новый файл дескриптор с номером 4 и новый открытый файл на другом разделе, проверяем:

Мы помним пример с pipe — как bash меняет файл дескрипторы, и уже выучили системный вызов dup2.

Пробуем подменить один файл дескриптор другим

Закрываем файл дескриптор 4, так как нам он не нужен:

И выходим из gdb

Проверяем новый файл:

Как видим, данные пишутся в новый файл, проверяем старый:

Данные не потеряны, приложение работает, логи пишутся в новое место.

Немного усложним задачу

Представим, что данные нам важны, но места на диске у нас нет ни в одном из разделов и подключить диск мы не можем.

Что мы можем сделать, так это перенаправить куда-то наши данные, например в pipe, а данные из pipe в свою очередь перенаправить в сеть через какую-либо программу, например netcat.
Мы можем создать именованный pipe командой mkfifo. Она создаст псевдофайл на файловой системе, даже если на ней нет свободного места.

Перезапускаем приложение, и проверяем:

Места на диске нет, но мы успешно создаем там именованный pipe:

Теперь нам надо как-то завернуть все данные, что попадают в этот pipe на другой сервер через сеть, для этого подойдет все тот же netcat.

На сервере remote-server.example.com запускаем

На нашем проблемном сервере запускаем в отдельном терминале

Теперь все данные, которые попадут в pipe автоматически попадут на stdin в netcat, который их отправит в сеть на порт 7777.

Все что нам осталось сделать это начать писать наши данные в этот именованный pipe.

У нас уже есть запущенное приложение:

Из всех флагов нам нужен только O_WRONLY так как файл уже существует и очищать нам его не нужно

Проверяем удаленный сервер remote-server.example.com

Данные идут, проверяем проблемный сервер

Данные сохранились, проблема решена.

Пользуясь случаем, передаю привет коллегам из компании Degiro.
Слушайте подкасты Радио-Т.

В качестве домашнего задания предлагаю подумать, что будет в файл дескрипторах процесса cat и sleep если запустить такую команду:

Источник

Читайте также:  Как скрыть заголовок окна windows
Оцените статью