Agregar separador de miles en un número

37

En pitón

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

Para dividir un número por trillizos, por ejemplo:

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

¿Cómo hacer lo mismo con bash / awk?

usuario2496
fuente

Respuestas:

30

Con sed:

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

(¡Tenga en cuenta que esto solo funciona para exactamente 9 dígitos!)

o esto con sed:

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

Con printf:

$ LC_NUMERIC=en_US printf "%'.f\n" 123456789
123,456,789
slm
fuente
También estoy probando con awk, pero es agregar coma al finalecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil
ahora entiendo pero parece complejoecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil
1
Eso primero sedsolo funciona si el número tiene exactamente 9 dígitos. El printfno funciona en zsh. Por lo tanto, la segunda sedrespuesta es probablemente la mejor.
Patrick
1
@RahulPatil Eso solo funciona correctamente si el número de dígitos es múltiplo de 3. Pruebe con "12345678" y verá lo que quiero decir.
Patrick
1
Puede hacerlo echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(¡que evidentemente no siempre funciona en Linux! ?, pero funciona bien en AIX y Solaris)
Johan
51

bash's printfadmite prácticamente todo lo que puede hacer en la printffunción C

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

printf de coreutils hará lo mismo

/usr/bin/printf "%'d" 1234567   # => 1,234,567
Mikel
fuente
Esto ahora también es compatible con la zshpublicación actualizada aquí .
don_crissti
1
Estoy en bash 4.1.2 y no admite ... :(
msb
@msb Parece depender de su sistema vsnprintf. En un sistema GNU / Linux, glibc parece haberlo soportado desde al menos 1995.
Mikel
2
Tenga en cuenta que printf usa el separador de miles para su ubicación actual , que puede ser una coma, un punto o nada en absoluto. Puedes export LC_NUMERIC="en_US"hacerlo si quieres forzar comas.
medmunds
Obtenga una lista de configuraciones regionales compatibles con locale -a. Tuve que usaren_US.utf8
eludom
7

Puedes usar numfmt:

$ numfmt --grouping 123456789
123,456,789

O:

$ numfmt --g 123456789
123,456,789

Tenga en cuenta que numfmt no es una utilidad POSIX, es parte de los coreutils de GNU.

Steven Penny
fuente
1
Gracias por el consejo de "agrupación". En el segundo ejemplo (--g), ¿quiso escribir algo así -d, --groupingya que las dobles guiones necesitan opciones largas?
Saltando Bunny
--gfunciona bien para mí en lugar de --grouping, es decir, numfmt --g 1234567890y numfmt --grouping 1234567890hacer lo mismo. Es una pequeña utilidad muy útil.
Mattst
4
cat <<'EOF' |
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
EOF
perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;'

produce:

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

Esto se logra dividiendo la cadena de dígitos en 2 grupos, el grupo de la derecha con 3 dígitos, el grupo de la izquierda con lo que queda, pero al menos un dígito. Luego, todo se reemplaza por los 2 grupos, separados por una coma. Esto continúa hasta que la sustitución falla. Las opciones "wpe" son para la lista de errores, encierran la declaración dentro de un bucle con una impresión automática y toman el siguiente argumento como el "programa" perl (vea el comando perldoc perlrun para más detalles).

Mis mejores deseos ... salud, drl

drl
fuente
Gracias a anónimo por los comentarios. Incluso un voto negativo puede ser útil, pero solo si se explica: comente lo que vio que estaba mal. Gracias ... salud
drl
Creo que el voto negativo aquí es porque no explicaste lo que hace el comando. El OP solicitó una BASH/ AWKalternativa, por lo que es posible que no la haya usado PERLantes. En cualquier caso, lo mejor es explicar lo que hace el comando, especialmente para las frases sencillas.
AnthonyK
@AnthonyK: gracias por la explicación probable. Agregué comentarios para explicar brevemente cómo funciona. Creo que las soluciones alternativas a menudo son útiles, pero su punto sobre posiblemente no haber usado Perl se nota ... saludos
drl
Probé las sugerencias de sed y python en esta página. El script perl fue el único que funcionó para un archivo completo. El archivo se archivó con texto y números.
Mark
3

Con algunas awkimplementaciones:

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

123,456,789  

"%'"'"'d\n"es: "%(comilla simple) (comilla doble) (comilla simple) (comilla doble) (comilla simple) d \ n"

Eso usará el separador de miles configurado para su configuración regional (generalmente ,en configuraciones regionales en inglés, espacio en francés, .en español / alemán ...). Lo mismo que devuelto porlocale thousands_sep

Ben
fuente
2

Un caso de uso común para mí es modificar la salida de una tubería de comando para que los números decimales se impriman con miles de separadores. En lugar de escribir una función o script, prefiero usar una técnica que pueda personalizar sobre la marcha para cualquier salida de una tubería de Unix.

He encontrado printf(proporcionado por Awk) la forma más flexible y memorable de lograr esto. POSIX especifica el carácter de apóstrofe / comilla simple como un modificador para formatear números decimales y tiene la ventaja de que es compatible con la configuración regional, por lo que no se limita al uso de caracteres de coma.

Al ejecutar comandos Awk desde un shell de Unix, puede haber dificultades para ingresar un carácter de comillas simples dentro de una cadena delimitada por comillas simples (para evitar la expansión del shell de variables posicionales, por ejemplo $1). En este caso, encuentro que la forma más legible y confiable de ingresar el carácter de comillas simples es ingresarlo como una secuencia de escape octal (comenzando por \0).

Ejemplo:

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

Salida simulada de una tubería que muestra qué directorios están utilizando la mayor cantidad de espacio en 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

Otras soluciones se enumeran en Cómo escapar de una cita simple dentro de awk .

Nota: como se advirtió en Print a Single Quote , se recomienda evitar el uso de secuencias de escape hexadecimales, ya que no funcionan de manera confiable en diferentes sistemas.

Anthony G - justicia para Monica
fuente
1
De todas las respuestas basadas en awk enumeradas aquí, esta es sin duda la más graciosa (en mi humilde opinión). No es necesario hackear una cita con otras citas como en otras soluciones.
TSJNachos117
Gracias @ TSJNachos117 La parte más difícil es recordar que la codificación octal para el carácter de apóstrofe es \047.
Anthony G - justicia para Monica
2

awky bashtener buenas soluciones integradas, basadas en printf, como se describe en las otras respuestas. Pero primero, sed.

Para sed, tenemos que hacerlo "manualmente". La regla general es que si tiene cuatro dígitos consecutivos, seguidos de un no dígito (o final de línea), se debe insertar una coma entre el primer y el segundo dígito.

Por ejemplo,

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

imprimirá

12345,678

Obviamente, debemos seguir repitiendo el proceso para seguir agregando suficientes comas.

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

En sed, el tcomando especifica una etiqueta a la que se saltará si el último s///comando fue exitoso. Por lo tanto, defino una etiqueta con :restart, para que salte hacia atrás.

Aquí hay una demostración de bash (en ideone ) que funciona con cualquier 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
fuente
1
$ echo 1232323 | awk '{printf(fmt,$1)}' fmt="%'6.3f\n"
12,32,323.000
Akshay Hegde
fuente
1

Si está buscando números GRANDES, no pude hacer que las soluciones anteriores funcionen. Por ejemplo, obtengamos un número realmente grande:

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

Tenga en cuenta que necesito treliminar el resultado de nueva línea de barra diagonal inversa de bc. Este número es demasiado grande para tratarlo como flotante o número de bit fijo en awk, y ni siquiera quiero construir una expresión regular lo suficientemente grande como para dar cuenta de todos los dígitos en sed. Más bien, puedo revertirlo y poner comas entre grupos de tres dígitos, luego revertirlo:

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
fuente
2
Buena respuesta. Sin embargo, nunca he encontrado un problema al usar números grandes con Awk. Probé su ejemplo en varias distribuciones basadas en Red Hat y Debian, pero en todos los casos, Awk no tuvo problemas con la gran cantidad. Pensé un poco más al respecto y se me ocurrió que todos los sistemas en los que había experimentado eran de 64 bits (incluso una VM muy antigua que ejecutaba RHEL 5 no compatible). No fue hasta que probé un viejo lap-top ejecutando un sistema operativo de 32 bits que yo era capaz de reproducir el problema: awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - justicia 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
usuario2796674
fuente
Eso agrega una coma principal espuria si el número de dígitos en el número es un múltiplo de 3.
Stéphane Chazelas
@ StéphaneChazelas: puede tomar la salida de ese último comando rev y canalizarlo a sed 's/^,//g'.
TSJNachos117
0

También quería tener la parte después del separador decimal correctamente separado / espaciado, por lo tanto, escribí este script sed que usa algunas variables de shell para ajustar las preferencias regionales y personales. También tiene en cuenta diferentes convenciones para el 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
fuente
0

Una solución A bash/ awk(según lo solicitado) que funciona independientemente de la longitud del número y se utiliza ,independientemente de la configuración de la thousands_sepconfiguración regional , y dondequiera que estén los números en la entrada y evita agregar el separador de miles después en 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}'

Da:

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

Con awkimplementaciones como mawkesa no son compatibles con los operadores de expresiones regulares de intervalo, cambie la expresión regular a/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Stéphane Chazelas
fuente