Tambahkan ribuan pemisah dalam sebuah angka

37

Dengan python

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

Untuk membagi angka dengan kembar tiga, misalnya:

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

Bagaimana melakukan hal yang sama dengan bash / awk?

pengguna2496
sumber

Jawaban:

29

Dengan sed:

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

(Perhatikan bahwa ini hanya berfungsi untuk tepat 9 digit!)

atau ini dengan sed:

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

Dengan printf:

$ LC_NUMERIC=en_US printf "%'.f\n" 123456789
123,456,789
slm
sumber
Saya juga mencoba dengan awk tetapi itu menambahkan koma pada akhirnyaecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil
sekarang saya mengerti tetapi tampaknya rumitecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil
1
Yang pertama sedhanya berfungsi jika jumlahnya tepat 9 digit. Tidak printfberfungsi pada zsh. Jadi sedjawaban kedua mungkin yang terbaik.
Patrick
1
@RahulPatil Itu hanya berfungsi dengan baik jika jumlah digit adalah kelipatan 3. Coba dengan "12345678" dan Anda akan melihat apa yang saya maksud.
Patrick
1
Anda dapat melakukannya echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(yang ternyata tidak selalu berhasil di Linux!?, Tetapi berfungsi dengan baik di AIX dan Solaris)
Johan
51

bash's printfdukungan cukup banyak segala sesuatu yang dapat Anda lakukan di printffungsi C

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

printf dari coreutils akan melakukan hal yang sama

