Conversão de multiplicador de unidade em um script de shell

3

Eu preciso converter tamanhos legíveis em humanos em bytes. Infelizmente, a função numfmt não está acessível para mim. Existe alguma outra alternativa? Eu preciso de uma função shell / bash para chamar de dentro do script sh.

1K deve ser convertido para 1000, 1M para 1000 000, etc.

    
por jaksky 13.02.2014 / 11:07

2 respostas

6

Se o Perl estiver disponível:

echo your_string | perl -ne '
    BEGIN{ %suffixes=( K => 3, M => 6, G => 9, T => 12, P => 15, E => 18 ); 
           $suffix_regex = join "|", keys %suffixes;
    }
    s/([0-9][0-9]*(?:\.[0-9]+)?)($suffix_regex)/$1e$suffixes{$2}/g;
    printf "%d\n", $_;
'

Como isso deve ser usado como um filtro de texto, é mais apropriado defini-lo como um script perl e entrada de canal nele. Você pode incorporá-lo dentro de uma função de shell adicionando o seguinte ao seu .bashrc (supondo que você use o Bash):

myconvert() {
    cat <<'EOF' >/dev/null
#!--perl-- -n
BEGIN{ %suffixes=( K => 3, M => 6, G => 9, T => 12, P => 15, E => 18 ); 
       $suffix_regex = join "|", keys %suffixes;
}
s/([0-9][0-9]*(?:\.[0-9]+)?)($suffix_regex)/$1e$suffixes{$2}/g;
printf "%d\n", $_;
__END__
EOF
   exec perl -x "/path/to/your/.bashrc"
}

Uma solução usando sed e bc :

myconvert(){
  sed '
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)K/*1000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)M/*1000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)G/*1000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)T/*1000000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)P/*1000000000000000/g;
      s/\([0-9][0-9]*\(\.[0-9]\+\)\?\)E/*1000000000000000000/g
  ' </dev/stdin | bc | sed 's/\..*$//' # Final sed to remove decimal point
}

Uso (o mesmo para as duas soluções):

$ echo '5.23K' | myconvert
5230
$ echo '6.27G' | myconvert
6270000000

Esta solução assume que sua entrada consiste apenas de strings no formato 5.23K (parte fracionária opcional), caso contrário, bc não saberá o que fazer com elas.

Nota

O one-liner Perl poderia ter sido inserido como está em uma função shell com < /dev/stdin como na solução sed . Isso não passou pela minha cabeça quando escrevi a primeira versão da resposta. Estou deixando o truque perl -x para o caso de poder beneficiar outra pessoa.

    
por 13.02.2014 / 11:30
4

Com gawk , você poderia fazer:

convert() {
  gawk --use-lc-numeric -v RS='[KMGTPE]' '
    match($0, /[0-9]*['"$(locale decimal_point)"']?[0-9]+$/) {
      $0 = substr($0, 1, RSTART-1) sprintf("%'\''.17g", \
        substr($0,RSTART) * (10**(index(RS,RT)*3-3)))
      RT=""
    }{printf "%s", $0 RT}'
}

Então:

$ echo "You owe me £1.23M" | convert
You owe me £1,230,000
$ export LC_ALL=fr_FR.UTF-8
$ echo "Tu me dois 1,34M€" | convert
Tu me dois 1 340 000€
$ echo "Sie schulden mir 1,34M€" | LC_ALL=de_DE.UTF-8 convert
Sie schulden mir 1.340.000€

Se você não precisa se preocupar com decimais ou milhares de separadores (sua pergunta não está clara sobre isso como você usou um separador de milhar de estilo francês), e use . como separador decimal e sem separador de milhar, poderia fazer:

convert() {
  perl -pe 's/\d*\.?\d+([KMGTPE])/$&*10**(index(KMGTPE,$1)*3+3)/ge'
}

Se preferir que 112334E seja exibido como 112334000000000000000000 em vez de 1.12334e+23 , você pode adicionar a opção -Mbignum a perl acima.

    
por 13.02.2014 / 15:57