Thêm hàng ngàn dấu phân cách trong một số


36

Trong trăn

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

Để chia một số theo ba, ví dụ:

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

Làm thế nào để làm điều tương tự với bash / awk?

Câu trả lời:


29

Với sed:

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

(Lưu ý rằng điều này chỉ hoạt động cho chính xác 9 chữ số!)

hoặc điều này với sed:

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

Với printf:

$ LC_NUMERIC=en_US printf "%'.f\n" 123456789
123,456,789

Tôi cũng đang cố gắng với awk nhưng cuối cùng nó lại thêm dấu phẩyecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g")'
Rahul Patil

bây giờ tôi hiểu nhưng có vẻ phức tạpecho 123456789 | awk '$0=gensub(/(...)/,"\\1,","g"){sub(",$",""); print}'
Rahul Patil

1
Điều đó đầu tiên sedchỉ hoạt động nếu số chính xác là 9 chữ số. Các printfkhông hoạt động trên zsh. Do đó, sedcâu trả lời thứ hai có lẽ là tốt nhất.
Patrick

1
@RahulPatil Điều đó chỉ hoạt động chính xác nếu số chữ số là bội số của 3. Hãy thử với "12345678" và bạn sẽ thấy ý tôi là gì.
Patrick

1
Bạn có thể làm echo 123456789 | awk '{printf ("%'\''d\n", $0)}'(điều hiển nhiên là không phải lúc nào cũng hoạt động trên Linux!?, Nhưng hoạt động tốt trên AIX và Solaris)
Johan

51

bash's printfhỗ trợ khá nhiều tất cả mọi thứ bạn có thể làm trong printfchức năng C

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

printf từ coreutils sẽ làm như vậy

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

Điều này bây giờ được hỗ trợ trong zshquá, cập nhật bài viết ở đây .
don_crissti