/usr/bin/printf "%'d" 1234567   # => 1,234,567
Mikel
sumber
Ini sekarang didukung zshjuga, pos yang diperbarui di sini .
don_crissti
1
Saya di bash 4.1.2 dan tidak mendukung ... :(
msb
@msb Tampaknya tergantung pada sistem Anda vsnprintf. Pada sistem GNU / Linux, glibc tampaknya telah mendukungnya setidaknya sejak 1995.
Mikel
2
Catatan printf menggunakan pemisah ribuan untuk lokal Anda saat ini , yang mungkin berupa koma, titik, atau tidak sama sekali. Anda bisa export LC_NUMERIC="en_US"jika Anda ingin memaksa koma.
medmunds
Dapatkan daftar lokasi yang didukung dengan locale -a. Saya harus menggunakanen_US.utf8
eludom
7

Anda dapat menggunakan numfmt:

$ numfmt --grouping 123456789
123,456,789

Atau:

$ numfmt --g 123456789
123,456,789

Perhatikan bahwa numfmt bukan utilitas POSIX, itu adalah bagian dari GNU coreutils.

Steven Penny
sumber
1
Terima kasih atas tip "pengelompokan". Pada contoh kedua (--g), apakah Anda bermaksud menulis sesuatu seperti -d, --groupingkarena tanda hubung ganda membutuhkan opsi yang panjang?
Hopping Bunny
--gberfungsi dengan baik untuk saya daripada --grouping, yaitu numfmt --g 1234567890dan numfmt --grouping 1234567890melakukan hal yang sama. Utilitas kecil yang sangat berguna.
Mattst
4
cat <<'EOF' |
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
EOF
perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;'

menghasilkan:

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

Ini dilakukan dengan memisahkan string angka menjadi 2 kelompok, kelompok kanan dengan 3 digit, kelompok kiri dengan apa pun yang tersisa, tetapi setidaknya satu digit. Kemudian semuanya digantikan oleh 2 grup, dipisahkan oleh koma. Ini berlanjut sampai substitusi gagal. Pilihan "wpe" adalah untuk daftar kesalahan, lampirkan pernyataan di dalam loop dengan cetak otomatis, dan ambil argumen berikutnya sebagai "program" perl (lihat perintah perldoc perlrun untuk detailnya).

Salam hangat, tepuk tangan, drl

drl
sumber
Terima kasih kepada anonim untuk umpan baliknya. Bahkan downvote dapat bermanfaat, tetapi hanya jika dijelaskan - beri komentar tentang apa yang Anda lihat salah. Terima kasih ... tepuk tangan
drl
Saya pikir downvote di sini adalah karena Anda tidak menjelaskan apa yang dilakukan perintah. OP meminta BASH/ AWKalternatif sehingga ia mungkin tidak pernah menggunakan PERLsebelumnya. Dalam hal apa pun, yang terbaik untuk menjelaskan apa yang dilakukan perintah - terutama untuk satu baris.
AnthonyK
@AnthonyK - terima kasih atas kemungkinan penjelasannya. Saya menambahkan komentar untuk menjelaskan secara singkat cara kerjanya. Saya pikir solusi alternatif sering berguna, tetapi poin Anda tentang perl tidak menggunakan perl dicatat ... cheers
drl
Saya mencoba saran sed dan python di halaman ini. Script perl adalah satu-satunya yang bekerja untuk seluruh file. File itu diajukan dengan teks dan angka.
Mark
3

Dengan beberapa awk implementasi:

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

123,456,789  

"%'"'"'d\n"adalah: "%(kutipan tunggal) (penawaran ganda) (penawaran tunggal) (penawaran ganda) (penawaran tunggal) d \ n"

Itu akan menggunakan pemisah ribuan yang dikonfigurasi untuk lokal Anda (biasanya ,dalam bahasa Inggris lokal, spasi dalam bahasa Prancis, .dalam bahasa Spanyol / Jerman ...). Sama seperti yang dikembalikan olehlocale thousands_sep

Ben
sumber
2

Kasus penggunaan umum bagi saya adalah untuk memodifikasi output dari pipa perintah sehingga angka desimal dicetak dengan ribuan pemisah. Daripada menulis fungsi atau skrip, saya lebih suka menggunakan teknik yang bisa saya sesuaikan dengan cepat setiap output dari pipa Unix.

Saya telah menemukan printf(disediakan oleh Awk) sebagai cara yang paling fleksibel dan mudah diingat untuk mencapai ini. Karakter tanda kutip / kutipan tunggal ditentukan oleh POSIX sebagai a pengubah untuk memformat angka desimal dan memiliki keunggulan yang disadari oleh lokal sehingga tidak dibatasi untuk menggunakan karakter koma.

Saat menjalankan perintah Awk dari shell Unix, mungkin ada kesulitan memasukkan karakter tanda kutip di dalam string yang dibatasi oleh tanda kutip tunggal (untuk menghindari shell ekspansi variabel posisi, misalnya, $1). Dalam hal ini, saya menemukan cara yang paling mudah dibaca dan dapat diandalkan untuk memasukkan karakter tanda kutip tunggal adalah dengan memasukkannya sebagai urutan pelepasan oktal (dimulai dengan \0).

Contoh:

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

Output simulasi pipa yang menunjukkan direktori mana yang menggunakan ruang disk paling banyak:

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

Solusi lain tercantum di Cara keluar dari kutipan tunggal di dalam awk .

Catatan: seperti diperingatkan dalam Cetak Kutipan Tunggal , disarankan untuk menghindari penggunaan urutan pelepasan heksadesimal karena mereka tidak bekerja dengan andal di berbagai sistem.

Anthony G - keadilan untuk Monica
sumber
1
Dari semua jawaban berbasis awk yang tercantum di sini, yang ini pastilah yang paling anggun (IMHO). Seseorang tidak perlu meretas kutipan dengan kutipan lain seperti pada solusi lain.
TSJNachos117
Terima kasih @ TSJNachos117 Bagian tersulit adalah mengingat bahwa pengkodean oktal untuk karakter apostrof adalah \047.
Anthony G - keadilan untuk Monica
2

awkdan bashmemiliki solusi bawaan yang baik, berdasarkan printf, seperti yang dijelaskan dalam jawaban lain. Tapi pertama-tama,sed ,.

Untuk sed , kita perlu melakukannya "secara manual". Aturan umum adalah bahwa jika Anda memiliki empat digit berturut-turut, diikuti oleh non-digit (atau end-of-line) maka koma harus dimasukkan antara digit pertama dan kedua.

Sebagai contoh,

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

akan dicetak

12345,678

Kita jelas perlu untuk kemudian terus mengulangi prosesnya, untuk terus menambahkan cukup koma.

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

Di sed, tperintah menentukan label yang akan dilompati jika s///perintah terakhir berhasil. Karena itu saya mendefinisikan label dengan :restart, agar melompat kembali.

Berikut ini adalah demo bash (pada ideone ) yang bekerja dengan sejumlah digit:

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

Jika Anda melihat nomor BESAR saya tidak dapat membuat solusi di atas berfungsi. Misalnya, mari kita dapatkan angka yang sangat besar:

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

Catatan Saya perlu truntuk menghapus output baris baru backslash dari bc. Angka ini terlalu besar untuk diperlakukan sebagai angka bit mengambang atau tetap dalam awk, dan saya bahkan tidak ingin membangun regexp yang cukup besar untuk memperhitungkan semua angka dalam sed. Sebaliknya, saya dapat membalikkannya dan menempatkan koma di antara kelompok tiga digit, lalu membatalkannya:

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
sumber
2
Jawaban yang bagus. Namun, saya belum pernah mengalami masalah menggunakan nomor besar dengan Awk. Saya mencoba contoh Anda pada sejumlah distribusi berbasis Red Hat dan Debian tetapi dalam semua kasus, Awk tidak memiliki masalah dengan jumlah besar. Saya memikirkan lebih banyak tentang hal itu dan terlintas dalam benak saya bahwa semua sistem yang telah saya uji coba adalah 64-bit (bahkan VM yang sangat tua yang menjalankan RHEL 5 yang tidak didukung). Tidak sampai saya diuji lap-top tua menjalankan OS 32-bit yang saya bisa mereplikasi masalah Anda: awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - keadilan untuk 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
pengguna2796674
sumber
Itu menambahkan tanda koma palsu jika jumlah digit dalam angka adalah kelipatan 3.
Stéphane Chazelas
@ StéphaneChazelas: Anda dapat mengambil output dari perintah rev terakhir, dan mengirimkannya ke sed 's/^,//g'.
TSJNachos117
0

Saya juga ingin memiliki bagian setelah para pemisah desimal benar dipisahkan / spasi, karena itu saya menulis ini sed-naskah yang menggunakan beberapa variabel shell untuk menyesuaikan dengan preferensi regional dan pribadi. Ini juga memperhitungkan konvensi yang berbeda untuk jumlah digit yang dikelompokkan bersama :

#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
sumber
0

Solusi A bash/ awk(seperti yang diminta) yang berfungsi terlepas dari panjang angka dan penggunaannya ,terlepas dari pengaturan lokal thousands_sep, dan di mana pun angka-angka itu di input dan menghindari menambahkan ribuan pemisah setelah di 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}'

Memberi:

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

Dengan awkimplementasi seperti mawkitu yang tidak mendukung operator regex interval, ubah regexp menjadi/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Stéphane Chazelas
sumber