Напомним, что интерпретатор работает с тремя стандартными потоками.
- stdout это стандартный поток вывода, который обеспечивает вывод команды. Его дескриптор равен 1.
- stderr это стандартный поток ошибок, который выводит ошибки команд. Его дескриптор равен 2.
- stdin это стандартный поток ввода, который обеспечивает ввод командам. Его дескриптор равен 0.
Входные потоки обеспечивают ввод командам, который обычно поступает от клавиш терминала. Потоки вывода печатают символы текста, обычно на терминал. Изначально терминал представлял собой печатную машинку ASCII или же дисплейный терминал, сейчас в основном это окно на рабочем столе графической среды.В разделе Текстовые потоки и фильтры мы видели, как перенаправлять стандартный вывод в файл или на стандартный ввод другой команды, а также можем перенаправить стандартный ввод из файла или из вывода другой команды.
Перенаправление вывода
Существует два способа перенаправить вывод:
- n>
- перенапрвляет вывод из файлового дескриптора nв файл. У вас должно быть право записи в файл. Если файла не существует, то он будет создан. Если файл существует, его содержимое будет утеряно без предупреждения.
- n>>
- также перенаправляет вывод из файлового дескриптора n в файл. Снова, у вас должно быть право на запись в файл. Если файл не существует, то будет создан. Если он существует, то вывод команды добавится к существующему файлу.
Символы nв n> или n>> являются дескрипторами файла. Если его не написать, то по умолчанию предполагается стандартный вывод. В Листинге 66 приведено перенаправление стандартного вывода и стандартного потока ошибок команды ls, используя файлы, созданные ранее в каталоге lpi103. Мы также продемонстрируем добавление вывода в существующий файл.
Листинг 66. Перенаправление вывода
[ian@echidna lpi103]$ ls x* z*
ls: z*: No such file or directory
xaa xab
[ian@echidna lpi103]$ ls x* z* >stdout.txt 2>stderr.txt
[ian@echidna lpi103]$ ls w* y*
ls: w*: No such file or directory
yaa yab
[ian@echidna lpi103]$ ls w* y* >>stdout.txt 2>>stderr.txt
[ian@echidna lpi103]$ cat stdout.txt
xaa
xab
yaa
yab
[ian@echidna lpi103]$ cat stderr.txt
ls: z*: No such file or directory
ls: w*: No such file or directory
Мы сказали, что перенаправление с помощью n> обычно переписывает существующий файл. Вы можете контролировать это с помощью опции noclobber встроенной команды set. Если она определена, то вы моежете ее подавить с помощью опции n>| как показано в Листинге 67.
Листинг 67. Перенаправление вывода с помощью noclobber
[ian@echidna lpi103]$ set -o noclobber
[ian@echidna lpi103]$ ls x* z* >stdout.txt 2>stderr.txt
-bash: stdout.txt: cannot overwrite existing file
[ian@echidna lpi103]$ ls x* z* >|stdout.txt 2>|stderr.txt
[ian@echidna lpi103]$ cat stdout.txt
xaa
xab
[ian@echidna lpi103]$ cat stderr.txt
ls: z*: No such file or directory
[ian@echidna lpi103]$ set +o noclobber #восстанавливанем изначальное значение параметра noclobber
Иногда вам требуется перенаправить как стандартный вывод так поток ошибок в файл. Это часто делается для автоматизированных процессов или фоновых задач, так что вы можете рассмотреть вывод позже. Используйте &> или &>>, чтобы перенаправить стандартный вывод и поток ошибок в одно и тоже место. Другой способ состоит в перенаправлении файлового дескриптора n, а затем перенаправлении файлового дескриптора m в одно и тоже место с помощью конструкции m>&n или m>>&n. Важен порядок, в котором осуществляется перенаправление вывода. Например,
command 2>&1 >output.txt
не тоже самое, что
command >output.txt 2>&1
Проиллюстрируем эти концепции в Листинге 68. Заметим, что в последней команде стандартный вывод был перенаправлен после потока ошибок, таким образом стандартный вывод все еще выводится на терминал.
Листинг 68. Перенаправление двух потоков в один файл
[ian@echidna lpi103]$ ls x* z* &>output.txt
[ian@echidna lpi103]$ cat output.txt
ls: z*: No such file or directory
xaa
xab
[ian@echidna lpi103]$ ls x* z* >output.txt 2>&1
[ian@echidna lpi103]$ cat output.txt
ls: z*: No such file or directory
xaa
xab
[ian@echidna lpi103]$ ls x* z* 2>&1 >output.txt
ls: z*: No such file or directory
[ian@echidna lpi103]$ cat output.txt
xaa
xab
В другие разы вам потребуется проигнорировать полностью стандартный вывод или поток ошибок. В этом случае перенаправьте нужный поток в /dev/null. В Листинге 69 мы показжем как игнорировать поток ошибок команды ls.
Листинг 69. Игнорирование вывода с помощью /dev/null
[ian@echidna lpi103]$ ls x* z* 2>/dev/null
xaa xab
[ian@echidna lpi103]$ cat /dev/null
Перенаправление ввода
Также как мы перенаправляли потоки stdout и stderr, вы тоже можете перенаправлять stdin из файла, используя оператор <. Если вы вспомните в обсуждении sort и uniq мы использовали команду tr для замены пробелов в файле text1 на символы табуляции. В том примере мы использовали вывод команды cat, чтобы создать стандартный ввод для команды tr. Вместо бессмысленного вызова cat,мы можем использовать перенаправление ввода для преобразования пробелов символы табуляции, как показано в Листинге 70.
Листинг 70. Перенаправление ввода
[ian@echidna lpi103]$ tr ' ' '\t'
2 pear
3 banana
У интерпретаторов, включая bash, также есть концепция документа, которая является другой формой перенаправления ввода. Она включает в себя использование << и слова, такого как END, в качестве маркера или сигнала конца ввода. Проиллюстрируем это на примере Листинга 71.
Листинг 71. Перенаправление ввода, используя концепцию документа
[ian@echidna lpi103]$ sort -k2 <
> 2 pear
> 3 banana
> END
1 apple
3 banana
2 pear
Помните, как мы создали файл text2 в Листинге 23? Вы можете удивиться, почему бы просто не набрать sort -k2, ввести свои данные, а затем нажать Ctrl-d, чтобы сигнализировать окончание ввода. Короткий ответ состоит в том, что вы можете, но в таком случае вы не узнали бы о концепции документа. В действительности, документы очень часто используются в сценариях (которые рассмотрены в руководстве по теме 109 об интерпретаторах, сценариях, программировании и компиляции). В сценарии нет другого способа сигнализировать о том, какие строки необходимо воспринимать как ввод. Так как сценарии интенсивно используют табуляцию для выравнивания информации, то существует другой прием работы с документами. Если вы используете <<- вместо <<, тогда ведущие символы табуляции будут удалены. В Листинге 72 мы использовали подобную технику для создания символа табуляции, который затем использовали в Листинге 42. Затем мы создадим очень маленький сценарий, содержащий две команды cat каждый из которых будет считывать из документа. Наконец, мы используем команду . (точку), чтобы обнаружить сценарий в текущем каталоге и запустить его.
Листинг 72. Перенаправление ввода с помощью документа
[ian@echidna lpi103]$ ht=$(echo -en "\t")
[ian@echidna lpi103]$ cat<
> cat <<-EOF
> apple
> EOF
> ${ht}cat <<-EOF
> ${ht}pear
> ${ht}EOF
> END
[ian@echidna lpi103]$ cat ex-here.sh
cat <<-EOF
apple
EOF
cat <<-EOF
pear
EOF
[ian@echidna lpi103]$ . ex-here.sh
apple
pear
Конвейеры
В разделе Текстовые потоки и фильтры мы обсуждали фильтрацию текста, как процесс взятия входного потока, совершения преобразования над текстом и отсылки его в выходной поток. Мы также сказали, что фильтрация часто осуществляется с помощью конвейера команд, в котором выход одной команды соединяется или перенаправляется на вход другой команды. Подобное использование конвейров не ограничивается только текстовыми потоками, хотя именно для они используются чаще всего.
Соединение stdout с stdin
Как мы уже видели, мы используем оператор | (конвейера) между двумя командами для перенаправления stdout первой команды на stdin второй команды. Мы конструируем длинные конвейеры из нескольких команд в Листинге 73.
Листинг 73. Конвейер из нескольких команд
command1 | command2 | command3
Следует заметить, что конвейеры перанаправляют только stdout на stdin. Вы не можете использовать 2|, чтобы перенаправить stderr, по крайней мере, используя наши сейчас знания. Если stderr был перенаправлен на stdout, тогда оба потока будут соеденены. Иллюстрируем этот подход в Листинге 74, в котором используем конвейер для сортировки сообщений об ошибках и нормальных сообщений команды ls с четырьмя шаблонами, расположенными не по алфавиту.
Листинг 74. Конвейер из двух выходных потоков
[ian@echidna lpi103]$ ls y* x* z* u* q* 2>&1 |sort
ls: q*: No such file or directory
ls: u*: No such file or directory
ls: z*: No such file or directory
xaa
xab
yaa
yab
У любой из команд могут быть опции или аргументы. Многие команды используют дефис (-) вместо имени файла как аргумент, чтобы сообщить, что ввод будет из stdin, а не из файла. Подробнее смотрите в man-страницах. Конструирование конвейера из команд, каждая из которых решает свою задачу, составляет философию решения задач в Linux UNIX.
Одним из преимуществ конвейеров в системах Linux и UNIX является то, что в отличие от других популярных операционных систем, конвейеры не используют промежуточных файлов. Stdout первой команды не пишется в файл, который затем считывается второй командой. Если ваша конкретная версия tar не поддерживает разжатие файлов с помощью bzip2,не беспокойтесь. Как мы видели в руководстве по Теме 102, вы можете просто использовать конвейер как, например
bunzip2 -c drgeo-1.1.0.tar.bz2 | tar -xvf -
Вывод в качестве аргументов
В разделе Использование командной строки мы изучили подстановку команд и как использовать вывод одной команды как часть другой. В предыдущем разделе Управление файлами мы узнали как использовать опцию -i команды find, чтобы использовать вывод команды find как ввод для другой команды. В Листинге 75 представлено три способа отображения содержимого файлов text1 и text2.
Листинг 75. Использование вывода как аргументов с помощью подстановки команды и find -exec
[ian@echidna lpi103]$ cat `ls text[12]`
1 apple
2 pear
3 banana
9 plum
3 banana
10 apple
[ian@echidna lpi103]$ cat $(find . -name "text[12]")
1 apple
2 pear
3 banana
9 plum
3 banana
10 apple
[ian@echidna lpi103]$ find . -name "text[12]" -exec cat '{}' \;
1 apple
2 pear
3 banana
9 plum
3 banana
10 apple
Приведенные примеры работают, но с ограничениями. Положим, что у вас есть файл, содержащий разделитель (пробел в этом случае). Посмотрите Листинг 76 и попробуйте понять, что делает каждая команда, прежде чем читать дальше.
Листинг 76. Использование вывода в качестве аргументов с помощью подстановки команды и find -exec
[ian@echidna lpi103]$ echo grapes>"text sample2"
[ian@echidna lpi103]$ cat `ls text*le2`
cat: text: No such file or directory
cat: sample2: No such file or directory
[ian@echidna lpi103]$ cat "`ls text*le2`"
grapes
[ian@echidna lpi103]$ cat "`ls text*2`"
cat: text2
text sample2: No such file or directory
Вот, что мы делали.
- Мы создали файл "text sample2", содержащий одну строку со словом "grapes"
- Мы попытались использовать подстановку команды, чтобы отобразить содержимое "text sample2". Нас постигла неудача, так как bash передал два параметра команде cat, а именно text и sample2.
- Будучи умнее bash, мы заключили значения команды подстановки в кавычки. Это сработало
- Наконец, мы заменили шаблон, и вывод представляет собой странную ошибку. Здесь получилось так, что bash передал команде cat один параметр, который эквивалентен строке, являющей результатом
echo -e "text2\ntext sample2"
Если это кажется странным, попробуйте сами!
Что нам требуется так это способ выделения имен файлов вне зависимости от количества в них слов. Мы не упомянули ранее, что когда вывод команды как, например ls, используется в конвейере или команде подстановке, то он считывается построчно. Один способ состоит в использовании встроенной команды read в цикле со встроенной командой while. Хотя это и находится за рамками этого руководства, мы покажем данное решение.
Листинг 77. Использование while и read в цикле
[ian@echidna lpi103]$ ls text*2 | while read l; do cat "$l";done
9 plum
3 banana
10 apple
grapes
xargs
Большую часть времени мы будем обрабатывать списки файлов, поэтому нам требуется средства их создания и обработки. К счастью, у команды find есть опция -print0, которая разделяет строки своего вывода символом конца строки, а не символом новой строки. Такие команды как tar и xargs содержат опцию -0 (или --null), которая позволяет им понимать такой тип параметров. Мы уже встречались с tar. Команда xargs работает почти как опция -exec команды find, однако существует большая разница между ними, которую мы увидим. Давайте посмотрим пример.
Листинг 78. Использование xargs с -0
[ian@echidna lpi103]$ find . -name "text*2" -print0 |xargs -0 cat
9 plum
3 banana
10 apple
1 apple
2 pear
3 banana
grapes
Заметим, что теперь мы перенаправили вывод из find в xargs. Вам не надо использовать точку с запятой в конце команды и, по умолчанию, xargs добавляет аргументы к командной строке. Однако система выдала 7 строк вместо ожидаемых четырех. Что пошло не так?
снова о find
Мы можем использовать команду wc для проверки того, что всего в двух файлах, вывод которых мы ожидали, четыре строки . Корень проблемы лежит в том, что find ведет поиск в каталоге с резервными копиями, в котором она находит backup/text1.bkp.2, соответствующий нашему шаблону. Чтобы решить проблему, воспользуемся опцией -maxdepth команды find, чтобы ограничить глубину поиска до текущего каталога. Есть также опция -mindepth, которая позволит еще более уточнить поиск. В Листинге 79 приведено полное решение.
Листинг 79. Ограничение find
[ian@echidna lpi103]$ ls text*2
text2 text sample2
[ian@echidna lpi103]$ wc text*2
3 6 25 text2
1 1 7 text sample2
4 7 32 total
[ian@echidna lpi103]$ find . -name "text*2" -maxdepth 1 -print0 |xargs -0 cat
9 plum
3 banana
10 apple
grapes
Подробно о xargs
Существует разница между xargs и find -exec.
- Команда xargs по умолчанию передает так много аргументов команде, насколько это возможно. Вы можете ограничить число входных строк с помощью -l или --max-lines за которой следует число. Кроме того, можете использовать -n или --max-args для ограничения количества передаваемых аргументов или -s или --max-chars для ограничения максимального числа символов в строке аргументов. Если ваша команда способна обработать несколько аргументов, то более эффективной будет передача ей как можно большего числа параметров за раз.
- Вы можете использовать '{}' как делали для find -exec если определите опцию -i или --replace. Вы можете изменить поведение '{}' по умолчанию для строки, которые сигнализируют место подстановки входного параметра, определив значение для -i. То есть подразумевается -l 1.
Наш последний пример с xargs показан в Листинге 80.
Листинг 80. Примеры xargs
[ian@echidna lpi103]$ # передача всех аргументов за раз
[ian@echidna lpi103]$ find . -name "text*2" |xargs echo
./text2 ./backup/text1.bkp.2 ./text sample2
[ian@echidna lpi103]$ # покажем файлы, которые мы создали раньше с помощью команды touch
[ian@echidna lpi103]$ ls f[0-n]*|xargs echo
f1 f1a f2 f3 f4 f5 f6 f7 f8 f9
[ian@echidna lpi103]$ # удалим их всех одной строкой
[ian@echidna lpi103]$ ls f[0-n]*|xargs rm
[ian@echidna lpi103]$ # используем строку подстановки
[ian@echidna lpi103]$ find . -name "text*2" |xargs -i echo - '{}' -
- ./text2 -
- ./backup/text1.bkp.2 -
- ./text sample2 -
[ian@echidna lpi103]$ # Ограничимся одной строкой вывода на вызов
[ian@echidna lpi103]$ find . -name "text*2" |xargs -l1 echo
./text2
./backup/text1.bkp.2
./text sample2
[ian@echidna lpi103]$ # Ограничимся одним аргументом на вызов
[ian@echidna lpi103]$ find . -name "text*2" |xargs -n1 echo
./text2
./backup/text1.bkp.2
./text
sample2
Заметим, что мы не использовали здесь -print0. Объясняет ли это последний пример в Листинге 80?
Разделение вывода
В этом разделе рассмотрим еще одну команду. Иногда вам понадобится просмотреть вывод на экране и сохранить его в файл для последующего использования. Хотя вы можете это сделать, перенаправив вывод в файл в одном окне, а затем использовать tail -fn1, чтобы увидеть текст на другом экране, использования команды tee гораздо проще.
Вы можете использовать tee в конвейере. Ее аргументы это файл (или файлы), в которые необходимо скопировать стандартный вывод. Опция -a добавляет, а не переписывает файлы. Как мы видели ранее в этом разделе при обсуждении конвейеров, вам понадобиться перенаправить stderr в stdout, до того как соединить его с tee в случае, если вам требуется оба потока. В Листинге 81 показано использование tee для сохранения вывода двух файлов f1 и f2.
Листинг 81. Разделение stdout с помощью tee
[ian@echidna lpi103]$ ls text[1-3]|tee f1 f2
text1
text2
text3
[ian@echidna lpi103]$ cat f1
text1
text2
text3
[ian@echidna lpi103]$ cat f2
text1
text2
text3
Взято с ibm developerworks