Sắp xếp tệp văn bản theo độ dài dòng bao gồm khoảng trắng


137

Tôi có một tệp CSV trông như thế này

AS2345, ASDF1232, Ví dụ về ông Plain, 110 ave nhị phân, Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Bà Plain Ví dụ, 1121110 Ternary st. 110 ave nhị phân .., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Ví dụ, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Ví dụ, 110 Ternary ave., Một số thành phố, RI, 12345, (999) 123-5555,1.56

Tôi cần sắp xếp nó theo chiều dài dòng bao gồm cả khoảng trắng. Lệnh sau không bao gồm khoảng trắng, có cách nào để sửa đổi nó để nó hoạt động với tôi không?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Tôi thực sự muốn sống ở Đại lộ nhị phân hoặc Phố Ternary, những người đó chắc chắn sẽ đồng ý với những điều như "8192 số tròn"
schnaader

Câu trả lời:


224

Câu trả lời

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

Hoặc, để thực hiện phân loại phụ ban đầu (có thể không chủ ý) của bất kỳ dòng có độ dài bằng nhau:

cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-

Trong cả hai trường hợp, chúng tôi đã giải quyết vấn đề đã nêu của bạn bằng cách di chuyển khỏi awk cho lần cắt cuối cùng của bạn.

Các dòng có độ dài phù hợp - phải làm gì trong trường hợp hòa:

Câu hỏi không xác định có muốn sắp xếp thêm hay không cho các dòng có độ dài phù hợp. Tôi đã giả định rằng điều này là không mong muốn và đề nghị sử dụng -s( --stable) để ngăn chặn các dòng như vậy được sắp xếp với nhau và giữ chúng theo thứ tự tương đối mà chúng xảy ra trong đầu vào.

(Những người muốn kiểm soát nhiều hơn việc sắp xếp các mối quan hệ này có thể xem xét --keytùy chọn sắp xếp .)

Tại sao giải pháp cố gắng của câu hỏi không thành công (awk line-dựng lại):

Thật thú vị khi lưu ý sự khác biệt giữa:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Họ mang lại năng suất tương ứng

hello   awk   world
hello awk world

