Pipe / redireciona um grupo de comandos

8

Atualmente, uso a configuração a seguir para redirecionar a saída de vários comandos:

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

Isso é muito útil e também funciona com pipes.

Esta é a melhor maneira de fazer isso? Existe uma alternativa que eu deveria considerar?

    
por wchargin 27.10.2014 / 17:53

3 respostas

13

A alternativa é usar chaves em vez de parênteses. Esta alteração executa os comandos no shell atual , não em uma subpasta

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

ref: link

Isso é particularmente relevante quando você modifica variáveis dentro do grupo:

$ 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
    
por 27.10.2014 / 18:09
4

A resposta de Glenn é boa - a distinção entre ( ... ) e { ... } é importante.

Uma estratégia que eu geralmente uso para a saída de erros, como o que está em sua pergunta, é o comando tee . Você poderia fazer algo assim:

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"

O comando tee enviará a saída para dois locais; -a opção "anexa" a saída ao arquivo nomeado e o comando também passará a entrada ao stdout. O >&2 no final da linha redireciona a stdout de tee para stderr, que pode ser tratada de maneira diferente (ou seja, em uma tarefa cron).

Uma outra dica que costumo usar em scripts de shell é alterar o comportamento da saída de depuração ou detalhada com base no fato de o script estar sendo executado em um terminal ou ter uma opção -v fornecida. Por exemplo:

#!/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

Os scripts podem começar com algo genérico como este na parte superior, com a saída Detalhada e Depuração espalhada por todo o script. É apenas uma maneira de fazer isso - há muitos, e pessoas diferentes terão seu próprio jeito de lidar com essas coisas, especialmente se já estiverem por aí há algum tempo. :)

Mais uma opção é manipular sua saída com um "manipulador" - uma função de shell que pode fazer coisas mais inteligentes. Por exemplo:

#!/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"

(Observe que ${var^^} é somente bash.)

Isso cria uma função de shell que pode usar a função syslog funções do seu sistema (com logger comando ) to send things to system logs. The logme () 'pode ser usada com opções que geram linhas únicas de dados de log ou com várias linhas de entrada que é processada em stdin. Jogue com ela se parecer atraente.

Note que este é um exemplo e provavelmente não deve ser copiado textualmente a menos que você o entenda e saiba que ele faz exatamente o que você precisa. Uma ideia melhor é pegar os conceitos aqui e implementá-los em seus próprios scripts.

    
por 27.10.2014 / 20:29
1

A maneira mais apropriada de fazer isso é com { command; } em vez de (command) . O motivo é que, quando os comandos são agrupados com () , um subshell é aberto para executar esses comandos e, portanto, as variáveis que são inicializadas durante esse bloco não estarão disponíveis para outras seções do script.

Em vez disso, quando usamos {} para o agrupamento de comandos, os comandos são executados dentro do mesmo shell e, portanto, as variáveis estarão disponíveis para outras seções do script.

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"

Aqui, quando esta seção for executada, a variável $var reterá seu valor, onde, como no outro caso, não será.

    
por 27.10.2014 / 18:15