Как перенаправить вывод всего сценария оболочки внутри самого сценария?

205

Можно ли перенаправить все выходные данные сценария оболочки Bourne куда-нибудь, но с помощью команд оболочки внутри самого сценария?

Перенаправить вывод одной команды легко, но я хочу что-то вроде этого:

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

# rest of script...

Значение: если скрипт запускается не в интерактивном режиме (например, cron), сохраните все данные в файле. Если он запускается в интерактивном режиме из оболочки, пусть вывод идет в стандартный вывод.

Я хочу сделать это для скрипта, обычно запускаемого периодической утилитой 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 (test -t 0).
Крис Джестер-Янг
2
Другими словами: если [! -t 0]; тогда exec> somefile 2> & 1; fi
Крис Шут-Янг
1
Смотрите здесь для всего добра: http://tldp.org/LDP/abs/html/io-redirection.html В основном то, что сказал Иисус Навин. exec> file перенаправляет stdout в определенный файл, exec <file заменяет stdin файлом и т. д. Он такой же, как обычно, но с использованием exec (более подробную информацию смотрите в man exec)
Локи
В своем разделе обновлений вы также должны закрыть FD 3 и 4, например, так: exec 1>&3 2>&4 3>&- 4>&-
Гурджит Сингх

Ответы:

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 по этой ссылке). Также обратите внимание, что фигурные скобки не запускают подоболочку , но аналогичные перенаправления ввода-вывода могут быть выполнены с подоболочками с использованием скобок.
Крис
3
Мне нравится это решение лучше, чем другие. Даже человек, обладающий только самым базовым пониманием перенаправления ввода / вывода, может понять, что происходит. Плюс, это более многословно. И, как питонер, я люблю многословно.
Джон Ред
Лучше сделать >>. У некоторых людей есть привычка >. Аппендинг всегда безопаснее и рекомендуется, чем овергинг. Кто-то написал приложение, которое использует стандартную команду копирования для экспорта некоторых данных в тот же пункт назначения.
neverMind9
Это приложение ccc.bmw71 (.pro) (виджет 3C Battery Monitor, ранее «Виджет Battery Monitor») всегда экспортирует историю батареи в /sdcard/bmw_history.txt. Если уже существует, угадайте, что, переписать! Это заставило меня случайно потерять некоторую историю батареи. Я установил ограничение в днях от 30 до очень большого числа, которое аннулировало его и вернул обратно до 30. Я хотел экспортировать текущую историю батареи перед импортом существующей резервной копии. Тогда это случилось.
neverMind9
176

Обычно мы помещаем один из них в верхнюю часть скрипта или около него. Скрипты, которые анализируют свои командные строки, будут выполнять перенаправление после анализа.

Отправить стандартный вывод в файл

exec > file

с stderr

exec > file                                                                      
exec 2>&1

добавить как stdout, так и stderr в файл

exec >> file
exec 2>&1

Как отметил Джонатан Леффлер в своем комментарии :

execимеет две отдельные работы. Первый - заменить текущую исполняемую оболочку (скрипт) новой программой. Другой - изменение перенаправлений ввода / вывода в текущей оболочке. Это отличает отсутствие аргументов exec.

Джошуа
источник
7
Я говорю также добавить 2> & 1 в конце этого, просто чтобы stderr тоже был пойман. :-)
Крис Шестер-Янг
7
Куда ты их положил? В верхней части сценария?
колан
4
При таком решении необходимо также сбросить редирект выхода из скрипта. Следующий ответ Джонатана Леффлера является в этом смысле более «стойким к ошибкам».
Chuim
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

В качестве альтернативы, вы можете записывать все в файл журнала при каждом запуске и по-прежнему выводить его на стандартный вывод, просто выполнив:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
источник
Вы имели в виду main_function >> /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, используя нотацию перенаправления вывода, а не нотацию перенаправления ввода для выходов. Вам это сходит с рук, потому что когда скрипт запускается из терминала, стандартный ввод также доступен для записи, и как стандартный вывод, так и стандартная ошибка читаются благодаря исторической причуде (или это «порок»?): Терминал открыт для чтение и запись, и одно и то же описание открытого файла используется для всех трех стандартных файловых дескрипторов ввода / вывода.
Джонатан Леффлер