Urutkan file teks berdasarkan panjang baris termasuk spasi

143

Saya memiliki file CSV yang terlihat seperti ini

AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Nyonya Plain Contoh, 1121110 Ternary st. 110 Binary ave .., Atlantis, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Example, 110 Ternary ave., Some City, RI, 12345, (999) 123-5555,1.56

Saya perlu mengurutkan berdasarkan panjang baris termasuk spasi. Perintah berikut tidak menyertakan spasi, apakah ada cara untuk memodifikasinya agar berfungsi untuk saya?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
gnarbarian
sumber
25
Saya sangat ingin tinggal di Binary Avenue atau Ternary Street, orang-orang itu pasti setuju dengan hal-hal seperti "8192 adalah bilangan bulat"
schnaader

Jawaban:

233

Menjawab

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

Atau, untuk melakukan sub-pengurutan asli (mungkin tidak disengaja) dari garis yang sama panjangnya:

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

Dalam kedua kasus tersebut, kami telah menyelesaikan masalah yang Anda nyatakan dengan beralih dari awk untuk potongan terakhir Anda.

Garis dengan panjang yang cocok - apa yang harus dilakukan dalam kasus dasi:

Pertanyaan tersebut tidak menentukan apakah pengurutan lebih lanjut diperlukan untuk garis yang cocok atau tidak. Saya berasumsi bahwa ini tidak diinginkan dan menyarankan penggunaan -s( --stable) untuk mencegah baris seperti itu diurutkan satu sama lain, dan menyimpannya dalam urutan relatif di mana mereka terjadi dalam input.

(Mereka yang ingin lebih mengontrol penyortiran ikatan ini mungkin melihat --keyopsi sortir .)

Mengapa solusi yang dicoba dari pertanyaan tersebut gagal (pembangunan kembali baris awk):

Menarik untuk diperhatikan perbedaan antara:

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

Mereka menghasilkan masing-masing

hello   awk   world
hello awk world

Bagian yang relevan dari manual (gawk) hanya menyebutkan sebagai tambahan bahwa awk akan membangun kembali seluruh $ 0 (berdasarkan pemisah, dll) saat Anda mengubah satu bidang. Saya kira itu bukan perilaku gila. Ini memiliki ini:

"Terakhir, ada kalanya nyaman untuk memaksa awk untuk membangun kembali seluruh record, menggunakan nilai terkini dari field dan OFS. Untuk melakukan ini, gunakan tugas yang tampaknya tidak berbahaya:"

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

"Ini memaksa canggung untuk membangun kembali rekaman itu."

Input tes termasuk beberapa baris dengan panjang yang sama:

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
neillb
sumber
2
heemayl, ya, terima kasih. Saya telah mencoba mencocokkan bentuk solusi yang dicoba OP jika memungkinkan, untuk memungkinkannya fokus hanya pada perbedaan penting antara miliknya dan milik saya.
neillb
2
Patut ditunjukkan bahwa cat $@rusak juga. Anda pasti ingin mengutipnya, seperticat "$@"
tripleee
29

The solusi AWK dari neillb besar jika Anda benar-benar ingin menggunakan awkdan itu menjelaskan mengapa itu merepotkan sana, tetapi jika apa yang Anda inginkan adalah untuk mendapatkan pekerjaan yang dilakukan dengan cepat dan tidak peduli apa yang Anda lakukan dalam, salah satu solusi adalah dengan menggunakan sort()Fungsi Perl dengan rutinitas perbandingan kustom untuk melakukan iterasi melalui baris masukan. Ini satu baris:

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

Anda dapat meletakkan ini di pipeline Anda di mana pun Anda membutuhkannya, baik menerima STDIN (dari catatau shell redirect) atau cukup berikan nama file ke perl sebagai argumen lain dan biarkan membuka file.

Dalam kasus saya, saya membutuhkan garis terpanjang terlebih dahulu, jadi saya bertukar $adan $bdalam perbandingan.

Caleb
sumber
1
Ini adalah solusi yang lebih baik karena awk menyebabkan pengurutan yang tidak terduga ketika file input berisi baris numerik dan alfanumerik Berikut perintah oneline: $ cat testfile | perl -e 'print sort {length ($ a) <=> length ($ b)} <>'
alemol
1
Cepat! Melakukan 465.000 file baris (satu kata per baris) dalam <1 detik, ketika output dialihkan ke file lain - dengan demikian:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus
1
Windows dengan karya StrawberryPerl:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc
14

Coba perintah ini sebagai gantinya:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
anubhava.dll
sumber
11

Hasil benchmark

