cat sejumlah besar file bersama dalam urutan yang benar


23

Saya memiliki sekitar 15.000 file yang diberi nama file_1.pdb, file_2.pdb, dll saya bisa kucing sekitar beberapa ribu dari ini dalam rangka dengan melakukan:

cat file_{1..2000}.pdb >> file_all.pdb

Namun, jika saya melakukan ini untuk 15.000 file, saya mendapatkan kesalahan

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

Saya telah melihat masalah ini diselesaikan dengan melakukan find . -name xx -exec xxtetapi ini tidak akan mempertahankan urutan yang menggabungkan file. Bagaimana saya bisa mencapai ini?


3
Apa nama file kesepuluh itu? (Atau file apa pun dengan lebih dari satu digit nomor pemesanan).
roaima

Saya (sekarang) memiliki 15.000 file ini di direktori dan cat file_{1..15000}.pdbkonstruk Anda berfungsi dengan baik untuk saya.
roaima

11
tergantung pada sistem apa batasnya. getconf ARG_MAXharus memberi tahu.
ilkkachu

3
Pertimbangkan mengubah pertanyaan Anda menjadi "ribuan" atau "sejumlah besar" file. Mungkin membuat pertanyaan lebih mudah ditemukan untuk orang lain dengan masalah yang sama.
msouth

Jawaban:


49

Menggunakan find, sortdan xargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

The findperintah menemukan semua file yang relevan, kemudian mencetak nama path mereka keluar untuk sortyang melakukan "versi semacam" untuk mendapatkan mereka dalam urutan yang benar (jika nomor di nama file telah nol-diisi dengan lebar tetap kita tidak akan diperlukan -V). xargsmengambil daftar nama jalur yang diurutkan ini dan menjalankannya catdalam jumlah yang sebanyak mungkin.

Ini akan berfungsi bahkan jika nama file berisi karakter aneh seperti baris baru dan spasi. Kami menggunakan -print0dengan finduntuk memberikan sortnama yang diakhiri nul untuk mengurutkan, dan sortmenangani ini menggunakan -z. xargsjuga membaca nama yang diakhiri dengan -0bendera.

Perhatikan bahwa saya sedang menulis hasilnya ke file yang namanya tidak cocok dengan pola file_*.pdb.


Solusi di atas menggunakan beberapa flag non-standar untuk beberapa utilitas. Ini didukung oleh implementasi GNU dari utilitas ini dan setidaknya oleh OpenBSD dan implementasi macOS.

Bendera non-standar yang digunakan adalah

  • -maxdepth 1, untuk membuat findhanya memasuki direktori paling atas tetapi tidak ada subdirektori. POSIXly, gunakanfind . ! -name . -prune ...
  • -print0, untuk membuat findkeluaran nama jalur yang diakhiri nul (ini dianggap oleh POSIX tetapi ditolak). Orang bisa menggunakan -exec printf '%s\0' {} +sebagai gantinya.
  • -z, untuk membuat sortcatatan nul-terminated. Tidak ada kesetaraan POSIX.
  • -V, untuk membuat sortsemacam mis 200setelah 3. Tidak ada kesetaraan POSIX, tetapi bisa diganti dengan semacam angka pada bagian-bagian tertentu dari nama file jika nama file memiliki awalan tetap.
  • -0, untuk membuat xargscatatan yang diakhiri nul. Tidak ada kesetaraan POSIX. POSIXly, orang perlu mengutip nama file dalam format yang dikenali oleh xargs.

Jika nama path berperilaku baik, dan jika struktur direktori datar (tanpa subdirektori), maka seseorang dapat melakukannya tanpa flag-flag ini, kecuali -Vdengan sort.


1
Anda tidak perlu penghentian nol yang tidak standar untuk ini. Nama file ini sangat membosankan dan alat POSIX sepenuhnya mampu menangani itu.
Kevin

