Aggiungi migliaia di separatori in un numero


37

In pitone

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

Per dividere un numero per terzine, ad esempio:

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

Come fare lo stesso con bash / awk?

Risposte:


30

Con sed:

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

(Nota che funziona solo per esattamente 9 cifre!)

o questo 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

Sto anche provando con awk, ma è aggiungere virgola alla fineecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil,

ora capisco ma sembra complessoecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil,

1
Che prima sedfunziona solo se il numero è esattamente di 9 cifre. Il printfnon funziona su zsh. Quindi la seconda sedrisposta è probabilmente la migliore.
Patrick,

1
@RahulPatil Funziona correttamente solo se il numero di cifre è un multiplo di 3. Prova con "12345678" e vedrai cosa intendo.
Patrick,

1
Puoi farlo echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(che evidentemente non funziona sempre su Linux!?, Ma funziona bene su AIX e Solaris)
Johan,

51

bash's printfsupporti praticamente tutto quello che si può fare nella printffunzione C

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

printf da coreutils farà lo stesso

/usr/bin/printf "%'d" 1234567   # => 1,234,567

Anche questo è supportato in zsh, post aggiornato qui .
don_crissti,

1
Sono su bash 4.1.2 e non supporta ... :(
msb

@msb Sembra dipendere da quello del tuo sistema vsnprintf. Su un sistema GNU / Linux, glibc sembra averlo supportato almeno dal 1995.
Mikel

2
Nota printf utilizza il separatore delle migliaia per le impostazioni internazionali correnti , che potrebbe essere una virgola, un punto o niente. Puoi export LC_NUMERIC="en_US"se vuoi forzare le virgole.
medmunds

Ottieni l'elenco delle impostazioni internazionali supportate con locale -a. Ho dovuto usareen_US.utf8
eludom

7

Puoi usare numfmt:

$ numfmt --grouping 123456789
123,456,789

O:

$ numfmt --g 123456789
123,456,789

Nota che numfmt non è un'utilità POSIX, fa parte dei coreutils GNU.


1
Grazie per il suggerimento "raggruppamento". Nel secondo esempio (--g), intendevi scrivere qualcosa di simile -d, --groupingpoiché le doppie sillabazioni richiedono lunghe opzioni?
Hopping Bunny il

--gfunziona bene per me invece di --grouping, cioè numfmt --g 1234567890e numfmt --grouping 1234567890fare la stessa cosa. È una piccola utility molto utile.
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

Ciò si ottiene dividendo la stringa di cifre in 2 gruppi, il gruppo di destra con 3 cifre, il gruppo di sinistra con tutto ciò che rimane, ma almeno una cifra. Quindi tutto viene sostituito dai 2 gruppi, separati da una virgola. Questo continua fino a quando la sostituzione fallisce. Le opzioni "wpe" sono per la lista degli errori, racchiudono l'istruzione all'interno di un ciclo con una stampa automatica e prendono l'argomento successivo come il "programma" perl (vedi il comando perldoc perlrun per i dettagli).

I migliori auguri ... evviva, drl


Grazie ad anonimo per il feedback. Anche un downvote può essere utile, ma solo se spiegato - per favore commenta ciò che hai visto che era sbagliato. Grazie ... evviva
drl

Penso che il downvote qui sia perché non hai spiegato cosa fa il comando. L'OP ha chiesto una BASH/ AWKalternativa, quindi potrebbe non averlo usato PERLprima. In ogni caso, è meglio spiegare cosa fa il comando, specialmente per le battute.
AnthonyK,

@AnthonyK - grazie per la probabile spiegazione. Ho aggiunto commenti per spiegare brevemente come funziona. Penso che le soluzioni alternative siano spesso utili, ma il tuo punto di vista sul fatto che forse non hai usato il perl è notato ... evviva
drl

Ho provato i suggerimenti sed e python su questa pagina. Lo script perl era l'unico che ha funzionato per un intero file. Il file è stato archiviato con testo e numeri.
Segna il

3

Con alcune awkimplementazioni:

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

123,456,789  

"%'"'"'d\n"è: "%(virgolette singole) (virgolette doppie) (virgolette singole) (virgolette doppie) (virgolette singole) d \ n"

Che utilizzerà il separatore di migliaia configurato per la tua locale (in genere ,in locali inglesi, spazio in francese, .in spagnolo / tedesco ...). Come restituito dalocale thousands_sep


2

Un caso d'uso comune per me è modificare l'output di una pipeline di comandi in modo che i numeri decimali vengano stampati con migliaia di separatori. Piuttosto che scrivere una funzione o uno script, preferisco usare una tecnica che posso personalizzare al volo per qualsiasi output da una pipeline Unix.

Ho trovato printf(fornito da Awk) il modo più flessibile e memorabile per ottenere questo risultato. L'apostrofo / virgoletta singola è specificato da POSIX come un modificatore per formattare i numeri decimali e ha il vantaggio di essere a conoscenza delle impostazioni locali, quindi non è limitato all'utilizzo dei caratteri virgola.

Quando si eseguono i comandi Awk da una shell Unix, possono esserci difficoltà a inserire un carattere a virgoletta singola all'interno di una stringa delimitata da virgolette singole (per evitare l'espansione della shell di variabili posizionali, ad es $1.). In questo caso, trovo che il modo più leggibile e affidabile per inserire il carattere a virgoletta singola sia immetterlo come una sequenza di escape ottale (che inizia con \0).