Di bawah ini adalah hasil benchmark di seluruh solusi dari jawaban lain untuk pertanyaan ini.

Metode pengujian

  • 10 berjalan berurutan pada mesin yang cepat, dirata-rata
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 kali ~ 2% lebih cepat)
  • File input berukuran 550MB, 6 juta baris monstrositas (British National Corpus txt)

Hasil

  1. perlSolusi Caleb membutuhkan waktu 11,2 detik
  2. perlsolusi saya membutuhkan waktu 11,6 detik
  3. neillb ini awksolusi # 1 mengambil 20 detik
  4. neillb ini awksolusi # 2 mengambil 23 detik
  5. awksolusi anubhava membutuhkan waktu 24 detik
  6. awkSolusi Jonathan membutuhkan waktu 25 detik
  7. bashSolusi Fretz membutuhkan waktu 400x lebih lama dari awksolusi (menggunakan kasus uji terpotong 100000 baris). Ini berfungsi dengan baik, hanya membutuhkan waktu selamanya.

perlSolusi lain

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

Bash murni:

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
Fritz G. Mehner
sumber
3

The length()Fungsi tidak termasuk spasi. Saya hanya akan membuat sedikit penyesuaian pada pipeline Anda (termasuk menghindari UUOC ).

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

The sedperintah langsung menghilangkan angka dan usus ditambah dengan awkperintah. Cara lainnya, jauhkan pemformatan Anda dari awk:

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

Saya menemukan solusi ini tidak akan berfungsi jika file Anda berisi baris yang dimulai dengan angka, karena akan diurutkan secara numerik bersama dengan semua baris yang dihitung. Solusinya adalah untuk memberikan sortyang -g(umum-numerik-macam) bendera bukan -n(numerik-macam):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-
Markus Amalthea Magnuson
sumber
2
Hai, Markus. Saya tidak mengamati konten garis (numerik atau tidak) - berlawanan dengan panjang baris - memiliki efek pada pengurutan kecuali dalam kasus garis dengan panjang yang cocok. Apakah ini yang kamu maksud? Dalam kasus seperti itu, saya tidak menemukan metode pengurutan yang beralih dari yang -nAnda sarankan -guntuk menghasilkan peningkatan, jadi saya harap tidak. Sekarang saya telah membahas, dalam jawaban saya, bagaimana melarang sub-penyortiran garis yang sama panjangnya (menggunakan --stable). Apakah itu yang Anda maksud atau tidak, terima kasih telah menyampaikannya kepada saya! Saya juga telah menambahkan masukan yang dipertimbangkan untuk diuji.
neillb
4
Tidak, izinkan saya menjelaskan dengan memecahnya. Hanya awkbagian tersebut yang akan menghasilkan daftar garis yang diawali dengan panjang garis dan spasi. Menyalurkannya ke sort -nakan berfungsi seperti yang diharapkan. Tetapi jika salah satu dari garis tersebut sudah memiliki angka di awal, garis tersebut akan dimulai dengan panjang + spasi + angka. sort -nmengabaikan spasi itu dan akan memperlakukannya sebagai satu angka yang digabungkan dari panjang + angka. Penggunaan -gbendera malah akan berhenti di ruang pertama, menghasilkan urutan yang benar. Cobalah sendiri dengan membuat file dengan beberapa baris berawalan angka dan jalankan perintah langkah demi langkah.
Markus Amalthea Magnuson
1
Saya juga menemukan bahwa sort -nmengabaikan ruang dan menghasilkan penyortiran yang salah. sort -gmengeluarkan urutan yang benar.
Robert Smith
Saya tidak dapat mereproduksi masalah yang dijelaskan dengan -ndalam sort (GNU coreutils) 8.21. The infodokumentasi menggambarkan -gkurang efisien dan berpotensi kurang tepat (itu mengkonversi angka untuk mengapung), jadi mungkin tidak menggunakannya jika Anda tidak perlu.
phils
dokumentasi nb untuk -n: "Urutkan secara numerik. Nomor dimulai setiap baris dan terdiri dari opsional kosong, tanda '-' opsional, dan nol atau lebih digit yang mungkin dipisahkan oleh ribuan pemisah, secara opsional diikuti dengan karakter titik desimal dan nol atau lebih digit . Angka kosong dianggap sebagai '0'. Lokal 'LC_NUMERIC' menentukan karakter titik desimal dan pemisah ribuan. Secara default, kosong adalah spasi atau tab, tetapi lokal 'LC_CTYPE' dapat mengubahnya. "
phils
2

Dengan POSIX Awk:

{
  c = length
  m[c] = m[c] ? m[c] RS $0 : $0
} END {
  for (c in m) print m[c]
}