6
Anda juga dapat menulis ini lebih ringkas dengan spesifikasi penanya sebagai printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, atau bahkan dengan poin Kevin echo file_{1..15000}.pdb | xargs cat,. The findsolusi memiliki jauh lebih banyak overhead karena harus mencari sistem file untuk file-file, tetapi lebih berguna ketika beberapa file mungkin tidak ada.
kojiro

4
@Kevin sementara apa yang Anda katakan itu benar, mungkin lebih baik memiliki jawaban yang berlaku dalam keadaan yang lebih umum. Dari ribuan orang berikutnya yang memiliki pertanyaan ini, kemungkinan beberapa dari mereka akan memiliki spasi atau apa pun dalam nama file mereka.
msouth

1
@chrylis Pengalihan tidak pernah menjadi bagian dari argumen perintah, dan itu xargsbukan catyang diarahkan (setiap catdoa akan menggunakan xargsoutput standar). Jika kami mengatakannya xargs -0 sh -c 'cat >all.pdb'maka masuk akal untuk menggunakannya >>sebagai ganti >, jika itu yang Anda isyaratkan.
Kusalananda

1
Sepertinya sort -n -k1.6akan berfungsi (untuk yang asli, file_nnnnama file, atau sort -n -k1.5yang tanpa garis bawah).
Scott

14

Dengan zsh( dari mana {1..15000}operator itu berasal):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

Atau untuk semua file_<digits>.pdbfile dalam urutan numerik:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(di mana <x-y>adalah operator glob yang pertandingan pada angka desimal x ke y. Dengan tidak ada xatau y, itu nomor desimal. Setara dengan extendedglob's [0-9]##atau kshglob' s +([0-9])(satu atau lebih digit)).

