Як я перенаправляю висновок цілого сценарію оболонки в межах самого сценарію?


205

Чи можна перенаправити весь вихід сценарію оболонки Bourne кудись, але з командами оболонки всередині самого сценарію?

Перенаправити вихід однієї команди легко, але я хочу чогось більшого:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Значення: якщо сценарій запускається неінтерактивно (наприклад, cron), збережіть вихід файлу з усього файлу. Якщо запустити інтерактивно з оболонки, відпустіть вихід у stdout як завжди.

Я хочу зробити це для сценарію, який зазвичай виконується періодичною утилітою FreeBSD. Це частина щоденного пробігу, яку я зазвичай не бачу щодня бачити електронною поштою, тому я не надсилаю її. Однак, якщо щось із цього конкретного сценарію виходить з ладу, це для мене важливо, і я хотів би мати можливість отримувати та надсилати по електронній пошті результати цієї частини щоденних завдань.

Оновлення: відповідь Джошуа є точковою, але я також хотів зберегти та відновити stdout та stderr навколо всього сценарію, що робиться так:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
Тестування на $ TERM - не найкращий спосіб перевірити інтерактивний режим. Натомість перевірити, чи stdin є tty (тест -t 0).
Кріс Єстер-Янг

2
Іншими словами: якщо [! -t 0]; потім виконати> somefile 2> & 1; fi
Кріс Єстер-Янг

1
Ознайомтеся з усіма благами: http://tldp.org/LDP/abs/html/io-redirection.html В основному те, що сказав Джошуа. exec> файл перенаправляє stdout до певного файлу, exec <файл замінює stdin файлом і т. д. Це те саме, що зазвичай, але використовуючи exec (див. man exec для більш детальної інформації).
Локі

У розділі оновлень слід також закрити FD 3 і 4, як-от так: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Відповіді:


173

Адресацію питання як оновленого.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Дужки "{...}" забезпечують перенаправлення одиниці вводу / виводу. Дужки повинні з’являтися там, де команда могла з’явитися - спрощено, на початку рядка або після крапки з двокрапкою. ( Так, це можна зробити більш точним; якщо ви хочете посперечатися, дайте мені знати. )

Ви маєте рацію, що можете зберегти оригінальний stdout та stderr за допомогою показаних вами переспрямувань, але людям, яким доведеться пізніше підтримувати скрипт, простіше зрозуміти, що відбувається, якщо застосувати область переспрямованого коду, як показано вище.

Відповідні розділи посібника Bash - це команди групування та перенаправлення вводу / виводу . Відповідні розділи специфікації оболонки POSIX - це складові команди та перенаправлення вводу / виводу . Bash має додаткові позначення, але інакше схожий на специфікацію оболонки POSIX.


3
Це набагато зрозуміліше, ніж збереження оригінальних дескрипторів та відновлення їх пізніше.
Стів Мадсен

23
Мені довелося зробити якийсь гугл, щоб зрозуміти, що це насправді, тому я хотів поділитися. Фігурні дужки стають «блоком коду» , який, по суті, створює анонімну функцію . Вихід усе, що знаходиться в блоці коду, може бути перенаправлений (див. Приклад 3-2 із цього посилання). Також зауважте, що фігурні дужки не запускають підзаголовки , однак подібні переадресації вводу / виводу можна зробити з підзагортами за допомогою дужок.
chris

3
Мені подобається це рішення краще, ніж інші. Навіть людина, яка має лише основне розуміння перенаправлення вводу / виводу, може зрозуміти, що відбувається. Плюс - це більш багатослівний. І як пітонер я люблю багатослівні.
Джон Ред

Краще робити >>. Деякі люди мають звичку >. Додавання завжди безпечніше і більш рекомендовано, ніж перенесення. Хтось написав програму, яка використовує стандартну команду копіювання для експорту деяких даних до того ж пункту призначення.
neverMind9

Цей додаток - ccc.bmw71 (.pro) (віджет 3C Battery Monitor, раніше «Віджет монітора акумуляторів») завжди експортує історію акумулятора в /sdcard/bmw_history.txt. Якщо вже існує, здогадайтесь, що, НАДАЙТЕ! Це змусило мене випадково втратити частину історії акумулятора. Я встановив ліміт у днях від 30 до дуже високої кількості, що визнало його недійсним і повернуло до 30. Я хотів експортувати поточну історію акумуляторів до імпорту існуючої резервної копії. Потім це сталося.
neverMind9

175

Зазвичай ми розміщуємо один із них у верхній частині сценарію чи біля нього. Сценарії, які аналізують їхні командні рядки, будуть робити переадресацію після розбору.

Надіслати stdout у файл

exec > file

з stderr

exec > file                                                                      
exec 2>&1

додайте до файлу як stdout, так і stderr

exec >> file
exec 2>&1

Як Джонатан Леффлер згадував у коментарі :

execмає дві окремі робочі місця. Перший - це замінити виконуваний в даний час оболонку (скрипт) новою програмою. Інша - зміна переадресацій вводу / виводу в поточній оболонці. Це відрізняється відсутністю аргументів exec.


7
Я кажу також додати 2> & 1 в кінці цього, просто так stderr також потрапляє. :-)
Кріс Єстер-Янг

7
Куди ти їх кладеш? Вгорі сценарію?
колан

4
За допомогою цього рішення потрібно також скинути перенаправлення виходу скрипту. Наступна відповідь Джонатана Леффлера - це більше "невдалий доказ" у цьому сенсі.
Чуїм

6
@JohnRed: execмає два окремих завдання. Одне полягає в заміні поточного сценарію іншою командою, використовуючи той самий процес - ви вказуєте іншу команду як аргумент exec(і ви можете налаштувати переадресації вводу / виводу, як це робите). Іншим завданням є зміна переадресацій вводу / виводу в поточному сценарії оболонки без його заміни. Це позначення відрізняється тим, що команда не є аргументом exec. Позначення у цій відповіді є варіантом "тільки вводу / виводу" - він змінює лише переадресацію і не замінює запущений сценарій. ( setКоманда аналогічно багатоцільова.)
Джонатан Леффлер

18
exec > >(tee -a "logs/logdata.log") 2>&1друкує журнали на екрані, а також записує їх у файл
shriyog

32

Ви можете зробити весь сценарій такою функцією:

main_function() {
  do_things_here
}

то в кінці сценарію є таке:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Крім того, ви можете увімкнути все в лог-файл кожного запуску і все одно вивести його в stdout, просто виконавши:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

Ви мали на увазі головну функцію >> /var/log/my_uber_script.log 2> & 1
Феліпе Альварес

Мені подобається використовувати main_function в такій трубі. Але в цьому випадку ваш сценарій не повертає початкового значення повернення. У випадку з bash слід вийти з цього пункту, використовуючи 'exit $ {PIPESTATUS [0]}'.
rudimeier

7

Для збереження вихідного stdout та stderr ви можете використовувати:

exec [fd number]<&1 
exec [fd number]<&2

Наприклад, наступний код надрукує "walla1" і "walla2" у файл журналу ( a.txt), "walla3" у stdout, "walla4" на stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

1
Зазвичай, було б краще використовувати exec 5>&1і exec 6>&2, використовуючи позначення перенаправлення виводу, а не позначення переадресації вводу для виходів. Ви ухиляєтесь від нього, тому що, коли сценарій запускається з терміналу, стандартний вхід також записується, і стандартний вихід і стандартна помилка читаються в силу (або це "порок"?) Історичної химерності: термінал відкривається для читання та запис, і однаковий опис відкритого файлу використовується для всіх трьох стандартних дескрипторів файлів вводу / виводу.
Джонатан Леффлер

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.