Esempio:

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

Output simulato di una pipeline che mostra quali directory utilizzano la maggior parte dello spazio su 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

Altre soluzioni sono elencate in Come sfuggire a una singola citazione in awk .

Nota: come indicato in Print a Single Quote , si consiglia di evitare l'uso di sequenze di escape esadecimali in quanto non funzionano in modo affidabile su sistemi diversi.


1
Di tutte le risposte basate su awk elencate qui, questa è sicuramente la più aggraziata (IMHO). Non è necessario hackerare una citazione con altre citazioni come in altre soluzioni.
TSJNachos117,

Grazie @ TSJNachos117 La parte più difficile è ricordare che la codifica ottale per l'apostrofo è \047.
Anthony G - giustizia per Monica il

2

awke bashavere buone soluzioni integrate, basate su printf, come descritto nelle altre risposte. Ma prima, sed.

Per sed, dobbiamo farlo "manualmente". La regola generale è che se si dispone di quattro cifre consecutive, seguite da una non cifra (o fine riga), è necessario inserire una virgola tra la prima e la seconda cifra.

Per esempio,

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

stamperà

12345,678

Ovviamente dobbiamo quindi continuare a ripetere il processo, al fine di continuare ad aggiungere abbastanza virgole.

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

In sed, il tcomando specifica un'etichetta a cui verrà eseguito il salto se l'ultimo s///comando ha avuto esito positivo. Definisco quindi un'etichetta con :restart, in modo che salti indietro.

Ecco una demo di bash (su ideone ) che funziona con qualsiasi numero di cifre:

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

1
$ echo 1232323 | awk '{printf(fmt,$1)}' fmt="%'6.3f\n"
12,32,323.000

1

Se stai guardando i GRANDI numeri, non sono riuscito a far funzionare le soluzioni di cui sopra. Ad esempio, otteniamo un numero davvero grande:

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

Nota Ho bisogno trdi rimuovere l'output newline backslash da bc. Questo numero è troppo grande per essere considerato un float o un numero di bit fisso in awk, e non voglio nemmeno creare una regexp abbastanza grande da tenere conto di tutte le cifre in sed. Piuttosto, posso capovolgerlo e inserire le virgole tra gruppi di tre cifre, quindi annullarlo:

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


2
Buona risposta. Tuttavia, non ho mai riscontrato problemi nell'uso di numeri grandi con Awk. Ho provato il tuo esempio su diverse distribuzioni basate su Red Hat e Debian, ma in ogni caso Awk non ha avuto problemi con il numero elevato. Ci ho pensato un po 'di più e mi è venuto in mente che tutti i sistemi su cui avevo sperimentato erano a 64 bit (anche una VM molto vecchia con RHEL 5 non supportato). Non è stato fino a quando ho provato un vecchio lap-top in esecuzione un sistema operativo a 32 bit che ero in grado di replicare il problema: awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - giustizia per Monica l'

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

Ciò aggiunge una virgola iniziale spuria se il numero di cifre nel numero è un multiplo di 3.
Stéphane Chazelas

@ StéphaneChazelas: potresti prendere l'output dell'ultimo comando rev e reindirizzarlo sed 's/^,//g'.
TSJNachos117,

0

Volevo anche che la parte dopo il separatore decimale fosse correttamente separata / spaziata, quindi ho scritto questo sed-script che utilizza alcune variabili shell per adattarsi alle preferenze regionali e personali. Tiene inoltre conto delle diverse convenzioni per il numero di cifre raggruppate insieme :

#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'
}

0

Una soluzione bash/ awk(come richiesto) che funziona indipendentemente dalla lunghezza del numero e utilizza ,indipendentemente thousands_sepdall'impostazione della locale e ovunque i numeri si trovino nell'input ed evita di aggiungere il separatore dei mille dopo 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

Con awkimplementazioni del genere mawknon supportano gli operatori regex di intervallo, modificare regexp in/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.