bash + usando printf para imprimir em formato especial

10

Acabei de escrever o seguinte script bash para verificar o acesso ping na lista de máquinas Linux:

for M in $list
 do
   ping -q -c 1  "$M" >/dev/null 
          if [[ $? -eq 0 ]]
   then
    echo "($C) $MACHINE CONNECTION OK"
   else
    echo "($C) $MACHINE CONNECTION FAIL"
   fi

   let C=$C+1
done

Isto imprime:

 (1) linux643 CONNECTION OK
 (2) linux72 CONNECTION OK
 (3) linux862 CONNECTION OK
 (4) linux12 CONNECTION OK
 (5) linux88 CONNECTION OK
 (6) Unix_machinetru64 CONNECTION OK

Como posso usar printf (ou qualquer outro comando) no meu script bash para imprimir o seguinte formato?

 (1) linux643 ............ CONNECTION OK
 (2) linux72 ............. CONNECTION OK
 (3) linux862 ............ CONNECTION OK
 (4) linux12 ............. CONNECTION OK
 (5) linux88 ............. CONNECTION FAIL
 (6) Unix_machinetru64 ... CONNECTION OK
    
por yael 30.06.2016 / 15:46

5 respostas

15

Usando a expansão de parâmetro para substituir espaços resultantes de %-s por pontos:

#!/bin/bash
list=(localhost google.com nowhere)
C=1
for M in "${list[@]}"
do
    machine_indented=$(printf '%-20s' "$M")
    machine_indented=${machine_indented// /.}

    if ping -q -c 1  "$M" &>/dev/null ;  then
        printf "(%2d) %s CONNECTION OK\n" "$C" "$machine_indented"
    else
        printf "(%2d) %s CONNECTION FAIL\n" "$C" "$machine_indented"
    fi
    ((C=C+1))
done
    
por 30.06.2016 / 16:04
8

for m in $list é zsh sintaxe. Em bash , seria for i in "${list[@]}" .

bash não possui operadores de preenchimento. Você pode fazer preenchimento com printf , mas apenas com espaços, não com caracteres arbitrários. zsh tem operadores de preenchimento.

#! /bin/zsh -
list=(
  linux643
  linux72
  linux862
  linux12
  linux88
  Unix_machinetru64
)
c=0
for machine in $list; do
  if ping -q -c 1 $machine >& /dev/null; then
    state=OK
  else
    state=FAIL
  fi
  printf '%4s %s\n' "($((++c)))" "${(r:25::.:):-$machine } CONNECTION $state"
done

O operador padding é ${(r:25:)parameter} para direito -pad com comprimento 25 com espaços ou ${(r:25::string:)parameter} para direito -pad com qualquer string em vez de espaço.

Também usamos printf '%4s' para à esquerda -pad a (x) com espaços. Nós poderíamos ter usado ${(l:4:):-"($((++c)))"} . Entretanto, uma diferença notável é que, se a string tiver mais de 4 caracteres, ${(l)} truncará a string, enquanto ela transbordaria com printf .

    
por 30.06.2016 / 16:41
5

O especificador de formato %s pode receber uma precisão ( %.20s , por exemplo) e, assim como quando você deseja gerar um valor flutuante com uma certa precisão (com %.4f , por exemplo), a saída será mais que muitos caracteres do argumento de string fornecido.

Portanto, crie uma string que contenha o nome da máquina e pontos suficientes para ficar sem pontos:

cnt=0
for hname in vboxhost ntp.stupi.se example.com nonexistant; do
   if ping -q -c 1  "$hname" >/dev/null 2>&1; then
       status="OK"
   else
       status="FAIL"
   fi

   printf "(%d) %.20s CONNECTION %s\n" \
       "$(( ++cnt ))" "$hname ...................." "$status"

done

Saída:

(1) vboxhost ........... CONNECTION OK
(2) ntp.stupi.se ....... CONNECTION OK
(3) example.com ........ CONNECTION OK
(4) nonexistant ........ CONNECTION FAIL
    
por 30.06.2016 / 17:07
2

Com coisas roubadas da resposta da @ choroba:

#!/bin/bash 
list=(linux643 linux72 google.com linux862 linux12 linux88 unix_machinetru64) 
C=1 
readonly TOTAL=50 
for M in "${list[@]}" 
do 
    DOTS=$(( TOTAL - ${#M} ))
    ping -q -c 1  "$M" &>/dev/null 

    if (($?)) ;  then 
        printf "(%d) %s" "$C" "$M" ; printf "%0.s." $(seq 1 $DOTS) ; printf " CONNECTION FAILED\n" 
    else 
        printf "(%d) %s" "$C" "$M" ; printf "%0.s." $(seq 1 $DOTS) ; printf " CONNECTION OK\n"  
    fi 
    ((C=C+1)) 
done 
    
por 30.06.2016 / 16:51
1

Eu faria isso com fping e awk . Infelizmente, awk ' printf não pode preencher com pontos, apenas com espaços ou zeros, então tenho que escrever uma função:

list=(kali surya indra ganesh durga hanuman nonexistent)

fping "${list[@]}" 2>&1 | 
  sort -k3 |
  awk -F'[: ]' 'BEGIN { fmt="(%02d) %s CONNECTION %s\n"};

       function dotpad(s,maxlen,     l,c,pads) {
         l = maxlen - length(s);
         pads = "";
         for (c=0;c<l;c++) {pads=pads"."};
         return s " " pads
       };

       /alive$/       { printf fmt, ++i, dotpad($1,19), "OK" };
       /unreachable$/ { printf fmt, ++i, dotpad($1,19), "FAIL" }
       /not known$/   { printf fmt, ++i, dotpad($1,19), "IMPOSSIBLE" } '
(01) durga .............. CONNECTION OK
(02) ganesh ............. CONNECTION OK
(03) indra .............. CONNECTION OK
(04) kali ............... CONNECTION OK
(05) nonexistent ........ CONNECTION IMPOSSIBLE
(06) hanuman ............ CONNECTION FAIL
(07) surya .............. CONNECTION FAIL

Estou usando números de dois dígitos zerados entre parênteses para que o formato não seja danificado se houver 10 a 99 hosts em $list (100+ ainda vai estragar tudo). A alternativa seria atrasar a impressão até um bloco END {} , e para o / regexp-matches / apenas inserir o nome do host em um dos três arrays, por exemplo ok , fail , unknown . ou apenas um array associativo (por exemplo, hosts[hostname]="OK" ). Então, você poderia contar o número de linhas e usá-las para decidir a largura do campo do contador de linhas.

Eu também decidi fazer a saída distinguir entre hosts desconhecidos ( CONNECTION IMPOSSIBLE ) e hosts inacessíveis ( CONNECTION FAIL ).

O sort -k3 é opcional, apenas agrupa a saída pelo resultado fping ("nome do host está ativo", "nome do host inacessível" ou "nome do host: Nome ou serviço desconhecido"). Sem o sort , os hosts desconhecidos sempre aparecerão primeiro na saída. Apenas sort sem o -k3 irá classificar por hostname.

    
por 03.07.2016 / 06:21