Dengan ksh93, menggunakan catperintah bawaannya (jadi tidak terpengaruh oleh batas execve()pemanggilan sistem karena tidak ada eksekusi ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

Dengan bash/ zsh/ ksh93(yang support zsh's {x..y}dan memiliki printfbuiltin):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

Pada sistem GNU atau yang kompatibel, Anda juga dapat menggunakan seq:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

Untuk xargssolusi berbasis, perawatan khusus harus diambil untuk nama file yang berisi tanda kutip kosong, tunggal atau ganda atau garis miring terbalik.

Seperti untuk -It's a trickier filename - 12.pdb, gunakan:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb

Ini seq -f | xarg cat > adalah solusi yang paling elegan, dan efektif. (MENURUT OPINI SAYA).
Hastur

Periksa nama file yang lebih rumit ... mungkin '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur

@ Rushur, oops! Ya, terima kasih, saya telah mengubahnya ke sintaks mengutip alternatif. Anda akan bekerja juga.
Stéphane Chazelas

11

A for loop dimungkinkan, dan sangat sederhana.

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

Kelemahannya adalah Anda sering melakukan cathal yang sama. Tetapi jika Anda tidak dapat mengingat dengan tepat bagaimana melakukan hal-hal dengan finddan biaya overhead doa tidak terlalu buruk dalam situasi Anda, maka ada baiknya diingat.


Saya sering menambahkan echo $i;dalam tubuh loop sebagai "indikator kemajuan"
Rolf

3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb

1
awk dapat melakukan pekerjaan seq di sini dan seq dapat melakukan pekerjaan awk ini: seq -f file_%.10g.pdb 15000. Perhatikan bahwa seqini bukan perintah standar.
Stéphane Chazelas

Terima kasih Stéphane - saya pikir seq -f ini cara yang bagus untuk melakukan ini; akan ingat itu.
LarryC

2

Premis

Anda seharusnya tidak melakukan kesalahan itu hanya untuk file 15k dengan format nama spesifik [ 1 , 2 ] .

Jika Anda menjalankan ekspansi itu dari direktori lain dan Anda harus menambahkan path ke setiap file, ukuran perintah Anda akan lebih besar, dan tentu saja itu bisa terjadi.

Solusi menjalankan perintah dari direktori itu.

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

Solusi Terbaik Jika sebaliknya saya menduga buruk dan Anda menjalankannya dari direktori di mana file-file tersebut ...
IMHO solusi terbaik adalah yang Stéphane Chazelas ' :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

dengan printf atau seq; diuji pada file 15k dengan hanya nomor mereka di dalam pra-cache itu bahkan yang lebih cepat (saat ini dan kecuali OP satu dari direktori yang sama di mana file tersebut).

Beberapa kata lagi

Anda harus bisa melewati baris perintah shell Anda lebih lama.
Baris perintah Anda adalah 213914 karakter dan berisi 15003 kata
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... bahkan menambahkan 8 byte untuk setiap kata adalah 333 938 byte (0,3M) jauh di bawah 2097142 (2,1M) yang dilaporkan oleh ARG_MAXkernel 3.13.0 atau 2088232 yang sedikit lebih kecil dilaporkan sebagai "Panjang perintah maksimum yang sebenarnya bisa kita dapat gunakan " olehxargs --show-limits

Coba lihat pada sistem Anda untuk output dari

getconf ARG_MAX
xargs --show-limits

Solusi yang dipandu kemalasan

Dalam kasus seperti ini saya lebih suka bekerja dengan balok bahkan karena biasanya keluar solusi waktu yang efisien.
Logikanya (jika ada) adalah saya terlalu malas untuk menulis 1 ... 1000 1001..2000 dll ...
Jadi saya meminta script untuk melakukannya untuk saya.
Hanya setelah saya memeriksa hasilnya sudah benar saya mengarahkan ulang ke skrip.

... tapi Kemalasan adalah kondisi pikiran .
Karena saya alergi xargs(saya benar-benar seharusnya menggunakannya di xargssini) dan saya tidak ingin memeriksa cara menggunakannya, saya segera selesai untuk menemukan kembali roda seperti pada contoh di bawah ini (tl; dr).

Perhatikan bahwa karena nama file dikontrol (tanpa spasi, baris baru ...) Anda dapat dengan mudah menggunakan sesuatu seperti skrip di bawah ini.

tl; dr

Versi 1: lulus sebagai parameter opsional nomor file 1, yang terakhir, ukuran blok, file output

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

Versi 2

Memanggil bash untuk ekspansi (sedikit lebih lambat dalam pengujian saya ~ 20%).

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

Tentu saja Anda dapat maju dan menyingkirkan seq [ 3 ] (dari coreutils) dan bekerja secara langsung dengan variabel dalam bash, atau menggunakan python, atau kompilasi program ac untuk melakukannya [ 4 ] ...


Perhatikan bahwa %gkependekan dari %.6g. Ini akan mewakili 1.000.000 sebagai 1e + 06 misalnya.
Stéphane Chazelas

Orang yang benar-benar malas menggunakan alat yang dirancang untuk tugas mengatasi keterbatasan E2BIG seperti xargs, zsh's zargsatau ksh93's command -x.
Stéphane Chazelas

seqbukan bash builtin, ini adalah perintah dari GNU coreutils. seq -f %g 1000000 1000000menghasilkan 1e + 06 bahkan dalam versi terbaru dari coreutils.
Stéphane Chazelas

@ StéphaneChazelas Kemalasan adalah kondisi pikiran. Aneh untuk mengatakan tetapi saya merasa lebih nyaman ketika saya bisa melihat (dan secara visual memeriksa output dari perintah serial) dan hanya kemudian mengarahkan ulang ke eksekusi. Konstruksi itu membuat saya berpikir kurang dari xarg... tapi saya mengerti itu bersifat pribadi dan mungkin hanya terkait dengan saya.
Hastur

@ StéphaneChazelas Gotcha, benar ... Tetap. Terima kasih. Saya diuji hanya dengan file 15k yang diberikan oleh OP, saya buruk.
Hastur

0

Cara lain untuk melakukannya bisa jadi

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.