Dodaj separator tysięcy w liczbie

37

W python

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

Aby podzielić liczbę na trojaczki, np .:

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

Jak zrobić to samo z bash / awk?

użytkownik2496
źródło

Odpowiedzi:

29

Z sed:

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

(Uwaga: działa to tylko dla dokładnie 9 cyfr!)

lub to z sed:

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

Z printf:

$ LC_NUMERIC=en_US printf "%'.f\n" 123456789
123,456,789
slm
źródło
Próbuję też z awk, ale w końcu dodaje przecinekecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil
teraz rozumiem, ale wydaje się to skomplikowaneecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil
1
Ten pierwszy seddziała tylko wtedy, gdy liczba składa się dokładnie z 9 cyfr. printfNie działa na zsh. Zatem druga sedodpowiedź jest prawdopodobnie najlepsza.
Patrick
1
@RahulPatil Działa to poprawnie tylko wtedy, gdy liczba cyfr jest wielokrotnością 3. Spróbuj użyć „12345678”, a zobaczysz, co mam na myśli.
Patrick
1
Możesz to zrobić echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(co najwyraźniej nie zawsze działa w systemie Linux!?, Ale działa dobrze w systemach AIX i Solaris)
Johan
51

bash„s printfobsługuje prawie wszystko, co można zrobić w printffunkcji C

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

printf od coreutils zrobi to samo