Contoh

Steven Penny
sumber
2

1) solusi awk murni. Misalkan panjang garis tidak boleh lebih> 1024

nama file kucing | awk 'BEGIN {min = 1024; s = "";} {l = panjang ($ 0); jika (l <min) {min = l; s = $ 0;}} SELESAI {print s} '

2) solusi satu liner bash dengan asumsi semua baris hanya memiliki 1 kata, tetapi dapat dikerjakan ulang untuk kasus apa pun di mana semua baris memiliki jumlah kata yang sama:

BARIS = $ (nama file kucing); untuk k di $ LINES; lakukan printf "$ k"; echo $ k | wc -L; selesai | urutkan -k2 | kepala -n 1 | cut -d "" -f1

Michael Yuniverg
sumber
1

Berikut adalah metode yang kompatibel dengan multibyte untuk mengurutkan baris berdasarkan panjang. Ini membutuhkan:

  1. wc -m tersedia untuk Anda (macOS memilikinya).
  2. Lokal Anda saat ini mendukung karakter multi-byte, misalnya, dengan menyetel LC_ALL=UTF-8. Anda dapat mengatur ini baik di .bash_profile Anda, atau cukup dengan membuatnya terlebih dahulu sebelum perintah berikut.
  3. testfile memiliki pengkodean karakter yang cocok dengan lokal Anda (misalnya, UTF-8).

Berikut perintah lengkapnya:

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-

Menjelaskan bagian demi bagian:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← membuat salinan setiap baris dalam variabel awk ldan double-escapes 'sehingga baris dapat di-echo dengan aman sebagai perintah shell ( \047adalah tanda kutip tunggal dalam notasi oktal).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← ini adalah perintah yang akan kita jalankan, yang menggemakan baris yang di-escape wc -m.
  • cmd | getline c;← menjalankan perintah dan menyalin nilai jumlah karakter yang dikembalikan ke variabel awk c.
  • close(cmd); ← tutup pipa ke perintah shell untuk menghindari mencapai batas sistem pada jumlah file yang terbuka dalam satu proses.
  • sub(/ */, "", c);← memotong spasi dari nilai jumlah karakter yang dikembalikan oleh wc.
  • { print c, $0 } ← mencetak nilai jumlah karakter baris, spasi, dan baris asli.
  • | sort -ns← mengurutkan baris (dengan menambahkan nilai jumlah karakter) secara numerik ( -n), dan mempertahankan urutan pengurutan yang stabil ( -s).
  • | cut -d" " -f2- ← menghapus nilai jumlah karakter yang ditambahkan.

Ini lambat (hanya 160 baris per detik pada Macbook Pro yang cepat) karena harus menjalankan sub-perintah untuk setiap baris.

Atau, lakukan ini hanya dengan gawk(pada versi 3.1.5, gawk adalah multibyte aware), yang akan jauh lebih cepat. Banyak kesulitan melakukan semua pelarian dan kutipan ganda untuk melewati baris dengan aman melalui perintah shell dari awk, tetapi ini adalah satu-satunya metode yang saya temukan yang tidak memerlukan penginstalan perangkat lunak tambahan (gawk tidak tersedia secara default di macOS).

Quinn Comendant
sumber
1

menggunakan Raku (sebelumnya dikenal sebagai Perl6)

~$ cat "BinaryAve.txt" | raku -e 'given lines() {.sort(*.chars).join("\n").say};'

AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Atlantis,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Ternary ave.,Some City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Liberty City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mrs. Plain Example, 1121110 Ternary st.                                        110 Binary ave..,Atlantis,RI,12345,(999)123-5555,1.56

Untuk membalik urutan, tambahkan .reversedi tengah rantai panggilan metode - segera setelahnya .sort(). Berikut kode yang menunjukkan yang .charsmencakup spasi:

~$ cat "number_triangle.txt" | raku -e 'given lines() {.map(*.chars).say};'
(1 3 5 7 9 11 13 15 17 19 0)
~$ cat "number_triangle.txt"
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 0

Berikut perbandingan waktu antara awk dan Raku menggunakan file txt 9,1MB dari Genbank:

~$ time cat "rat_whole_genome.txt" | raku -e 'given lines() {.sort(*.chars).join("\n").say};' > /dev/null
    
    real    0m1.308s
    user    0m1.213s
    sys 0m0.173s
    
~$ #awk code from neillb
~$ time cat "rat_whole_genome.txt" | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-  > /dev/null
    
    real    0m1.189s
    user    0m1.170s
    sys 0m0.050s

HTH.

https://raku.org

gembira 1
sumber