Работа с потоками STDIN, STDOUT, STDERR
Виды потоков
В системах Linux и Unix существуют стандартные входной (STDIN) и выходные (STDOUT, STDERR) потоки (каналы). Далее рассмотрим подробнее каждый из них.
- STDIN (Номер файлового дескриптора — 0)
Стандартный входной поток. Канал принимающий данные для обработки и последующей передачи на канал STDOUT и/или STDERR. - STDOUT (Номер файлового дескриптора — 1)
Стандартный выходной поток. Представляет собой канал записи результатов выполнения каких-либо процессов. - STDERR (Номер файлового дескриптора — 2)
Стандартный выходной поток ошибок. В данный канал попадают сообщения об ошибках.
В рамках терминала канал STDIN считывает входные данные, а каналы STDOUT и STDERR выводят выходные данные на экран.
Управление потоками
Для перенаправления каналов в терминале, применяют определенные символы. Рассмотрим каждый из них на примере команды поиска системных файлов, которые содержат слово — core. Все найденные файлы будут формироваться в поток STDOUT. Те найденные файлы, к которым у обычного пользователя нет доступа будут попадать в STDERR.
find / -name core > /tmp/testfile
В файл /tmp/testfile попадет список путей ко всем найденным файлам, а список ошибок отобразится в терминале.
Запись STDOUT в файл
Символ > — затирает все его содержимое и вставляет значение из потока, поэтому будьте осторожны при правке системных файлов используя данный символ. Если Вам нужно добавить данные в конец файла — используйте два последовательных символа — >> .
- >> — вывод STDOUT в конец файла.
find / -name core >> /tmp/testfile
В конец файла /tmp/testfile попадет список путей ко всем найденным файлам, а список ошибок отобразится в терминале.
Запись STDOUT в конец файла
- >& — вывод STDOUT и STDERR в файл
find / -name core >& /tmp/testfile
С помощью составного символа — >& мы объединяем стандартный выходной поток с выходным потоком ошибок. В файл /tmp/testfile попадет список путей ко всем найденным файлам и список ошибок.
Объединение выходных потоков
- 2> — вывод STDERR в файл
find / -name core 2> /tmp/testfile
В файл /tmp/testfile попадет список ошибок, а список найденных файлов, будет выведен в терминале.
Вывод STDERR
Вывод потоков можно комбинировать и распределять по разным местам. Например, выведем список найденных файлов в /tmp/testfile , а список ошибок отбросим, перенаправив их в /dev/null .
find / -name core > /tmp/testfile 2> /dev/null
Перенаправление потоков
Для того чтобы направить выходной поток одной команды на входной поток другой, применяют символ — | (pipe).
Для примера, выведем в консоли отдельные процессы системы с именем — chrome .
Здесь результат выполнения команды ps передается в роли входных данных для команды grep , в которых она ищет совпадения с именем chrome .
Заключение
В этой небольшой статье мы рассмотрели все стандартные входные и выходные и потоки, которые, в свою очередь, очень часто применяются системными администраторами на практике.
Понравилась статья? Расскажите о ней друзьям!
Потоки данных
Статья посвящена работой с потоками данных в bash. Я постарался написать ее наиболее доступным и простым языком, чтобы было понятно даже новичкам в Linux.
В одной из моих статей мы рассматривали запись звука в файл с помощью команды:
Эта команда читает файл (устройство) /dev/audio с помощью команды cat и перенаправляет информацию из него в файл /tmp/my.sound (с помощью оператора >).
У каждой программы существует 3 системных потока: stdout, stderr, stdin.
stdout
Стандартный поток вывода данных для программ. Например, когда мы пишем команду ls, то список папок и файлов она выводит именно в этот поток, который отображается у нас в консоли:
stderr
Поток вывода ошибок. Если программа не смогла сделать все как надо — она пишет именно в этот поток. Например, когда rm пытается удалить несуществующий файл:
$ rm example.txt
rm: example.txt: No such file or directory
stdin
Поток ввода данных. А вот это довольно интересный и удобный поток. Например, его использует вэб-сервер, когда просит интерпретаторы выполнить скрипты через CGI. Мы тоже можем попробовать:
В этом примере мы встретили оператор перенаправления потока вывода. Мы остановимся на нем позже.
Перенаправление потоков
Для начала рассмотрим перенаправление потоков в файлы, устройства и другие потоки.
В этом примере мы направили stdout команды ls в файл 1.txt. Читаем его:
Да, все успешно записалось.
Теперь попробуем направить stderr команды rm:
Здесь мы использовали номер потока stderr (2). По умолчанию оператор > перенаправляет поток stdout, который имеет номер 1. Чтобы направить другой поток, надо перед оператором > поставить его номер.
Мы можем направлять одни потоки в направлении других:
В этом примере мы направили поток stdout в файл 1.txt, а затем направили stderr туда же, куда направлен stdout с помощью оператора & перед номером потока.
Теперь давайте поиграем с потоком stdin. Например, я хочу найти все папки «.svn» в некотором проекте и удалить:
Команда find с параметром. выводит в stdout все вложенные папки и файлы, которые находит в данной папке и во всех вложенных.
Теперь нам надо выбрать только папки с именем «.svn»:
Оператор | перенаправляет stdout одного приложения в stdin следующего. То есть все строки найденные с помощью find пошли в команду grep, которая выбирает строки по определенным условиям и выводит их. Здесь условие — это регулярное выражение, которое говорит о том, что строка должна заканчиваться на «/.svn».
Нужные папки мы выбрали, осталось их удалить.
И снова новый оператор: `. Он забирает stdout из команды, которую он окружает и вставляет в данное место как строку.
Получается, что мы запросили все файлы, выбрали из них папки с именем «.svn» и отдали результат как аргументы команде rm. В этом случае у нас будут проблемы если имена файлов и папок содержат пробелы. Исправляем ситуацию:
Теперь мы отдаем нужные файлы команде xargs, которая вызывает rm -Rf и в качестве параметров использует свой stdin построчно. Задача решена.
Каждый может помочь развитию данной серии статей, поделиться своим опытом. Добро пожаловать: http://www.linuxman.ru. Все изменения в Вики я буду со временем переносить и в Хабр.
Ой, у вас баннер убежал!
Редакторский дайджест
Присылаем лучшие статьи раз в месяц
Скоро на этот адрес придет письмо. Подтвердите подписку, если всё в силе.
Похожие публикации
Bash-скрипты, часть 10: практические примеры
Bash-скрипты, часть 9: регулярные выражения
Bash-скрипты, часть 8: язык обработки данных awk
Курсы
AdBlock похитил этот баннер, но баннеры не зубы — отрастут
Комментарии 43
А по-моему, вполне понятно все написано в книжках. Разобраться совсем недолго… Примеры книг могу привести, если надо.
А так за статью(не считаем ошибки) респект. Немногие сейчас отваживаются их писать)
Команда find с параметром * выводит в stdout все вложенные папки и файлы, которые находит в данной папке и во всех вложенных.
Неверно. Это сделает команда find .
Не забываем, что globbing (замена wildcards) происходит в шелле. И * по умолчанию не расширяется на dot entries в текущем каталоге.
Нельзя не вспомнить про будильник настоящего юниксоида:
Настоящий юниксоид напишет так:
sleep 8h && cat /dev/urandom > /dev/dsp
Будет (собственно я не говорил, что Ваш код в корне не верен), но настоящий юниксоид напишет:
sleep 8h && cat /dev/urandom > /dev/dsp
Так как код:
sleep 8h; cat /dev/urandom > /dev/dsp
при отсутствии /bin/sleep сразу начнет звенеть…
Ваш пример довольно безобидный, но когда начинают писать
cd /dir1/dir2/dir3; rm *
становится страшно…
Спасибо за совет, но, прочитав мануал, я понял эту команду как:
tee [-ai] [file . ]
The tee utility copies standard input to standard output, making a copy
in zero or more files. The output is unbuffered.
cat ./file.txt | grep ‘key’ | tee ./file2.txt | grep ‘subkey’ > ./file3.txt
Как ./file2.txt направить не в физический файл, а в пайп, где я еще хочу сделать sort?
У тебя есть такой пример:
Извини, у меня нет цензурных слов. За такое я отрываю разработчикам руки. Такой код в скриптах — это бомба замедленного действия, она срабатывает редко, но неожиданно и разрушительно. О grep-е после find-а и xargs rm вместо -delete уже говорили, но это можно попытаться оправдать тем, что примеры учебные и искуственные. А вот опасность этого примера оправдать нельзя!
Автор, создай у себя, для эксперимента директорию где-нибудь в темпе:
В «документы» положи очень важные и ценные тебе документы. В «документы на удаление» — ерунду. А теперь выполни эту команду и сожалей, что директория «документы» исчезла. Не .svn в этой директории, а «документы» целиком!
Писать так — опасно, учить других писать так — во сто крат опасней! Читать маны перед написанием учебной статьи — напротив, не только неопасно, но и крайне полезно.
Угу, этот пример тоже хорош:
Такой же убийственный
Спасибо за дельный комментарий.
Но если все таки забыть про учебную составляющую и обратить внимание на саму задачу удаления каталогов .svn, то у меня возник вот какой вопрос: как же правильно написать команду?
У меня не получилось удалить каталоги .svn с использованием -delete, так как я создал там вложенные файлы (это часто бывает в реальной жизни в таком каталоге).
find. -type d -and -iname ‘.svn’ -delete
find: cannot delete `./документы/.svn’: Каталог не пуст
В конце концов появился вот такой вариант:
find. -type d -and -iname ‘.svn’ -execdir rm -Rf .svn \; 2>/dev/null
Есть ли более оптимальный?
Обычно пользуюсь find -print0 | xargs -0 . А только средствами find… попробуйте
find . -depth \( -path ‘*/.svn/*’ -or -iname ‘.svn’ \) -delete
Хотя поздновато уже, не ручаюсь.
find ищет по каким-то условиям и выполняет определённое действие с результатом поиска. Дело в том, что по умолчанию find делает -print, то есть выводит результат на стандартный вывод, разделяя имена переводом строки.
xargs читает со стандартного ввода записи, разделяя, их, среди прочего, и пробелами. Таким образом, если find найдёт файл с именем «раз два», то xargs запустит указанную команду с аргументами «раз» и «два». Нам нужно, чтобы find разделял записи разделителем, который не может присутствовать в именах файлов. Среди ext2/3 таких символов два — это нулевой символ (не имеет печатаемого обозначения) и символ прямого слэша. Прямой слэш, кажется, в некоторых файловых системах может являться частью имени файла, потому нам остаётся только нулевой символ.
Как сказано в мануале в первых строках по find: «you should probably consider using ‘-print0’ instead».
Действие -print0 заставляет find вывести на стандартный вывод результаты, разделяемые нулевым символом, а опция -0 у xargs заставляет в качестве разделителя записей принимать только нулевой символ.
В моём мане (это не сарказм, маны на разных системах бывают очень разные) есть такой пример для этого дела:
Что касается возможности запуска без xargs — для скриптов я бы посоветовал такую конструкцию:
Для применения вручную, после запуска без -delete и изучения списка:
При этом, файлы и директории типа .svnlalala тоже будут уничтожены, если присутствуют.