Como evitar repetir o mesmo comando no script bash

4

Eu tenho este script bash:

gunzip -c /var/log/cisco/cisco.log-$(date +%Y%m%d).gz | awk '/ath_bstuck_tasklet/ { print $4 }' | sort | uniq -c > /tmp/netgear_beacon.txt
echo "There are  'wc -l /tmp/netgear_beacon.txt | awk '{print $1}'' Stuck beacon; resetting" >> /tmp/netgear_beacon.txt

gunzip -c /var/log/cisco/cisco.log-'date +%Y%m%d'.gz | awk '/Virtual device ath0 asks to queue packet/ { print $4 }' | sort | uniq -c > /tmp/netgear_buffer_queue.txt
echo "There are  'wc -l /tmp/netgear_buffer_queue.txt | awk '{print $1}''  routers with 'Virtual device ath0 asks to queue packet' errors" >> /tmp/netgear_buffer_queue.txt

gunzip -c /var/log/cisco/cisco.log-'date +%Y%m%d'.gz | awk '/CMS_MSG_DNSPROXY_RELOAD/ { print $4 }' | sort | uniq -c > /tmp/netgear_dns.txt
echo "There are  'wc -l /tmp/netgear_dns.txt | awk '{print $1}''  routers with 'DNS Proxy Issue' errors" >> /tmp/netgear_dns.txt

gunzip -c /var/log/cisco/cisco.log-$(date +%Y%m%d).gz | awk '/beacon/ { print $4 }' | sort | uniq -c > /tmp/netgear_beacon_frame.txt
echo "There are  'wc -l /tmp/netgear_beacon_frame.txt | awk '{print $1}'' routers with beacon frame errors" >> /tmp/netgear_beacon_frame.txt

gunzip -c /var/log/cisco/cisco.log-$(date +%Y%m%d).gz | awk '/ACK/ { print $4 }' | sort | uniq -c | awk -v x=50 '$1 >= x' > /tmp/netgear_ACK.txt
echo "There are  'wc -l /tmp/netgear_ACK.txt | awk '{print $1}'' routers with more than 50 ACK" >> /tmp/netgear_ACK.txt

Eu tentaria não repetir o comando gunzip todas as vezes. Eu iria executá-lo apenas uma vez e usá-lo para todas as etapas. Eu estava pensando em uma variável, mas é a melhor prática?

    
por Federi 27.11.2015 / 15:02

2 respostas

10

Não há "práticas recomendadas". Apenas coisas que fazem sentido e facilitam as coisas.

Extrair as partes comuns e parametrizar o resto é uma dessas coisas:

lines="'gunzip -c /var/log/cisco/cisco.log-$(date +%Y%m%d).gz'"
#gunzip would always output the same thing on the same day, so 
#just run it once and store the results in a variable
grepAndLog(){
  local regex="$1" file="$2" msg="$3" filter="${4:-cat}"
  #^names for positional parameters

  printf "%s\n" "$lines" | grep "$regex" | cut -d' ' -f4 | sort | uniq -c | eval "$filter"  > "/tmp/$file"
  local count='wc -l < "/tmp/$file"'   
  echo "There are $count "" $msg" >> "/tmp/$file"
}
grepAndLog ath_bstuck_tasklet netgear_bacon.txt \
 'Stuck beacon; resetting'
grepAndLog netgear_buffer_queue netgear_buffer_queue.txt \
 "routers with 'Virtual device ath0 asks to queue packet' errors"
grepAndLog CMS_MSG_DNSPROXY_RELOAD netgear_dns.txt \
 " routers with 'DNS Proxy Issue' errors"
grepAndLog ath_bstuck_tasklet netgear_bacon.txt \
 " routers with beacon frame errors"
grepAndLog ACK netgear_ACK.txt \
 " routers with more than 50 ACK" 'awk -v x=50 "\ >= x"'

Ainda é uma solução de shell principal. Mas o IMO é mais legível e mais de 40% mais curto.

Sobre o código:

Estou usando grep "$regex" | cut -d' ' -f4 em vez da expressão awk. Além disso, a função grepAndLog é uma generalização do que você faz em cada linha do script: Você tem alguma entrada (a saída do gunzip), você usa isso para uma expressão (o parâmetro $regex ) e imprime as linhas resultantes, classificadas e prefixadas com count em $file . Então você acrescenta a contagem de linha (Eu faço wc -l < "$file" em vez de wc -l "$file" | awk ... ) envolto em uma mensagem cujo começo é constante e cujo final varia ( $msg ).

Na sua última linha, você não simplesmente usa grep, mas usa outro filtro além disso. Em vez de criar uma ramificação if para isso na função, simplesmente uso cat como um filtro adicional padrão implícito nos casos normais em que não existe quarto parâmetro ( local filter="${4:-cat}" significa criar um filtro variável de função local cujo conteúdo é o quarto parâmetro dado à função ou cat se nenhum quarto parâmetro for fornecido). cat será sobreposto se um quarto parâmetro for dado a grepAndLog .

    
por 27.11.2015 / 15:42
9

A melhor coisa a fazer aqui seria realizar todo o processamento em um único awk . Algo semelhante a isto:

gunzip -c /var/log/cisco/cisco.log-$(date +%Y%m%d).gz | awk '
/ath_bstuck_tasklet/ { netgear_beakon[$4] = 1 }
/Virtual device ath0 asks to queue packet/ { netgear_buffer_queue[$4] = 1 }
...
/ACK/ { netgear_ACK[$4] ++ }
END {
  n=0; for(k in netgear_beakon) n++; print n,"Stuck beacon; resetting";
  n=0; for(k in netgear_buffer_queue) n++; print n,"routers with Virtual device ath0 asks to queue packet";
  ...
  n=0; for(k in netgear_ACK) n+=(netgear_ACK[k]>=50); print n,"routers with more than 50 ACK"
}'

Além de eliminar a leitura do arquivo mais de uma vez, isso também elimina a necessidade de executar sort e uniq várias vezes. Isso armazena (ou conta) cada item exclusivo em uma matriz e, em seguida, calcula o número de itens, iterando sobre as chaves de cada matriz.

    
por 27.11.2015 / 15:15