Phần có liên quan của sổ tay (gawk's) chỉ đề cập đến một khía cạnh mà awk sẽ xây dựng lại toàn bộ $ 0 (dựa trên dấu phân cách, v.v.) khi bạn thay đổi một trường. Tôi đoán đó không phải là hành vi điên rồ. Nó có cái này:

"Cuối cùng, có những lúc thuận tiện để buộc awk xây dựng lại toàn bộ hồ sơ, sử dụng giá trị hiện tại của các trường và OFS. Để làm điều này, hãy sử dụng phép gán dường như vô hại:"

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

"Lực lượng này awk để xây dựng lại hồ sơ."

Kiểm tra đầu vào bao gồm một số dòng có độ dài bằng nhau:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g

1
heemayl, đúng vậy, cảm ơn. Tôi đã cố gắng khớp với hình dạng của giải pháp đã cố gắng của OP nếu có thể, để cho phép anh ấy chỉ tập trung vào những khác biệt quan trọng giữa anh ấy và của tôi.
neillb

1
Nó cũng đáng để chỉ ra rằng nó cat $@cũng bị hỏng. Bạn hoàn toàn chắc chắn muốn trích dẫn nó, nhưcat "$@"
tripleee 18/07/17

27

Các giải pháp AWK từ neillb là tuyệt vời nếu bạn thực sự muốn sử dụng awkvà nó giải thích tại sao đó là một rắc rối ở đó, nhưng nếu những gì bạn muốn là để hoàn thành công việc một cách nhanh chóng và không quan tâm những gì bạn làm điều đó trong, một trong những giải pháp là sử dụng sort()Chức năng của Perl với thói quen caparison tùy chỉnh để lặp lại các dòng đầu vào. Đây là một lót:

perl -e 'print sort { length($a) <=> length($b) } <>'

Bạn có thể đặt điều này trong đường ống của bạn bất cứ nơi nào bạn cần nó, hoặc là nhận STDIN (từ cathoặc một chuyển hướng vỏ) hoặc chỉ cần cung cấp tên tập tin để perl như là đối số khác và để cho nó mở file.

Trong trường hợp của tôi, tôi cần các dòng dài nhất trước tiên, vì vậy tôi đã trao đổi $a$bso sánh.


Đây là giải pháp tốt hơn vì awk gây ra sự sắp xếp không mong muốn khi tệp đầu vào chứa các dòng số và alfanumeric Ở đây lệnh oneline: $ cat testfile | perl -e 'print sort {length ($ a) <=> length ($ b)} <>'
alemol

Nhanh! Đã có 465.000 tệp dòng (một từ trên mỗi dòng) trong <1 giây, khi đầu ra được chuyển hướng sang một tệp khác - do đó:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

Windows với StrawberryPerl hoạt động:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc

14

Hãy thử lệnh này thay thế:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-

10

Kết quả điểm chuẩn

Dưới đây là kết quả của một điểm chuẩn trên các giải pháp từ các câu trả lời khác cho câu hỏi này.

Phương pháp kiểm tra

  • 10 tuần tự chạy trên một máy nhanh, tính trung bình
  • Perl 5,24
  • awk 3.1.5 (gawk 4.1.0 lần nhanh hơn ~ 2%)
  • Các tập tin đầu vào là một quái vật 550 MB, 6 triệu dòng (British National Corpus txt)

Các kết quả

  1. perlGiải pháp của Caleb mất 11,2 giây
  2. perlgiải pháp của tôi mất 11,6 giây
  3. awkgiải pháp số 1 của neillb mất 20 giây
  4. awkgiải pháp số 2 của neillb mất 23 giây
  5. awkdung dịch anubhava mất 24 giây
  6. awkGiải pháp của Jonathan mất 25 giây
  7. bashGiải pháp của Fretz mất nhiều thời gian hơn 400 lần so với các awkgiải pháp (sử dụng trường hợp thử nghiệm rút ngắn 100000 dòng). Nó hoạt động tốt, chỉ mất mãi mãi.

perlTùy chọn bổ sung

Ngoài ra, tôi đã thêm một giải pháp Perl khác:

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file

6

Bash thuần túy:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done

3

Các length()chức năng không bao gồm không gian. Tôi sẽ chỉ thực hiện các điều chỉnh nhỏ cho đường ống của bạn (bao gồm tránh UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

Các sedlệnh trực tiếp loại bỏ các chữ số và đại tràng gia tăng theo awklệnh. Ngoài ra, giữ định dạng của bạn từ awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'

2

Tôi thấy các giải pháp này sẽ không hoạt động nếu tệp của bạn chứa các dòng bắt đầu bằng một số, vì chúng sẽ được sắp xếp bằng số cùng với tất cả các dòng được tính. Giải pháp là để cung cấp cho sortcác -g(tổng-số-sort) cờ thay vì -n(số-sort):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

2
Xin chào, Markus. Tôi không quan sát nội dung dòng (số hay không) - trái ngược với độ dài dòng - như có bất kỳ ảnh hưởng nào đến việc sắp xếp ngoại trừ trong trường hợp các dòng có độ dài phù hợp. Đây có phải là những gì bạn có ý nghĩa? Trong những trường hợp như vậy, tôi không tìm thấy các phương pháp sắp xếp chuyển đổi từ -nđề xuất của bạn -gđể mang lại bất kỳ cải thiện nào, vì vậy tôi hy vọng là không. Bây giờ tôi đã giải quyết, trong câu trả lời của tôi, làm thế nào để cấm phân loại phụ các dòng có độ dài bằng nhau (sử dụng --stable). Có hay không đó là những gì bạn muốn nói, cảm ơn vì đã mang nó đến sự chú ý của tôi! Tôi cũng đã thêm một đầu vào được xem xét để kiểm tra.
neillb

4
Không, hãy để tôi giải thích bằng cách phá vỡ nó. Chỉ awkmột phần sẽ tạo ra một danh sách các dòng có tiền tố với độ dài dòng và khoảng trắng. Đường ống để nó sort -nsẽ làm việc như mong đợi. Nhưng nếu bất kỳ dòng nào trong số đó đã có số ở đầu, thì những dòng đó sẽ bắt đầu bằng chiều dài + dấu cách + số. sort -nbỏ qua không gian đó và sẽ coi nó là một số được nối từ chiều dài + số. -gThay vào đó, việc sử dụng cờ sẽ dừng lại ở không gian đầu tiên, mang lại một cách sắp xếp chính xác. Hãy tự thử bằng cách tạo một tệp có một số dòng có tiền tố số và chạy từng bước lệnh.
Markus Amalthea Magnuson

1
Tôi cũng thấy rằng sort -nbỏ qua không gian và tạo ra một sự sắp xếp không chính xác. sort -gxuất ra thứ tự đúng.
Robert Smith

Tôi không thể tái tạo vấn đề được mô tả với -ntrong sort (GNU coreutils) 8.21. Các infotài liệu mô tả -gnhư kém hiệu quả và có khả năng kém chính xác (nó chuyển đổi số để phao), vì vậy có lẽ không sử dụng nó nếu bạn không cần.
phils

Tài liệu nb cho -n: "Sắp xếp bằng số. Số bắt đầu mỗi dòng và bao gồm các khoảng trống tùy chọn, dấu '-' tùy chọn và 0 hoặc nhiều chữ số có thể được phân tách bằng hàng nghìn dấu phân cách, theo sau là ký tự dấu thập phân và 0 hoặc nhiều chữ số . Một số trống được coi là '0'. Địa điểm 'LC_NUMERIC' chỉ định ký tự dấu thập phân và dấu phân cách hàng nghìn. Theo mặc định, khoảng trống là khoảng trắng hoặc tab, nhưng ngôn ngữ 'LC_CTYPE' có thể thay đổi điều này. "
phils


2

1) giải pháp awk tinh khiết. Giả sử chiều dài dòng không thể lớn hơn> 1024 thì

tên tập tin mèo | awk 'BEGIN {min = 1024; s = "";} {l = chiều dài ($ 0); if (l <min) {min = l; s = $ 0;}} HẾT {in s} '

2) một giải pháp bash liner giả sử tất cả các dòng chỉ có 1 từ, nhưng có thể làm lại cho mọi trường hợp trong đó tất cả các dòng có cùng số lượng từ:

