Adicionar separador de milhares em um número

37

Em python

 re.sub(r"(?<=.)(?=(?:...)+$)", ",", stroke ) 

Para dividir um número por trigêmeos, por exemplo:

 echo 123456789 | python -c 'import sys;import re; print re.sub(r"(?<=.)(?=(?:...)+$)", ",",  sys.stdin.read());'
 123,456,789

Como fazer o mesmo com o bash / awk?

user2496
fonte

Respostas:

29

Com sed:

$ echo "123456789" | sed 's/\([[:digit:]]\{3\}\)\([[:digit:]]\{3\}\)\([[:digit:]]\{3\}\)/\1,\2,\3/g'
123,456,789

(Observe que isso funciona apenas para exatamente 9 dígitos!)

ou isso com sed:

$ echo "123456789" | sed ':a;s/\B[0-9]\{3\}\>/,&/;ta'
123,456,789

Com printf:

$ LC_NUMERIC=en_US printf "%'.f\n" 123456789
123,456,789
slm
fonte
Eu também estou tentando com o awk, mas é adicionar vírgula no finalecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil
agora eu recebo, mas é parece complexaecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil
11
Isso primeiro sedsó funciona se o número tiver exatamente 9 dígitos. O printfnão funciona no zsh. Portanto, a segunda sedresposta é provavelmente a melhor.
660 Patrick Patrick
11
@RahulPatil Isso só funciona corretamente se o número de dígitos for múltiplo de 3. Tente com "12345678" e você verá o que quero dizer.
226 Patrick Patrick
11
Você pode fazer echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(o que, evidentemente, nem sempre funciona no Linux, mas funciona bem no AIX e Solaris!?)
Johan
51

bash's printfsuportes praticamente tudo que você pode fazer na printffunção C

type printf           # => printf is a shell builtin
printf "%'d" 123456   # => 123,456

printf de coreutils fará o mesmo