1
Tôi đang sử dụng bash 4.1.2 và nó không hỗ trợ ... :(
msb

@msb Nó dường như phụ thuộc vào hệ thống của bạn vsnprintf. Trên hệ thống GNU / Linux, glibc dường như đã hỗ trợ nó kể từ ít nhất là năm 1995.
Mikel

2
Lưu ý printf sử dụng dấu phân cách hàng nghìn cho ngôn ngữ hiện tại của bạn , có thể là dấu phẩy, dấu chấm hoặc không có gì cả. Bạn có thể export LC_NUMERIC="en_US"nếu bạn muốn buộc dấu phẩy.
medmunds

Nhận danh sách các miền được hỗ trợ với locale -a. Tôi đã phải sử dụngen_US.utf8
eludom

7

Bạn có thể sử dụng numfmt:

$ numfmt --grouping 123456789
123,456,789

Hoặc là:

$ numfmt --g 123456789
123,456,789

Lưu ý rằng numfmt không phải là tiện ích POSIX, nó là một phần của lõi GNU.


1
Cảm ơn các mẹo "nhóm". Trong ví dụ thứ hai (--g), ý của bạn là viết một cái gì đó giống như -d, --groupingvì các dấu gạch nối kép cần các tùy chọn dài?
Nhảy Bunny

--glàm việc tốt cho tôi thay vì --grouping, tức là numfmt --g 1234567890numfmt --grouping 1234567890làm điều tương tự. Đó là một tiện ích nhỏ rất hữu ích.
mattst

4
cat <<'EOF' |
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
EOF
perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;'

sản xuất:

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

Điều này được thực hiện bằng cách chia chuỗi các chữ số thành 2 nhóm, nhóm bên phải có 3 chữ số, nhóm bên trái với bất cứ thứ gì còn lại, nhưng ít nhất một chữ số. Sau đó, mọi thứ được thay thế bởi 2 nhóm, được phân tách bằng dấu phẩy. Điều này tiếp tục cho đến khi sự thay thế thất bại. Các tùy chọn "wpe" là để liệt kê lỗi, kèm theo câu lệnh bên trong một vòng lặp với một bản in tự động và lấy đối số tiếp theo làm "chương trình" perl (xem lệnh perldoc perlrun để biết chi tiết).

Lời chúc tốt đẹp nhất ... chúc mừng, drl


Nhờ ẩn danh cho các thông tin phản hồi. Ngay cả một downvote có thể hữu ích, nhưng chỉ khi được giải thích - vui lòng nhận xét về những gì bạn thấy đó là sai. Cảm ơn ... chúc mừng
drl

Tôi nghĩ rằng downvote ở đây là vì bạn đã không giải thích những gì lệnh làm. OP đã yêu cầu một BASH/ AWKthay thế để anh ta có thể không sử dụng PERLtrước đó. Trong mọi trường hợp, tốt nhất để giải thích những gì lệnh thực hiện - đặc biệt là như vậy đối với một lớp lót.
AnthonyK

@AnthonyK - cảm ơn vì lời giải thích có thể xảy ra. Tôi đã thêm ý kiến ​​để giải thích ngắn gọn về cách thức hoạt động. Tôi nghĩ các giải pháp thay thế thường hữu ích, nhưng quan điểm của bạn về việc có thể không sử dụng perl được ghi nhận ... chúc mừng
drl

Tôi đã thử các đề nghị sed và python trên trang này. Kịch bản perl là tập lệnh duy nhất hoạt động cho toàn bộ tập tin. Các tập tin đã được nộp với văn bản và số.
Đánh dấu

3

Với một số awktriển khai:

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

123,456,789  

"%'"'"'d\n"là: "%(trích dẫn đơn) (trích dẫn kép) (trích dẫn đơn) (trích dẫn kép) (trích dẫn đơn) d \ n"

Điều đó sẽ sử dụng dấu phân cách nghìn được định cấu hình cho ngôn ngữ của bạn (thường là ,tiếng địa phương tiếng Anh, không gian bằng tiếng Pháp, .tiếng Tây Ban Nha / tiếng Đức ...). Tương tự như được trả lại bởilocale thousands_sep


2

Một trường hợp sử dụng phổ biến đối với tôi là sửa đổi đầu ra của một đường ống lệnh để các số thập phân được in bằng dấu phân cách. Thay vì viết một hàm hoặc tập lệnh, tôi thích sử dụng một kỹ thuật mà tôi có thể tùy chỉnh nhanh chóng cho bất kỳ đầu ra nào từ một đường ống Unix.

Tôi đã tìm thấy printf(được cung cấp bởi Awk) là cách linh hoạt nhất và đáng nhớ để thực hiện điều này. Các dấu nháy đơn / nhân vật báo giá duy nhất được xác định bởi POSIX như một modifier đến các số thập phân dạng và có ưu điểm là nó locale-aware vì vậy nó không hạn chế việc sử dụng ký tự dấu phẩy.

Khi chạy các lệnh Awk từ shell Unix, có thể gặp khó khăn khi nhập ký tự singe-quote bên trong chuỗi được phân tách bằng dấu ngoặc đơn (để tránh mở rộng shell của các biến vị trí, ví dụ $1:). Trong trường hợp này, tôi thấy cách dễ đọc và đáng tin cậy nhất để nhập ký tự trích dẫn đơn là nhập nó dưới dạng một chuỗi thoát bát phân (bắt đầu bằng \0).

Thí dụ:

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

Đầu ra mô phỏng của một đường ống hiển thị thư mục nào đang sử dụng nhiều dung lượng đĩa nhất:

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

Các giải pháp khác được liệt kê trong Làm thế nào để thoát khỏi một trích dẫn trong awk .

Lưu ý: như đã cảnh báo trong In một trích dẫn , nên tránh sử dụng các chuỗi thoát thập lục phân vì chúng không hoạt động đáng tin cậy trên các hệ thống khác nhau.


1
Trong tất cả các câu trả lời dựa trên awk được liệt kê ở đây, câu trả lời này chắc chắn là duyên dáng nhất (IMHO). Người ta không cần phải hack trong một trích dẫn với các trích dẫn khác như trong các giải pháp khác.
TSJNachos117

Cảm ơn @ TSJNachos117 Phần khó nhất là nhớ rằng mã hóa bát phân cho ký tự dấu nháy đơn là \047.
Anthony G - công lý cho Monica

2

awkbashcó các giải pháp tích hợp tốt, dựa trên printf, như được mô tả trong các câu trả lời khác. Nhưng trước tiên , sed.

Đối với sed, chúng ta cần phải làm "thủ công". Nguyên tắc chung là nếu bạn có bốn chữ số liên tiếp, theo sau là một chữ số không (hoặc cuối dòng) thì nên chèn dấu phẩy giữa chữ số thứ nhất và chữ số thứ hai.

Ví dụ,

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

sẽ in

12345,678

Chúng ta rõ ràng cần phải tiếp tục lặp lại quá trình, để tiếp tục thêm đủ dấu phẩy.

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

Trong sed, tlệnh chỉ định một nhãn sẽ được nhảy tới nếu s///lệnh cuối cùng thành công. Do đó, tôi xác định một nhãn với :restart, để nó nhảy trở lại.

Dưới đây là bản demo bash (trên ideone ) hoạt động với bất kỳ số chữ số nào:

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

Nếu bạn đang nhìn vào số LỚN, tôi không thể làm cho các giải pháp trên hoạt động được. Ví dụ: hãy lấy một số thực sự lớn:

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

Lưu ý Tôi cần trloại bỏ dấu gạch chéo đầu ra dòng mới từ bc. Con số này quá lớn để coi là số float hoặc số bit cố định trong awk và tôi thậm chí không muốn xây dựng một biểu thức chính quy đủ lớn để tính tất cả các chữ số trong sed. Thay vào đó, tôi có thể đảo ngược nó và đặt dấu phẩy giữa các nhóm ba chữ số, sau đó đảo ngược nó:

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
Câu trả lời tốt. Tuy nhiên, tôi chưa bao giờ gặp phải sự cố khi sử dụng số lượng lớn với Awk. Tôi đã thử ví dụ của bạn về một số bản phân phối dựa trên Red Hat và Debian nhưng trong mọi trường hợp, Awk không gặp vấn đề gì với số lượng lớn. Tôi đã nghĩ thêm về nó và tôi nhận ra rằng tất cả các hệ thống mà tôi đã thử nghiệm là 64-bit (ngay cả một VM rất cũ chạy RHEL 5 không được hỗ trợ). Mãi cho đến khi tôi thử nghiệm một lap-top cũ chạy HĐH 32 bit thì tôi mới có thể sao chép vấn đề của bạn : awk: run time error: improper conversion(number 1) in printf("%'d.
Anthony G - công lý cho 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

Điều đó thêm dấu phẩy hàng đầu nếu số chữ số trong số đó là bội số của 3.
Stéphane Chazelas

@ StéphaneChazelas: Bạn có thể lấy đầu ra của lệnh rev cuối cùng đó và chuyển nó tới sed 's/^,//g'.
TSJNachos117

0

Tôi cũng muốn có một phần sau khi các dấu phân cách thập phân tách một cách chính xác / khoảng cách, do đó tôi đã viết này sed-kịch bản trong đó sử dụng một số biến vỏ để thích nghi với sở thích của khu vực và cá nhân. Nó cũng tính đến các quy ước khác nhau cho số chữ số được nhóm lại với nhau :

#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

Giải pháp A bash/ awk(theo yêu cầu) hoạt động bất kể độ dài của số và sử dụng ,bất kể thousands_sepcài đặt của miền địa phương và bất cứ nơi nào các số nằm trong đầu vào và tránh thêm dấu phân cách sau 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}'

Cung cấp:

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

Với các awktriển khai như mawkthế không hỗ trợ các toán tử regex khoảng thời gian, hãy thay đổi biểu thức chính quy thành/(^|[^.0123456789])[0123456789][0123456789][0123456789][0123456789]+/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.