Desenhando um histograma a partir de uma saída do comando bash

31

Eu tenho a seguinte saída:

2015/1/7    8
2015/1/8    49
2015/1/9    40
2015/1/10   337
2015/1/11   11
2015/1/12   3
2015/1/13   9
2015/1/14   102
2015/1/15   62
2015/1/16   10
2015/1/17   30
2015/1/18   30
2015/1/19   1
2015/1/20   3
2015/1/21   23
2015/1/22   12
2015/1/24   6
2015/1/25   3
2015/1/27   2
2015/1/28   16
2015/1/29   1
2015/2/1    12
2015/2/2    2
2015/2/3    1
2015/2/4    10
2015/2/5    13
2015/2/6    2
2015/2/9    2
2015/2/10   25
2015/2/11   1
2015/2/12   6
2015/2/13   12
2015/2/14   2
2015/2/16   8
2015/2/17   8
2015/2/20   1
2015/2/23   1
2015/2/27   1
2015/3/2    3
2015/3/3    2

E eu gostaria de desenhar um histograma

2015/1/7  ===
2015/1/8  ===========
2015/1/9  ==========
2015/1/10 ====================================================================
2015/1/11 ===
2015/1/11 =
...

Você sabe se existe um comando bash que me permita fazer isso?

Natim
fonte
11
bashplotlib é uma boa solução
Michael Mior
Esse é realmente um dos riscos de fornecer links, em vez de respostas independentes. Se a resposta SO excluída for útil, publique-a como resposta aqui.
Jeff Schaller

Respostas:

12

Tente isso em :

perl -lane 'print $F[0], "\t", "=" x ($F[1] / 5)' file

EXPLICAÇÕES:

  • -aé uma explícita split()na @Fmatriz, obtemos os valores com$F[n]
  • x é dizer ao perl para imprimir um caractere N vezes
  • ($F[1] / 5) : aqui obtemos o número e dividimos por 5 para obter uma saída bonita
Gilles Quenot
fonte
11
perl -lane 'print $F[0], "\t", $F[1], "\t", "=" x ($F[1] / 3 + 1)'Parece realmente grandes :) Obrigado
Natim
12

Em perl:

perl -pe 's/ (\d+)$/"="x$1/e' file
  • efaz com que a expressão seja avaliada, por isso sou =repetido usando o valor de $1(o número correspondente a (\d+)).
  • Você poderia fazer em "="x($1\/3)vez de "="x$1obter linhas mais curtas. (O /escapou, pois estamos no meio de um comando de substituição.)

In bash(inspirado nesta resposta do SO ):

while read d n 
do 
    printf "%s\t%${n}s\n" "$d" = | tr ' ' '=' 
done < test.txt
  • printfpreenche a segunda string usando espaços para obter uma largura de $n ( %${n}s) e substituo os espaços por =.
  • As colunas são delimitadas usando uma guia ( \t), mas você pode torná-la mais bonita canalizando para column -ts'\t'.
  • Você pode usar em $((n/3))vez de ${n}obter linhas mais curtas.

Outra versão:

unset IFS; printf "%s\t%*s\n" $(sed 's/$/ =/' test.txt) | tr ' ' =