/usr/bin/printf "%'d" 1234567   # => 1,234,567
Mikel
fonte
Agora isso também é suportado zsh, post atualizado aqui .
don_crissti
11
Estou no bash 4.1.2 e ele não suporta ... :(
msb 31/01
@msb Parece depender do seu sistema vsnprintf. Em um sistema GNU / Linux, o glibc parece suportá-lo desde, pelo menos, 1995.
Mikel
2
Nota printf usa o separador de milhares para o seu código do idioma atual , que pode ser vírgula, ponto ou nada. Você pode, export LC_NUMERIC="en_US"se quiser forçar vírgulas.
medmunds 27/03
Obter lista de códigos de idioma suportados com locale -a. Eu tive que usaren_US.utf8
eludom
7

Você pode usar numfmt:

$ numfmt --grouping 123456789
123,456,789

Ou:

$ numfmt --g 123456789
123,456,789

Observe que o numfmt não é um utilitário POSIX, ele faz parte do GNU coreutils.

Steven Penny
fonte
11
Obrigado pela dica "agrupamento". No segundo exemplo (--g), você quis escrever algo como, -d, --groupinguma vez que as hifenizações duplas precisam de opções longas?
Hopping Bunny
--gfunciona bem para mim em vez de --grouping, ou seja, numfmt --g 1234567890e numfmt --grouping 1234567890faz a mesma coisa. É um pequeno utilitário muito útil.
mattst 13/10
4
cat <<'EOF' |
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
EOF
perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;'

produz:

13,407,807,929,942,597,099,574,024,998,205,846,127,479,365,820,592,393,377,723,561,443,721,764,030,073,546,976,801,874,298,166,903,427,690,031,858,186,486,050,853,753,882,811,946,569,946,433,649,006,084,096

Isso é feito dividindo a sequência de dígitos em 2 grupos, o grupo da direita com 3 dígitos, o grupo da esquerda com o que resta, mas com pelo menos um dígito. Então tudo é substituído pelos 2 grupos, separados por vírgula. Isso continua até a substituição falhar. As opções "wpe" são para listagem de erros, incluem a declaração dentro de um loop com uma impressão automática e usam o próximo argumento como o "programa" perl (consulte o comando perldoc perlrun para obter detalhes).

Muitas felicidades ... felicidades, drl

drl
fonte
Obrigado anônimo pelo feedback. Mesmo um voto negativo pode ser útil, mas apenas se explicado - por favor, comente o que você viu que estava errado. Obrigado ... felicidades
drl
Acho que o voto negativo aqui é porque você não explicou o que o comando faz. O OP pediu uma BASH/ AWKalternativa para que ele não pode ter usado PERLantes. De qualquer forma, é melhor explicar o que o comando faz - especialmente para as frases de uso único.
AnthonyK
@ AnthonyK - obrigado pela explicação provável. Adicionei comentários para explicar brevemente como funciona. Eu acho que soluções alternativas são muitas vezes útil, mas o seu ponto sobre a possibilidade de não ter perl usada é conhecida ... aplausos
drl
Eu tentei as sugestões sed e python nesta página. O script perl foi o único que funcionou para um arquivo inteiro. O arquivo foi arquivado com texto e números.
Mark
3

Com algumas awkimplementações:

echo "123456789" | awk '{ printf("%'"'"'d\n",$1); }'  

123,456,789  

"%'"'"'d\n" é: "% (aspas simples) (aspas duplas) (aspas simples) (aspas duplas) (aspas simples) d \ n"

Isso usará o separador de milhar configurado para o seu código do idioma (normalmente ,em locais do inglês, espaço em francês, .em espanhol / alemão ...). O mesmo que retornado porlocale thousands_sep

Ben
fonte
2

Um caso de uso comum para mim é modificar a saída de um pipeline de comandos para que os números decimais sejam impressos com mil separadores. Em vez de escrever uma função ou script, prefiro usar uma técnica que eu possa personalizar rapidamente para qualquer saída de um pipeline Unix.

Eu descobri printf(fornecido pela Awk) a maneira mais flexível e memorável de conseguir isso. O caractere de apóstrofo / aspas simples é especificado pelo POSIX como um modificador para formatar números decimais e tem a vantagem de reconhecer o código de idioma e não se restringir ao uso de caracteres de vírgula.

Ao executar comandos do Awk a partir de um shell Unix, pode haver dificuldades para inserir um caractere de aspas simples dentro de uma string delimitada por aspas simples (para evitar a expansão de variáveis ​​posicionais, por exemplo, shell $1). Nesse caso, acho que a maneira mais legível e confiável de inserir o caractere de aspas simples é inseri-lo como uma sequência de escape octal (começando com \0).

Exemplo:

printf "first 1000\nsecond 10000000\n" |
  awk '{printf "%9s: %11\047d\n", $1, $2}'
  first:       1,000
 second:  10,000,000

Saída simulada de um pipeline mostrando quais diretórios estão usando mais espaço em disco:

printf "7654321 /home/export\n110384 /home/incoming\n" |
  awk '{printf "%22s: %9\047d\n", $2, $1}'
  /home/export: 7,654,321
/home/incoming:   110,384

Outras soluções estão listadas em Como escapar de uma única citação no awk .

Nota: conforme advertido em Imprimir uma cotação , é recomendável evitar o uso de seqüências de escape hexadecimais, pois elas não funcionam de maneira confiável em diferentes sistemas.

Anthony G - justiça para Monica
fonte
11
De todas as respostas baseadas em awk listadas aqui, essa é certamente a mais graciosa (IMHO). Não é necessário invadir uma citação com outras citações, como em outras soluções.
TSJNachos117 03/04
Obrigado @ TSJNachos117 A parte mais difícil é lembrar que a codificação octal para o caractere de apóstrofo é \047.
Anthony G - justice for Monica
2

awke bashtenha boas soluções internas, com base em printf, conforme descrito nas outras respostas. Mas primeiro sed.

Pois sed, precisamos fazer isso "manualmente". A regra geral é que, se você tiver quatro dígitos consecutivos, seguidos por um não dígito (ou final de linha), uma vírgula deverá ser inserida entre o primeiro e o segundo dígito.

Por exemplo,

echo 12345678 | sed -re 's/([0-9])([0-9]{3})($|[^0-9])/\1,\2\3/'

irá imprimir

12345,678

Obviamente, precisamos continuar repetindo o processo, para continuar adicionando vírgulas suficientes.

sed -re ' :restart ; s/([0-9])([0-9]{3})($|[^0-9])/\1,\2\3/ ; t restart '

Em sed, o tcomando especifica um rótulo que será saltado para se o último s///comando for bem-sucedido. Por isso, defino um rótulo com :restart, para que ele salte para trás.

Aqui está uma demonstração do bash (em ideone ) que funciona com qualquer número de dígitos:

function thousands {
    sed -re ' :restart ; s/([0-9])([0-9]{3})($|[^0-9])/\1,\2\3/ ; t restart '
}                                                 
echo 12 | thousands
echo 1234 | thousands
echo 123456 | thousands
echo 1234567 | thousands
echo 123456789 | thousands
echo 1234567890 | thousands
Aaron McDaid
fonte
1
$ echo 1232323 | awk '{printf(fmt,$1)}' fmt="%'6.3f\n"
12,32,323.000
Akshay Hegde
fonte
1

Se você está procurando números grandes, não consegui fazer as soluções acima funcionarem. Por exemplo, vamos obter um número realmente grande:

$ echo 2^512 |bc -l|tr -d -c [0-9] 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096

Nota: preciso trremover a saída de nova linha com barra invertida do bc. Esse número é muito grande para ser tratado como um número flutuante ou de bit fixo no awk, e eu nem quero criar uma regexp grande o suficiente para dar conta de todos os dígitos no sed. Em vez disso, posso revertê-lo e colocar vírgulas entre grupos de três dígitos e depois revertê-lo:

echo 2^512 |bc -l|tr -d -c [0-9] |rev |sed -e 's/\([0-9][0-9][0-9]\)/\1,/g' |rev 13,407,807,929,942,597,099,574,024,998,205,846,127,479,365,820,592,393,377,723,561,443,721,764,030,073,546,976,801,874,298,166,903,427,690,031,858,186,486,050,853,753,882,811,946,569,946,433,649,006,084,096

Michael Benedict
fonte
2
Boa resposta. No entanto, nunca encontrei um problema ao usar grandes números com o Awk. Eu tentei seu exemplo em várias distribuições baseadas no Red Hat e no Debian, mas em todos os casos, o Awk não teve nenhum problema com o grande número. Pensei um pouco mais sobre isso e me ocorreu que todos os sistemas em que eu havia experimentado eram de 64 bits (mesmo uma VM muito antiga executando o RHEL 5 sem suporte). Não foi até que eu testei um lap-top velho executando um sistema operacional de 32 bits que eu era capaz de replicar o problema: awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - justiça para Monica
1
a="13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096"

echo "$a" | rev | sed "s#[[:digit:]]\{3\}#&,#g" | rev

13,407,807,929,942,597,099,574,024,998,205,846,127,479,365,820,592,393,377,723,561,443,721,764,030,073,546,976,801,874,298,166,903,427,690,031,858,186,486,050,853,753,882,811,946,569,946,433,649,006,084,096
user2796674
fonte
Isso acrescenta uma vírgula líder espúria se o número de dígitos do número é um múltiplo de 3.
Stéphane Chazelas
@ StéphaneChazelas: Você pode pegar a saída desse último comando rev e canalizá-lo para sed 's/^,//g'.
TSJNachos117 03/04
0

Eu também queria que a parte após o separador decimal fosse separada / espaçada corretamente, por isso escrevi esse script sed que usa algumas variáveis ​​de shell para se ajustar às preferências regionais e pessoais. Também leva em consideração convenções diferentes para o número de dígitos agrupados :

#DECIMALSEP='.' # usa                                                                                                               
DECIMALSEP=','  # europe

#THOUSSEP=',' # usa
#THOUSSEP='.' # europe
#THOUSSEP='_' # underscore
#THOUSSEP=' ' # space
THOUSSEP=' '  # thinspace

# group before decimal separator
#GROUPBEFDS=4   # china
GROUPBEFDS=3    # europe and usa

# group after decimal separator
#GROUPAFTDS=5   # used by many publications 
GROUPAFTDS=3


function digitgrouping {
  sed -e '
    s%\([0-9'"$DECIMALSEP"']\+\)'"$THOUSSEP"'%\1__HIDETHOUSSEP__%g
    :restartA ; s%\([0-9]\)\([0-9]\{'"$GROUPBEFDS"'\}\)\(['"$DECIMALSEP$THOUSSEP"']\)%\1'"$THOUSSEP"'\2\3% ; t restartA
    :restartB ; s%\('"$DECIMALSEP"'\([0-9]\{'"$GROUPAFTDS"'\}\'"$THOUSSEP"'\)*\)\([0-9]\{'"$GROUPAFTDS"'\}\)\([0-9]\)%\1\3'"$THOUSSEP"'\4% ; t restartB
    :restartC ; s%\([^'"$DECIMALSEP"'][0-9]\+\)\([0-9]\{'"$GROUPBEFDS"'\}\)\($\|[^0-9]\)%\1'"$THOUSSEP"'\2\3% ; t restartC
    s%__HIDETHOUSSEP__%\'"$THOUSSEP"'%g'
}
erik
fonte
0

Uma solução bash/ awk(conforme solicitado) que funciona independentemente do tamanho do número e usa ,independentemente da thousands_sepconfiguração da localidade , e onde quer que os números estejam na entrada e evita adicionar o separador de milhar depois em 1.12345:

echo not number 123456789012345678901234567890 1234.56789 |
  awk '{while (match($0, /(^|[^.0123456789])[0123456789]{4,}/))
        $0 = substr($0, 1, RSTART+RLENGTH-4) "," substr($0, RSTART+RLENGTH-3)
        print}'

Dá:

not number 123,456,789,012,345,678,901,234,567,890 1,234.56789

Em awkimplementações como mawkessa, não há suporte para os operadores de regex com intervalo, altere o regexp para/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Stéphane Chazelas
fonte