LINES = $ (tên tệp mèo); cho k tính bằng $ LINES; làm printf "$ k"; tiếng vang $ k | wc -L; xong | sắp xếp -k2 | đầu -n 1 | cắt -d "" -f1


1

Đây là một phương pháp tương thích đa dòng để sắp xếp các dòng theo chiều dài. Nó yêu cầu:

  1. wc -m có sẵn cho bạn (macOS có nó).
  2. Ngôn ngữ hiện tại của bạn hỗ trợ các ký tự nhiều byte, ví dụ: bằng cách cài đặt LC_ALL=UTF-8. Bạn có thể đặt cái này trong .bash_profile hoặc đơn giản bằng cách thêm nó trước lệnh sau.
  3. testfile có mã hóa ký tự khớp với miền địa phương của bạn (ví dụ: UTF-8).

Đây là lệnh đầy đủ:

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

Giải thích từng phần:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← tạo một bản sao của mỗi dòng trong biến awk lvà thoát kép mỗi 'dòng để dòng có thể được lặp lại một cách an toàn dưới dạng lệnh shell ( \047là một trích dẫn đơn trong ký hiệu bát phân).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← đây là lệnh chúng ta sẽ thực thi, nó lặp lại dòng thoát wc -m.
  • cmd | getline c;← thực thi lệnh và sao chép giá trị đếm ký tự được trả về biến awk c.
  • close(cmd); ← đóng đường ống vào lệnh shell để tránh chạm vào giới hạn hệ thống về số lượng tệp đang mở trong một quy trình.
  • sub(/ */, "", c);← cắt khoảng trắng từ giá trị đếm ký tự được trả về wc.
  • { print c, $0 } ← in giá trị đếm ký tự của dòng, khoảng trắng và dòng gốc.
  • | sort -ns← sắp xếp các dòng (theo giá trị đếm ký tự được chuẩn bị trước) bằng số ( -n) và duy trì thứ tự sắp xếp ổn định ( -s).
  • | cut -d" " -f2- ← loại bỏ các giá trị đếm ký tự được chuẩn bị trước.

Nó chậm (chỉ 160 dòng mỗi giây trên Macbook Pro nhanh) vì nó phải thực thi lệnh phụ cho mỗi dòng.

Ngoài ra, chỉ cần làm điều này với gawk(như phiên bản 3.1.5, gawk là nhận biết đa bào), sẽ nhanh hơn đáng kể. Rất nhiều khó khăn khi thực hiện tất cả các lần thoát và trích dẫn kép để chuyển các dòng một cách an toàn thông qua lệnh shell từ awk, nhưng đây là phương pháp duy nhất tôi có thể thấy rằng không yêu cầu cài đặt phần mềm bổ sung (mặc định không có sẵn gawk hệ điều hành Mac).

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.