A única desvantagem que vejo é que você precisará canalizar seda saída para algo se quiser reduzir, caso contrário, essa é a opção mais limpa. Se houver uma chance de o seu arquivo de entrada conter um de [?*vocês, o comando w / set -f;.

muru
fonte
2
Bravo por mostrar uma solução shell também. Sua solução Perl também é muito limpa.
pintos
@mikeserv Wonderful! Eu sempre esqueço %*s, apesar de ter sido o primeiro printftruque relacionado que aprendi na programação C.
muru 7/01/15
A printf(sed) | trversão não funciona aqui até onde eu sei.
Natim
@ Natim aqui estar onde?
Muru
limitações @mikeserv no comprimento do argumento, talvez?
Muru
6

Fácil com awk

awk '{$2=sprintf("%-*s", $2, ""); gsub(" ", "=", $2); printf("%-10s%s\n", $1, $2)}' file

2015/1/7 ========
2015/1/8 =================================================
2015/1/9 ========================================
..
..

Ou com minha linguagem de programação favorita

python3 -c 'import sys
for line in sys.stdin:
  data, width = line.split()
  print("{:<10}{:=<{width}}".format(data, "", width=width))' <file
iruvar
fonte
3

E se:

#! /bin/bash
histo="======================================================================+"

read datewd value

while [ -n "$datewd" ] ; do
   # Use a default width of 70 for the histogram
   echo -n "$datewd      "
   echo ${histo:0:$value}

   read datewd value
done

Qual produz:

~/bash $./histogram.sh < histdata.txt
2015/1/7    ========
2015/1/8    =================================================
2015/1/9    ========================================
2015/1/10   ======================================================================+
2015/1/11   ===========
2015/1/12   ===
2015/1/13   =========
2015/1/14   ======================================================================+
2015/1/15   ==============================================================
2015/1/16   ==========
2015/1/17   ==============================
2015/1/18   ==============================
2015/1/19   =
2015/1/20   ===
2015/1/21   =======================
2015/1/22   ============
2015/1/24   ======
2015/1/25   ===
2015/1/27   ==
2015/1/28   ================
2015/1/29   =
2015/2/1    ============
2015/2/2    ==
2015/2/3    =
2015/2/4    ==========
2015/2/5    =============
2015/2/6    ==
2015/2/9    ==
2015/2/10   =========================
2015/2/11   =
2015/2/12   ======
2015/2/13   ============
2015/2/14   ==
2015/2/16   ========
2015/2/17   ========
2015/2/20   =
2015/2/23   =
2015/2/27   =
2015/3/2    ===
2015/3/3    ==
~/bash $
Robert Nix
fonte
1

Isso me pareceu um divertido problema de linha de comando tradicional. Aqui está a minha bashsolução de script:

awk '{if (count[$1]){count[$1] += $2} else {count[$1] = $2}} \
        END{for (year in count) {print year, count[year];}}' data |
sed -e 's/\// /g' | sort -k1,1n -k2,2n -k3,3n |
awk '{printf("%d/%d/%d\t", $1,$2,$3); for (i=0;i<$4;++i) {printf("=")}; printf("\n");}'

O pequeno script acima pressupõe que os dados estão em um arquivo chamado "dados" imaginativamente.

Não estou muito feliz com a linha "execute-o com sed e classifique" - seria desnecessário se seu mês e dia do mês sempre tivessem 2 dígitos, mas é a vida.

Além disso, como uma nota histórica, os Unixes tradicionais costumavam vir com um utilitário de plotagem de linha de comando que podia fazer gráficos e plotagens ASCII bastante feios. Não me lembro do nome, mas parece que os plotutils do GNU substituem o antigo utilitário tradicional.

Bruce Ediger
fonte
Não deveria ser if ($1 in count) ...?
muru 6/01/15
11
@ muru - parece funcionar de qualquer maneira. No entanto, encontrei um erro de digitação na cláusula "else". Obrigado.
Bruce Ediger
1

Bom exercício aqui. Eu joguei os dados em um arquivo chamado "data" porque sou muito imaginativa.

Bem, você pediu no bash ... aqui está no bash puro.

cat data | while read date i; do printf "%-10s " $date; for x in $(seq 1 $i); do echo -n "="; done; echo; done

awk é uma opção melhor.

awk '{ s=" ";while ($2-->0) s=s"=";printf "%-10s %s\n",$1,s }' data
Nomes falsos
fonte
Você pode canalizar os dados através do awk em vez de usar um arquivo?
Natim
Sim, é a mesma coisa de qualquer maneira. Basta adicionar um "dados do gato |" no começo, como eu tinha para os bits do bash ou um "<data" no final. Ou você pode até ter a parte awk sem um arquivo especificado, colar os dados e pressionar Ctrl-D no final. A especificação do arquivo trata apenas o arquivo como stdin, e eu não queria continuar copiando e colando o arquivo de dados porque sou preguiçoso.
Falsenames
11
Na verdade, eu apenas reli a pergunta enquanto vinculava isso a um colega de trabalho ... você disse que tinha "saída", não um arquivo de dados. Assim, você pode executar o que quer que esteja criando esse relatório, canalizá-lo para o awk e pronto. Canaliza apenas a saída direta do último comando como a fonte de entrada para o próximo comando.
Falsenames
0

Tente o seguinte:

while read value count; do
    printf '%s:\t%s\n' "${value}" "$(printf "%${count}s" | tr ' ' '=')"
done <path/to/my-output

A única parte complicada é a construção do bar. Eu faço isso aqui delegando printfe trgostando desta resposta SO .

Como bônus, é shcompatível com POSIX .

Referências:

rubicks
fonte