/usr/bin/printf "%'d" 1234567   # => 1,234,567
Mikel
źródło
To jest teraz obsługiwane zshrównież w zaktualizowanym poście tutaj .
don_crissti
1
Jestem na bash 4.1.2 i to nie obsługuje ... :(
msb
@msb Wygląda na to, że zależy od twojego systemu vsnprintf. W systemie GNU / Linux wydaje się, że glibc obsługuje go od co najmniej 1995 roku.
Mikel
2
Uwaga printf używa separatora tysięcy dla twoich bieżących ustawień regionalnych , które mogą być przecinkiem, kropką lub niczym. Możesz, export LC_NUMERIC="en_US"jeśli chcesz wymusić przecinki.
medmunds
Uzyskaj listę obsługiwanych ustawień regionalnych za pomocą locale -a. Musiałem użyćen_US.utf8
eludom
7

Możesz użyć numfmt:

$ numfmt --grouping 123456789
123,456,789

Lub:

$ numfmt --g 123456789
123,456,789

Zauważ, że numfmt nie jest narzędziem POSIX, jest częścią jądra GNU.

Steven Penny
źródło
1
Dzięki za wskazówkę dotyczącą „grupowania”. Czy w drugim przykładzie (--g) chciałeś napisać coś takiego, -d, --groupingponieważ podwójne dzielenie wyrazów wymaga długich opcji?
Hopping Bunny
--gdziała dobrze dla mnie zamiast --grouping, tj. numfmt --g 1234567890i numfmt --grouping 1234567890zrobić to samo. Jest to bardzo przydatne małe narzędzie.
mattst
4
cat <<'EOF' |
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
EOF
perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;'

produkuje:

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

Odbywa się to poprzez podzielenie ciągu cyfr na 2 grupy, prawą grupę z 3 cyframi, lewą grupę z resztą, ale co najmniej jedną cyfrę. Następnie wszystko jest zastępowane przez 2 grupy, oddzielone przecinkiem. Trwa to do momentu niepowodzenia podstawienia. Opcje „wpe” służą do wyświetlania listy błędów, zawierają instrukcję w pętli z automatycznym drukowaniem i przyjmują następny argument jako „program” perla (szczegóły w poleceniu perldoc perlrun).

Najlepsze życzenia ... Pozdrawiam, drl

drl
źródło
Dzięki anonimowy za opinie. Nawet opinia negatywna może być przydatna, ale tylko wtedy, gdy zostanie wyjaśniona - skomentuj to, co widziałeś, że było złe. Dzięki ... na zdrowie
drl
Myślę, że głosowanie tutaj jest spowodowane tym, że nie wyjaśniłeś, co robi polecenie. OP poprosił o BASH/ AWKalternatywę, więc może nie używał PERLwcześniej. W każdym razie najlepiej wyjaśnić, co robi polecenie - szczególnie w przypadku jedno-liniowych.
AnthonyK
@AnthonyK - dziękuję za prawdopodobne wyjaśnienie. Dodałem komentarze, aby krótko wyjaśnić, jak to działa. Myślę, że alternatywne rozwiązania są często przydatne, ale
zauważasz,
Na tej stronie wypróbowałem sugestie dotyczące sed i python. Skrypt perla był jedynym, który działał dla całego pliku. Plik został złożony z tekstem i liczbami.
Mark
3

Z niektórymi awkimplementacjami:

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

123,456,789  

"%'"'"'d\n"to: "%(pojedynczy cytat) (podwójny cytat) (pojedynczy cytat) (podwójny cytat) (pojedynczy cytat) d \ n"

To wykorzysta skonfigurowany separator tysięcy dla twoich ustawień regionalnych (zazwyczaj ,w języku angielskim, spacja w języku francuskim, .w języku hiszpańskim / niemieckim ...). Taki sam jak zwrócony przezlocale thousands_sep

Ben
źródło
2

Częstym przypadkiem użycia jest dla mnie modyfikacja wyjścia potoku poleceń, tak aby liczby dziesiętne były drukowane z tysiącem separatorów. Zamiast pisać funkcję lub skrypt, wolę używać techniki, którą mogę dostosować w locie dla dowolnego wyjścia z potoku Unix.

printfPrzekonałem się (zapewniony przez Awk), że jest to najbardziej elastyczny i niezapomniany sposób na osiągnięcie tego. Znak apostrofu / pojedynczego cudzysłowu jest określany przez POSIX jako modyfikator formatowania liczb dziesiętnych i ma tę zaletę, że rozpoznaje ustawienia regionalne, więc nie ogranicza się do używania przecinków.

Podczas uruchamiania poleceń Awk z powłoki uniksowej mogą wystąpić trudności z wprowadzeniem znaku pojedynczego cudzysłowu w ciągu ograniczonym pojedynczymi cudzysłowami (np. W celu uniknięcia rozszerzenia powłoki przez zmienne pozycyjne $1). W tym przypadku uważam, że najbardziej czytelnym i niezawodnym sposobem wprowadzenia znaku pojedynczego cudzysłowu jest wprowadzenie go w postaci ósemkowej sekwencji ucieczki (zaczynającej się od \0).

Przykład:

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

Symulowane wyjście potoku pokazujące, które katalogi zajmują najwięcej miejsca na dysku:

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

Inne rozwiązania są wymienione w temacie Jak uniknąć pojedynczego cytatu w awk .

Uwaga: zgodnie z ostrzeżeniem w Print a Single Quote , zaleca się unikanie używania szesnastkowych sekwencji ucieczki, ponieważ nie działają one niezawodnie w różnych systemach.

Anthony G - sprawiedliwość dla Moniki
źródło
1
Ze wszystkich wymienionych tutaj odpowiedzi opartych na awk, ta z pewnością jest najbardziej wdzięczna (IMHO). Nie trzeba hakować cytatu innymi cytatami, jak w innych rozwiązaniach.
TSJNachos117
Dzięki @ TSJNachos117 Najtrudniejszą częścią jest zapamiętanie, że ósemkowe kodowanie znaku apostrofu to \047.
Anthony G - sprawiedliwość dla Moniki
2

awki bashmają dobre wbudowane rozwiązania oparte na printf, jak opisano w innych odpowiedziach. Ale najpierw sed.

Bo sedmusimy to zrobić „ręcznie”. Ogólna zasada jest taka, że ​​jeśli masz cztery kolejne cyfry, po których następuje cyfra (lub koniec linii), to pomiędzy pierwszą i drugą cyfrą należy wstawić przecinek.

Na przykład,

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

wydrukuje

12345,678

Oczywiście musimy nadal powtarzać ten proces, aby dodawać wystarczającą liczbę przecinków.

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

W sedpolu tpolecenie określa etykietę, która zostanie przeskoczona, jeśli ostatnie s///polecenie zakończyło się powodzeniem. Dlatego definiuję etykietę za pomocą :restart, aby odskakiwała.

Oto demo bash (na ideone ), które działa z dowolną liczbą cyfr:

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
źródło
1
$ echo 1232323 | awk '{printf(fmt,$1)}' fmt="%'6.3f\n"
12,32,323.000
Akshay Hegde
źródło
1

Jeśli patrzysz na DUŻE liczby, nie byłem w stanie sprawić, by powyższe rozwiązania działały. Na przykład, uzyskajmy naprawdę dużą liczbę:

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

Uwaga: Potrzebuję trusunąć wyjście nowego wiersza odwrotnego ukośnika z bc. Ta liczba jest zbyt duża, aby traktować ją jako liczbę zmiennoprzecinkową lub stałą liczbę bitów w awk, i nawet nie chcę budować wyrażenia regularnego wystarczająco dużego, aby uwzględnić wszystkie cyfry w sed. Mogę raczej odwrócić i umieścić przecinki między grupami po trzy cyfry, a następnie cofnąć:

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
źródło
2
Dobra odpowiedź. Jednak nigdy nie spotkałem się z problemem używania dużych liczb z Awk. Próbowałem twojego przykładu w wielu dystrybucjach opartych na Red Hat i Debianie, ale we wszystkich przypadkach Awk nie miał problemu z dużą liczbą. Pomyślałem o tym trochę i przyszło mi do głowy, że wszystkie systemy, na których eksperymentowałem, były 64-bitowe (nawet bardzo stara maszyna wirtualna z nieobsługiwanym RHEL 5). Dopiero testowałem stary lap-top z systemem 32-bitowym systemie operacyjnym, który udało mi się replikować problemu: awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - sprawiedliwość dla Moniki
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
użytkownik2796674
źródło
To dodaje fałszywy przecinek wiodący, jeśli liczba cyfr w liczbie jest wielokrotnością 3.
Stéphane Chazelas
@ StéphaneChazelas: Możesz pobrać dane wyjściowe ostatniego polecenia rev i przesłać je do potoku sed 's/^,//g'.
TSJNachos117
0

Chciałem również mieć udział po tym separatorem dziesiętnym prawidłowo oddzielone / rozstawione, dlatego napisałem ten SED-skrypt, który wykorzystuje pewne zmienne powłoki, aby dostosować się do preferencji regionalnych i osobistych. Uwzględnia także różne konwencje dotyczące liczby cyfr zgrupowanych razem :

#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
źródło
0

Rozwiązanie A bash/ awk(zgodnie z żądaniem), które działa niezależnie od długości liczby i używa ,niezależnie od ustawienia ustawień regionalnych thousands_seporaz wszędzie tam, gdzie liczby są na wejściu i pozwala uniknąć dodawania separatora tysięcy po 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}'

Daje:

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

W przypadku awktakich implementacji mawk, które nie obsługują operatorów wyrażeń regularnych, zmień wyrażenie regularne na/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Stéphane Chazelas
źródło