Передать / перенаправить группу команд

8

В настоящее время я использую следующую настройку для перенаправления вывода нескольких команд:

echo "Some normal commands"
(
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"

Это довольно полезно, и это также работает с трубами.

Это лучший способ сделать это? Есть ли альтернатива, которую я должен рассмотреть?

wchargin
источник
Вот как я это сделаю. Вы сталкиваетесь с определенной проблемой с этим подходом?
Братчли
@JoelDavis Нет, просто интересно, есть ли лучший способ сделать это. Из ответов, которые я получил, похоже, что есть! :)
wchargin

Ответы:

15

Альтернативой является использование скобок вместо скобок. Это изменение выполняет команды в текущей оболочке, а не в подоболочке

echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"

ссылка: https://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

Это особенно актуально, когда вы изменяете переменные внутри группы:

$ x=5; ( x=10; echo inside: $x; ); echo outside: $x
inside: 10
outside: 5

$ x=5; { x=10; echo inside: $x; }; echo outside: $x
inside: 10
outside: 10
Гленн Джекман
источник
Отлично-спасибо! Это работает лучше с моим отступом также. (Vim отступы, { }но не ( ).)
wchargin
2
Обратите внимание, что вы можете свернуть группу команд в одну строку в любом случае; например, (echo msg1; echo msg2)- но с фигурными скобками, это должно быть { echo msg1; echo msg2;}, с пробелом после {и точка с запятой ( ;) или амперсанд ( &) перед }.
G-Man говорит: «Восстановите Монику»
4

Ответ Гленна хороший - различие между ( ... )и { ... }важно.

Одна из стратегий, которую я часто использую для вывода ошибок, например, в вашем вопросе, это teeкоманда Вы могли бы сделать что-то вроде этого:

echo "Normal output"
{
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "Warning text"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "This event is logged."
} | tee -a $logfile >&2
echo "More normal output"

Команда teeотправит вывод в два места; -aопция «добавляет» вывод в именованный файл, и команда также передает ввод в стандартный вывод. В >&2конце строки перенаправляет teestdout в stderr, который может обрабатываться по-разному (например, в задании cron).

Еще один совет, который я часто использую в сценариях оболочки, - это изменение поведения отладочной или подробной информации в зависимости от того, выполняется ли сценарий на терминале или имеет -vопцию. Например:

#!/bin/sh

# Set defaults
if [ -t 0 ]; then
  Verbose=true; vflag="-v"
else
  Verbose=false; vflag=""
fi
Debug=false; AskYN=true; Doit=true

# Detect options (altering defaults)
while getopts vdqbn opt; do
  case "$opt" in
    v)  Verbose=true; vflag="-v" ;;             # Verbose mode
    d)  Debug=true; Verbose=true; vflag="-v" ;; # Very Verbose
    q)  Verbose=false; vflag="" ;;              # quiet mode (non-verbose)
    b)  AskYN=false ;;                          # batch mode
    n)  Doit=false ;;                           # test mode
    *)  usage; exit 1 ;;
  esac
done

# Shift our options for further processing
shift $(($OPTIND - 1))

$Verbose && echo "INFO: Verbose output is turned on." >&2
$Debug && echo "INFO: In fact, expect to be overrun." >&2

# Do your thing here
if $AskYN; then
  read -p "Continue? " choice
  case "$choice" in
    Y|y) $Doit && somecommand ;;
    *) echo "Done." ;;
  esac
fi

Сценарии могут начинаться с чего-то общего, подобного этому, сверху, с подробным и отладочным выводом, разбросанным по всему сценарию. Это всего лишь один из способов сделать это - их много, и у разных людей будет свой собственный способ справиться с этим, особенно если они уже давно. :)

Еще один вариант - обработать вывод с помощью «обработчика» - функции оболочки, которая может делать более умные вещи. Например:

#!/bin/bash

logme() {
  case "${1^^}" in
    [IN]*)  level=notice ;;
    W*)     level=warning ;;
    A*)     level=alert ;;
    E*)     level=emerg ;;
    *)      level=notice ;;
  esac
  if [[ "$#" -eq 1 ]]; then
    # Strip off unnecessary prefixes like "INFO:"
    string="${1#+([A-Z])?(:) }"
  else
    shift
    string="$@"
  fi
  logger -p "${facility}.${level}" -t "$(hostname -s)" "$string"
}

echo "Normal output"
logme INFO "Here we go..."
somecommand | logme
echo "Additional normal output"

(Обратите внимание, что ${var^^}только для bash.)

Это создает функцию оболочки, которая может использовать функции вашей системы syslogloggerкомандой ) to send things to system logs. Thelogme () `функция может использоваться либо с опциями, которые генерируют отдельные строки данных журнала, либо с несколькими строками ввода, которые обрабатываются в stdin. Поиграйте с ней, если она кажется привлекательным

Обратите внимание, что это пример, и, вероятно, его не следует копировать дословно, если вы не понимаете его и не знаете, что он делает именно то, что вам нужно. Лучшая идея - взять концепции и реализовать их самостоятельно в своих собственных сценариях.

Ghoti
источник
Большое спасибо за ваш подробный ответ! Я на самом деле использовал function log () { cat >> $logfile }, который в основном является более простой версией вашего logme.
wchargin
Кроме того, кто-то может быть заинтересован ts(1); Использование: { printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2.
wchargin
@wchargin - ts(1)не установлен на системах, которые я использую - FreeBSD, OSX и старый Ubuntu. Можете ли вы сказать нам, что это обеспечивает?
ghoti
Он входит в состав moreutils, который также включает в себя несколько полезных инструментов, таких как sponge(1)(запись в файл только после закрытия stdin, так что вы можете обойтись something < foo | sponge fooбез зазубрин fooпри перенаправлении) и vipe(1)(вставка текстового редактора в канал).
wchargin
1

Более подходящий способ сделать это с { command; }, чем (command). Причина в том, что когда команды сгруппированы с ()подоболочкой, открывается для выполнения этих команд, и, следовательно, переменные, которые инициализируются во время этого блока, не будут доступны для других разделов скрипта.

Вместо этого, когда мы используем {}для группировки команд, команды выполняются в той же оболочке, и, следовательно, переменные будут доступны для других разделов скрипта.

echo "Some normal commands"

{
    var=1
    echo "Error: something happened"
    echo "Warning: this incident will be logged"
} >> logfile

echo "The value of var is: $var"
echo "More normal commands"

Здесь, когда этот раздел выполняется, $varпеременная сохраняет свое значение, а в другом случае - нет.

Каннан Мохан
источник
Кроме того, при использовании в одной строке не забывайте ставить пробелы между ними и завершайте команду точкой с запятой. Пример: { command; }.
CMCDragonkai