Якщо ви часто працюєте в командному рядку, ви повинні бути знайомі з перенаправленням введення / виведення. Але, можливо, ви не знайомі з перенаправленням введення / виведення всередині bash-скрипта. Я не маю на увазі перенаправлення, що використовується при виклику інших програм з скрипта, я говорю про перенаправлення вводу / виводу вашого скрипта в цілому, починаючи з моменту його запуску.
Давайте, наприклад, припустимо, що вам знадобилося забезпечити ваш скрипт опцією «--log», за допомогою якої користувач міг би перенаправляти весь висновок роботи вашого скрипта в потрібний лог-файл. Звичайно, користувач міг би просто перенаправити висновок скрипта засобами bash, але давайте уявимо, що з якихось причин таке рішення не годиться. Давайте реалізуємо це:
#! / Bin / bash echo hello # Розбираємо опції командного рядка. # Виконуємо наступний код, якщо знайшли опцію --log if test -t 1; then # Stdout прив'язаний до терміналу exec> log else # Stdout не прив'язаний до терміналу, # протокол вести не вийде false fi echo goodbye echo error> & 2
У першому затвердження if за допомогою test виконується перевірка, чи підключено файловий дескриптор з номером один (потік стандартного виведення) до терміналу. Якщо це так, то exec «перевідкривається» його для запису в файл log. Виклик exec в контексті поточної оболонки без передачі їй команди, а тільки з зазначенням перенаправлення виведення, призводить до того, що ви можете відкривати / закривати файли, дублюючи їх дескриптори. Якщо ж файловий дескриптор з номером 1 не підключено до терміналу, то ми просто не робимо нічого.
Якщо ви запустите вищенаведений скрипт, то ви побачите, що перша і остання echo виконають свій висновок в термінал. Перша echo спрацює тому, що вона з'являється до включення перенаправлення, а друга - оскільки в сценарії її висновок перенаправлений в стандартний потік помилок (файловий дескриптор номер 2). І як же перенаправити стандартний потік помилок в той же файл? Всього Чи одне невелика зміна у виклику exec:
#! / Bin / bash echo hello if test -t 1; then # Stdout прив'язаний до терміналу exec> log 2> & 1 else # Stdout не прив'язаний до терміналу, # протокол вести не вийде false fi echo goodbye echo error> & 2
В наведеному вище прикладі exec перенаправляє потік помилок туди ж, куди спрямований потік виводу (це, власне, і називається дублюванням файлових дескрипторів). Майте на увазі, що порядок тут дуже важливий: якщо ви його зміните і переоткроете спершу потік помилок (т. Е. Exec 2> & 1> log), то весь висновок все одно залишиться спрямованим на термінал, оскільки він буде направлений туди ж, куди і потік виведення, а той, у свою чергу за замовчуванням спрямований в термінал.
Заради інтересу можна спробувати виконати те ж саме, коли потік стандартного висновку не підключений до терміналу. Ми не зможемо цього зробити, якщо потік виведення вже підключений до іншого файлу або перенаправлений в конвеєр, оскільки таким чином ми порушимо існувало перенаправлення.
Візьмемо, наприклад, команду:
bash test.sh | grep good
Те, що нам потрібно в кінцевому підсумку, це еквівалент наступної команди:
bash test.sh | tee log | grep good
Ймовірно, вашої першою думкою буде викликати exec якось так:
exec | tee log & # працювати не буде
намагаючись таким чином за допомогою exec перенаправити висновок в tee, запущеної в фоновому режимі. Але це не буде працювати (хоча bash нічого і не скаже). Така конструкція всього лише перенаправляє висновок exec в tee, а оскільки exec не виводить нічого, то tee створить порожній файл і на цьому закінчить.
Є ще одна думка: запустити tee у фоновому режимі, направивши в неї введення з одного файлового дескриптора, а висновок перенапровіть в інший. І ви можете це зробити, але проблема в тому, що не існує способу створити новий процес, стандартне введення якого був би підключений до каналу. Якби ми могли це зробити, то отримати висновок tee було б просто, оскільки за замовчуванням він спрямований туди ж, куди і висновок сценарію цілком. Таким чином, ми могли б просто закрити потік виведення сценарію і підключити його до нашого каналу, якби була можливість цей канал створити.
Виходить, ми в глухому куті? Ні. Рішення описано в останніх двох реченнях попереднього абзацу. Нам потрібно лише створити канал, вірно? Відмінно, давайте використовувати іменовані канали.
#! / Bin / bash echo hello if test -t 1; then # Stdout is a terminal. exec> log else # Stdout is not a terminal. npipe = / tmp / $$. tmp trap "rm -f $ npipe" EXIT mknod $ npipe p tee & - exec 1> $ npipe fi echo goodbye
Тут, якщо потік стандартного висновку не підключений до терміналу, ми створюємо іменований канал (канал, який розташовується у вигляді файлу в файлової системі) за допомогою mknod, і за допомогою trap видаляємо його після завершення роботи сценарію. Потім ми запускаємо tee, пов'язуючи його потік введення зі створеним каналом і вказуємо записувати відео у файл log. Пам'ятайте, що tee крім запису в файл всього отриманого з потоку введення, також виводить все в потік стандартного виведення. Також, згадайте про те, що потік виведення tee направлений туди ж, куди і весь висновок сценарію, що викликає tee. Таким чином, весь висновок tee потраплятиме туди, куди в даний момент спрямований потік виведення нашого сценарію, тобто, в перенаправлений користувачем потік виведення або канал конвеєра, задані в момент виклику сценарію з командного рядка. Так що тепер ми отримали стандартний висновок tee там, де нам це потрібно: в перенаправлення або канал конвеєра, певний користувачем.
Тепер залишається передати на вхід tee потрібні дані. Оскільки tee тепер зчитує дані з іменованого каналу, все що нам необхідно, це перенаправити стандартний висновок в іменований канал. Ми закриваємо поточний потік виводу (exec 1> & -) і відкриваємо його в іменований канал (exec 1> $ npipe). Зверніть увагу, що закриття поточного потоку виведення нічого не порушує, оскільки tee також здійснює висновок в перенаправлений користувачем потік або канал.
Тепер, коли ви виконаєте команду і направите її висновок в канал до grep, ви отримаєте і стандартний висновок і запис в лог-файлі.
Подібних прийомів маса, відкрийте man-сторінку bash!
PS У Bash 4 те ж саме можна зробити за допомогою конструкції coproc, але про це іншим разом.
Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.
По мотивам linuxjournal.com .
І як же перенаправити стандартний потік помилок в той же файл?Виходить, ми в глухому куті?
Нам потрібно лише створити канал, вірно?