Menggabungkan / mengurutkan / unik sejumlah besar file teks secara efisien

8

Saya mencoba naif:

$ cat * | sort -u > /tmp/bla.txt

yang gagal dengan:

-bash: /bin/cat: Argument list too long

Jadi untuk menghindari solusi konyol seperti (membuat file sementara yang sangat besar):

$ find . -type f -exec cat {} >> /tmp/unsorted.txt \;
$ cat /tmp/unsorted.txt | sort -u > /tmp/bla.txt

Saya pikir saya bisa memproses file satu per satu menggunakan (ini akan mengurangi konsumsi memori, dan lebih dekat ke mekanisme streaming):

$ cat proc.sh
#!/bin/sh
old=/tmp/old.txt
tmp=/tmp/tmp.txt
cat $old "$1" | sort -u > $tmp
mv $tmp $old

Diikuti kemudian oleh:

$ touch /tmp/old.txt
$ find . -type f -exec /tmp/proc.sh {} \;

Apakah ada penggantian unix-style yang lebih sederhana untuk: cat * | sort -uketika jumlah file mencapai MAX_ARG? Rasanya akward menulis skrip shell kecil untuk tugas umum seperti itu.

malat
sumber
2
Apakah diperlukan penggabungan? sortmelakukannya secara otomatis untuk input file multipel .. tapi kemudian sort -u *akan gagal Argument list too longjuga saya kira
Sundeep

Jawaban:

8

Dengan GNU sort, dan sebuah shell di mana printfbuilt-in (semua yang seperti POSIX saat ini kecuali beberapa varian pdksh):

printf '%s\0' * | sort -u --files0-from=- > output

Sekarang, masalah dengan itu adalah karena dua komponen dari pipa itu dijalankan secara bersamaan dan independen, pada saat yang kiri memperluas *gumpalan, yang tepat mungkin telah membuat outputfile yang dapat menyebabkan masalah (mungkin tidak dengan di -usini) seperti halnya outputfile input dan output, jadi Anda mungkin ingin agar outputnya pergi ke direktori lain ( > ../outputmisalnya), atau pastikan glob tidak cocok dengan file output.

Cara lain untuk mengatasinya dalam contoh ini adalah dengan menuliskannya:

printf '%s\0' * | sort -u --files0-from=- -o output

Dengan begitu, ini sortterbuka outputuntuk menulis dan (dalam pengujian saya), itu tidak akan melakukannya sebelum menerima daftar lengkap file (begitu lama setelah gumpalan telah diperluas). Ini juga akan menghindari clobbering outputjika tidak ada file input yang dapat dibaca.

Cara lain untuk menulisnya dengan zshataubash

sort -u --files0-from=<(printf '%s\0' *) -o output

Itu menggunakan proses substitusi (di mana <(...)digantikan oleh path file yang merujuk ke ujung pipa membaca printfuntuk menulis). Fitur itu berasal ksh, tetapi kshbersikeras membuat perluasan <(...)argumen terpisah ke perintah sehingga Anda tidak dapat menggunakannya dengan --option=<(...)sintaks. Akan bekerja dengan sintaks ini:

sort -u --files0-from <(printf '%s\0' *) -o output

Perhatikan bahwa Anda akan melihat perbedaan dari pendekatan yang mengumpankan output catpada file dalam kasus di mana ada file yang tidak berakhir dengan karakter baris baru:

$ printf a > a
$ printf b > b
$ printf '%s\0' a b | sort -u --files0-from=-
a
b
$ printf '%s\0' a b | xargs -r0 cat | sort -u
ab

Perhatikan juga bahwa sortmengurutkan menggunakan algoritme collation di locale ( strcollate()), dan sort -umelaporkan salah satu dari setiap set baris yang mengurutkan yang sama dengan algoritma itu, bukan garis unik pada level byte. Jika Anda hanya peduli tentang garis yang unik pada tingkat byte dan tidak terlalu peduli tentang urutannya, Anda mungkin ingin memperbaiki lokal ke C di mana penyortiran didasarkan pada nilai byte ( memcmp(); yang mungkin akan mempercepat segalanya secara signifikan):

printf '%s\0' * | LC_ALL=C sort -u --files0-from=- -o output
Stéphane Chazelas
sumber
Terasa lebih alami untuk menulis, ini juga memberi kesempatan bagi Anda sortuntuk mengurangi konsumsi memorinya. Saya masih menemukan printf '%s\0' *sedikit rumit untuk diketik.
malat
Anda bisa menggunakan find . -type f -maxdepth 1 -print0bukan printf '%s\0' *, tapi saya tidak bisa mengklaim itu lebih mudah untuk diketik. Dan yang terakhir lebih mudah untuk didefinisikan sebagai alias, tentu saja!
Toby Speight
@TobySpeight echomemang memiliki -n, saya lebih suka sesuatu seperti printf -0 %sini tampaknya tingkat yang sedikit lebih rendah daripada'%s\0'
malat
@Toby, -maxdepthdan -print0merupakan ekstensi GNU (meskipun banyak didukung hari ini). Dengan yang lain find(walaupun jika Anda memiliki jenis GNU, Anda kemungkinan juga akan menemukan GNU), Anda dapat melakukannya LC_ALL=C find . ! -name . -prune -type f ! -name '.*' -exec printf '%s\0' {} +( LC_ALL=Cmasih mengecualikan file tersembunyi yang berisi karakter yang tidak valid, bahkan dengan GNU find), tetapi itu sedikit berlebihan ketika Anda secara umum memiliki printfbuiltin.
Stéphane Chazelas
2
@malat, Anda selalu dapat mendefinisikan print0fungsi sebagai print0() { [ "$#" -eq 0 ] || printf '%s\0' "$@";}dan kemudianprint0 * | sort...
Stéphane Chazelas
11

