Saltar al contenido

Formato de número en BASH con separador de miles

Solución:

$ printf "%'.3fn" 12345678.901
12,345,678.901

tl; dr

  • Usar numfmt, si ÑU Las utilidades están disponibles, como en Linux de forma predeterminada:

    • numfmt --grouping 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
  • De lo contrario, usar printf con el ' bandera de campo envuelta en un función de shell ese conserva el número de posiciones decimales de entrada (no codifica el número de producción lugares decimales).

    • groupDigits 12343423455.23353 # -> 12,343,423,455.23353 in locale en_US
    • Consulte la parte inferior de esta respuesta para obtener la definición de groupDigits(), que también es compatible múltiple números de entrada.
  • Alternativas ad-hoc que involucran subcapas ese también conserva el número de posiciones decimales de entrada (asume que la marca decimal de entrada es . o ,):

    • Una variante modular, pero algo ineficiente que acepta el número de entrada a través de stdin (y por lo tanto también se puede utilizar con entrada de canalización):
      (n=$(</dev/stdin); f=${n#*[.,]}; printf "%'.${#f}fn" "$n") <<<12343423455.23353
    • Alternativa significativamente más rápida, pero menos modular, que utiliza variable intermedia $n: n=12343423455.23353; (f=${n#*[.,]} printf "%'.${#f}fn" "$n")
  • Alternativamente, considere el uso de mi Linux / macOS grp CLI (instalable con npm install -g grp-cli):

    • grp -n 12343423455.23353

En todos los casos hay advertencias; vea abajo.


La respuesta de Ignacio Vázquez-Abrams contiene el puntero crucial para usar con printf: los ' bandera de campo (siguiendo el %) formatea un número con el separador de miles de la configuración regional activa:

  • Tenga en cuenta que man printf (man 1 printf) no contiene esta información en sí: el utilidad / shell incorporado printf en última instancia llama al función de biblioteca printf(), y solo man 3 printf ofrece una imagen completa con respecto a los formatos compatibles.
  • Variables de entorno LC_NUMERIC e, indirectamente, LANG o LC_ALL controlar la configuración regional activa con respecto al formato de números.
  • Ambos numfmt y printf respetar la configuración regional activa, tanto con respecto al separador de miles como a la marca decimal (“punto decimal”).
  • Usando solo printf por sí mismo, como en la respuesta de Ignacio, requiere que usted Código difícil el número de producción lugares decimales, en lugar de conservar la cantidad de lugares decimales que tenga la entrada; es esta limitación la que groupDigits() a continuación supera.
  • printf "%'.<numDecPlaces>f" tiene una ventaja sobre numfmt --grouping, sin embargo:

    • numfmt solo acepta decimal números, mientras que printf‘s %f también acepta hexadecimal enteros (p. ej., 0x3e8) y números en decimal notación cientifica (p.ej, 1e3).

Advertencias

  • Locales sin agrupar: Algunas configuraciones regionales, en particular C y POSIX, por definición NO aplica agrupación, por lo que el uso de ' no tiene ningún efecto en ese evento.

  • Incoherencias de la configuración regional del mundo real entre plataformas:

    • (LC_ALL='de_DE.UTF-8'; printf "%'.1fn" 1000) # SHOULD yield: 1.000,0
    • Linux: rendimientos 1.000,0, como se esperaba.
    • macOS / BSD: Cede inesperadamente 1000,0 – NO agrupación (!).
  • Formato de número de entrada: Cuando pasa un número a numfmt o printf, eso:

    • no debe ya contiene agrupación de dígitos
    • debe ya utilizo el activo marca decimal de la configuración regional
    • Por ejemplo:
      • (LC_ALL='lt_LT.UTF-8'; printf "%'.1fn" 1000,1) # -> '1 000,1'
      • OK: el número de entrada no está agrupado y usa la marca decimal lituana (coma).
  • Portabilidad: POSIX no exigir los printf utilidad (a diferencia de la C printf() función de biblioteca) para admitir caracteres de formato de punto flotante como %f, dado que POSIX[-like] las conchas son solo números enteros; en la práctica, sin embargo, no conozco ningún shell / plataforma que no lo haga.

  • Redondeo de errores y desbordamiento:

    • Cuando usas numfmt y printf como se describe, se produce una conversión de ida y vuelta (cadena -> número -> cadena), que está sujeta a errores de redondeo; en otras palabras: reformatear con agrupación de dígitos puede llevar a un número diferente.
    • Usando carácter de formato f para emplear valores de coma flotante de doble precisión IEEE-754, solo hasta 15 dígitos significativos (los dígitos independientemente de la ubicación de la marca decimal) son garantizado para conservarse con precisión (aunque para números específicos puede funcionar con más dígitos). En la práctica, numfmt y ÑU printf puede manejar con precisión más que eso; vea abajo. Si alguien sabe cómo y por qué, hágamelo saber.
    • Con demasiados dígitos significativos o un valor demasiado grande, el el comportamiento difiere entre numfmt y printf en general, y Entre printf implementaciones en plataformas; por ejemplo:

numft:

[Fixed in coreutils 8.24, according to @pixelbeat] Comenzando con 20 dígitos significativos, el valor se desborda silenciosamente (!), Presumiblemente un error (a partir de GNU coreutils 8.23):

# 20 significant digits cause quiet overflow:
$ (fractPart=0000000000567890; num="1000.${fractPart}"; numfmt --grouping "$num")
-92.23372036854775807    # QUIET OVERFLOW

Por el contrario, un número demasiado grande lo hace generar un error por defecto.

printf:

Linux printf maneja hasta 20 dígitos significativos precisamente, mientras que la implementación de BSD / macOS está limitada a 17:

# Linux: 21 significant digits cause rounding error:
$  (fractPart=00000000005678901; num="1000.${fractPart}"; printf "%'.${#fractPart}fn" "$num")
1,000.00000000005678902  # ROUNDING ERROR

# BSD/macOS: 18 significant digits cause rounding error:
$  (fractPart=00000000005678; num="1000.${fractPart}"; printf "%'.${#fractPart}fn" "$num")
1,000.00000000005673  # ROUNDING ERROR

La versión de Linux nunca parece desbordarse, mientras que la versión BSD / macOS informa un error con números demasiado grandes.


Función de shell Bash groupDigits():

# SYNOPSIS
#   groupDigits num ...
# DESCRIPTION
#   Formats the specified number(s) according to the rules of the
#   current locale in terms of digit grouping (thousands separators).
#   Note that input numbers
#     - must not already be digit-grouped themselves,
#     - must use the *current* locale's decimal mark.
#   Numbers can be integers or floats.
#   Processing stops at the first number that can't be formatted, and a
#   non-zero exit code is returned.
# CAVEATS
#   - No input validation is performed.
#   - printf(1) is not guaranteed to support non-integer formats by POSIX,
#     though not doing so is rare these days.
#   - Round-trip number conversion is involved (string > double > string)
#     so rounding errors can occur.
# EXAMPLES
#   groupDigits 1000 # -> '1,000'
#   groupDigits 1000.5 # -> '1,000.5'
#   (LC_ALL=lt_LT.UTF-8; groupDigits 1000,5) # -> '1 000,5'
groupDigits() {
  local decimalMark fractPart
  decimalMark=$(printf "%.1f" 0); decimalMark=${decimalMark:1:1}
  for num; do
    fractPart=${num##*${decimalMark}}; [[ "$num" == "$fractPart" ]] && fractPart=""
    printf "%'.${#fractPart}fn" "$num" || return
  done
}
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *