
Статья посвящена работой с потоками данных в
bash. Я постарался написать ее наиболее доступным и простым языком, чтобы было понятно даже новичкам в Linux.
В
одной из моих статей мы рассматривали запись звука в файл с помощью команды:
cat /dev/audio > /tmp/my.sound
Эта команда читает файл (устройство) /dev/audio с помощью команды cat и перенаправляет информацию из него в файл /tmp/my.sound (с помощью оператора >).
У каждой программы существует 3 системных потока:
stdout,
stderr,
stdin.
stdout
Стандартный поток вывода данных для программ. Например, когда мы пишем команду ls, то список папок и файлов она выводит именно в этот поток, который отображается у нас в консоли:
$ ls
bin incoming pub usr
stderr
Поток вывода ошибок. Если программа не смогла сделать все как надо — она пишет именно в этот поток. Например, когда rm пытается удалить несуществующий файл:
$ rm example.txt
rm: example.txt: No such file or directory
stdin
Поток ввода данных. А вот это довольно интересный и удобный поток. Например, его использует вэб-сервер, когда просит интерпретаторы выполнить скрипты через
CGI. Мы тоже можем попробовать:
$ echo '<?php echo «Hello world»; ?>' | php
Hello world
В этом примере мы встретили оператор перенаправления потока вывода. Мы остановимся на нем позже.
Подробнее:
http://ru.wikipedia.org/wiki/Стандартные_потоки
Перенаправление потоков
Для начала рассмотрим перенаправление потоков в файлы, устройства и другие потоки.
$ ls >1.txt
В этом примере мы направили stdout команды ls в файл 1.txt. Читаем его:
$ cat 1.txt
bin incoming pub usr
Да, все успешно записалось.
Теперь попробуем направить stderr команды rm:
$ rm example.txt 2>1.txt
Здесь мы использовали номер потока stderr (2). По умолчанию оператор > перенаправляет поток stdout, который имеет номер 1. Чтобы направить другой поток, надо перед оператором > поставить его номер.
Мы можем направлять одни потоки в направлении других:
$ rm exmple.txt >1.txt 2>&1
В этом примере мы направили поток stdout в файл 1.txt, а затем направили stderr туда же, куда направлен stdout с помощью оператора & перед номером потока.
Теперь давайте поиграем с потоком stdin. Например, я хочу найти все папки ".svn" в некотором проекте и удалить:
cd myproject
find .
Команда find с параметром. выводит в stdout все вложенные папки и файлы, которые находит в данной папке и во всех вложенных.
Теперь нам надо выбрать только папки с именем ".svn":
find . | grep -e '/.svn$'
Оператор | перенаправляет stdout одного приложения в stdin следующего. То есть все строки найденные с помощью find пошли в команду grep, которая выбирает строки по определенным условиям и выводит их. Здесь условие — это регулярное выражение, которое говорит о том, что строка должна заканчиваться на "/.svn".
Нужные папки мы выбрали, осталось их удалить.
rm -Rf `find . | grep -e '/.svn$'`
И снова новый оператор:
`. Он забирает stdout из команды, которую он окружает и вставляет в данное место как строку.
Получается, что мы запросили все файлы, выбрали из них папки с именем ".svn" и отдали результат как аргументы команде rm. В этом случае у нас будут проблемы если имена файлов и папок содержат пробелы. Исправляем ситуацию:
find . | grep -e '/.svn$' | xargs rm -Rf
Теперь мы отдаем нужные файлы команде xargs, которая вызывает rm -Rf и в качестве параметров использует свой stdin построчно. Задача решена.
Каждый может помочь развитию данной серии статей, поделиться своим опытом. Добро пожаловать: http://www.linuxman.ru. Все изменения в Вики я буду со временем переносить и в Хабр.
комментарии (43)
Во-вторых, сами догадаетесь? :)
А так за статью(не считаем ошибки) респект. Немногие сейчас отваживаются их писать)
Неверно. Это сделает команда
find .Не забываем, что globbing (замена wildcards) происходит в шелле. И
*по умолчанию не расширяется на dot entries в текущем каталоге.Столько тонкостей в процессе общественного обсуждения и деления опытом узнаёшь, о которых даже не догадывался.
Аффтар, пиши истчё!!! (простите за эту фразу)
Автор, не пиши больше такое.
Так что соглашаюсь с предыдущим автором, за find *|grep — надо ругать.
find . -type d -and -iname '.svn'."-and" в данном случае не нужен.
xargsи другими спецсимволами, лучше всегда пользоватьсяxargs -d '\n'. В связке сfindещё естьfind -print0 | xargs -0$ rm exmple.txt >1.txt 2>&1
Исправьте 1.txt на что-нибудь не содержащее 1, т.к. происходит путаница с потоками.
только не забывайте хорошенько все разжевывать :)
вот, например, про xargs узнать бы по подробнее с доходчивыми примерами
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 *
становится страшно…
awk '{ print $0 > поток 1; print $0 > поток 2; }'
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Хотя поздновато уже, не ручаюсь.
xargs читает со стандартного ввода записи, разделяя, их, среди прочего, и пробелами. Таким образом, если find найдёт файл с именем «раз два», то xargs запустит указанную команду с аргументами «раз» и «два». Нам нужно, чтобы find разделял записи разделителем, который не может присутствовать в именах файлов. Среди ext2/3 таких символов два — это нулевой символ (не имеет печатаемого обозначения) и символ прямого слэша. Прямой слэш, кажется, в некоторых файловых системах может являться частью имени файла, потому нам остаётся только нулевой символ.
Как сказано в мануале в первых строках по find: «you should probably consider using ‘-print0’ instead».
Действие -print0 заставляет find вывести на стандартный вывод результаты, разделяемые нулевым символом, а опция -0 у xargs заставляет в качестве разделителя записей принимать только нулевой символ.
В моём мане (это не сарказм, маны на разных системах бывают очень разные) есть такой пример для этого дела:
Что касается возможности запуска без xargs — для скриптов я бы посоветовал такую конструкцию:
Для применения вручную, после запуска без -delete и изучения списка:
При этом, файлы и директории типа .svnlalala тоже будут уничтожены, если присутствуют.
Мда…
Как насчет man find в районе exec?
Просто любопытно: вы программы как устанавливаете? ./configure; make; make install?