Perbaikan sederhana, bekerja setidaknya di Bash, karena printfsudah dibangun, dan batas argumen baris perintah tidak berlaku untuk itu:

printf "%s\0" * | xargs -0 cat | sort -u > /tmp/bla.txt

( echo * | xargsjuga akan berfungsi, kecuali untuk penanganan nama file dengan spasi putih, dll.)

ilkkachu
sumber
Ini sepertinya jawaban yang lebih baik daripada yang diterima, karena tidak memerlukan catproses terpisah untuk setiap file.
LarsH
4
@ LarsH, find -exec {} +mengumpulkan banyak file per satu eksekusi. Dengan find -exec \;itu akan menjadi satu kucing per file.
ilkkachu
Ah, senang tahu. (Padding)
LarsH
9
find . -maxdepth 1 -type f ! -name ".*" -exec cat {} + | sort -u -o /path/to/sorted.txt

Ini akan menggabungkan semua file biasa yang tidak tersembunyi di direktori saat ini dan mengurutkan konten gabungannya (sambil menghapus garis yang digandakan) ke dalam file /path/to/sorted.txt.

Kusalananda
sumber
Saya mencoba menggunakan hanya dua file sekaligus untuk menghindari konsumsi banyak memori (jumlah file saya agak besar). Apakah Anda yakin |operasi rantai akan benar membatasi penggunaan memori?
malat
2
@malat sortakan melakukan semacam out-of-core jika persyaratan memori memerlukannya. Sisi kiri pipa akan mengkonsumsi sangat sedikit memori dibandingkan.
Kusalananda
1

Efisiensi adalah istilah relatif sehingga Anda benar-benar harus menentukan faktor mana yang ingin Anda kurangi; cpu, memori, disk, waktu dll. Demi argumen, saya akan berasumsi bahwa Anda ingin meminimalkan penggunaan memori dan bersedia menghabiskan lebih banyak siklus cpu untuk mencapainya. Solusi seperti yang diberikan oleh Stéphane Chazelas bekerja dengan baik

sort -u --files0-from <(printf '%s\0' *) > ../output

tetapi mereka menganggap bahwa file teks individu memiliki tingkat keunikan yang tinggi untuk memulai. Jika tidak, yaitu setelah

sort -u < sample.txt > sample.srt

sample.srt lebih dari 10% lebih kecil dari sample.txt maka Anda akan menghemat memori yang signifikan dengan menghapus duplikat di dalam file sebelum Anda bergabung. Anda juga akan menghemat lebih banyak memori dengan tidak merantai perintah yang berarti hasil dari proses yang berbeda tidak perlu berada di memori pada saat yang sama.

find /somedir -maxdepth 1 type f -exec sort -u -o {} {} \;
sort -u --files0-from <(printf '%s\0' *) > ../output
Paul Smith
sumber
1
Penggunaan memori jarang menjadi perhatian sortkarena sortresor menggunakan file sementara ketika penggunaan memori melampaui ambang batas (biasanya relatif kecil). base64 /dev/urandom | sort -uakan mengisi disk Anda tetapi tidak menggunakan banyak memori.
Stéphane Chazelas
Yah, setidaknya itu adalah kasus sebagian besar sortimplementasi termasuk yang asli di Unix v3 pada tahun 1972, tetapi tampaknya bukan dari busybox sort. Mungkin karena itu dimaksudkan untuk berjalan pada sistem kecil yang tidak memiliki penyimpanan permanen.
Stéphane Chazelas
Perhatikan bahwa yes | sort -u(semua data duplikat) tidak harus menggunakan lebih dari beberapa byte memori, apalagi disk. Tetapi sortsetidaknya dengan GNU dan Solaris , kami melihatnya menulis banyak file besar 2 byte /tmp( y\nuntuk setiap beberapa megabita input) sehingga akhirnya akan mengisi disk.
Stéphane Chazelas
0

Seperti @ilkkachu, tetapi kucing (1) tidak perlu:

printf "%s\0" * | xargs -0 sort -u

Juga, Jika datanya terlalu lama, Anda mungkin ingin menggunakan opsi sortir (1) --parallel = N

Ketika N adalah jumlah CPU yang dimiliki komputer Anda

Udi
sumber