Glob dengan Urutan Numerik

28

Saya memiliki daftar file pdf ini dalam direktori:

c0.pdf   c12.pdf  c15.pdf  c18.pdf  c20.pdf  c4.pdf  c7.pdf
c10.pdf  c13.pdf  c16.pdf  c19.pdf  c2.pdf   c5.pdf  c8.pdf
c11.pdf  c14.pdf  c17.pdf  c1.pdf   c3.pdf   c6.pdf  c9.pdf

Saya ingin menggabungkan ini menggunakan ghostscript dalam urutan numerik (mirip dengan ini):

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=out.pdf *.pdf

Tetapi urutan ekspansi shell tidak mereproduksi urutan alami angka tetapi urutan alfabet:

$ for f in *.pdf; do echo $f; done
c0.pdf
c10.pdf
c11.pdf
c12.pdf
c13.pdf
c14.pdf
c15.pdf
c16.pdf
c17.pdf
c18.pdf
c19.pdf
c1.pdf
c20.pdf
c2.pdf
c3.pdf
c4.pdf
c5.pdf
c6.pdf
c7.pdf
c8.pdf
c9.pdf

Bagaimana saya bisa mencapai urutan yang diinginkan dalam ekspansi (jika mungkin tanpa menambahkan 0-pasangan secara manual ke nomor dalam nama file)?

Saya telah menemukan saran untuk digunakan ls | sort -V, tetapi saya tidak bisa menggunakannya untuk kasus penggunaan khusus saya.

moooeeeep
sumber
Anda bisa menggunakan dua angka angka dalam semua kasus, sehingga urutan alfabet akan cocok dengan urutan angka. Kecuali jika Anda ingin melakukan sesuatu dengan cara yang sulit.
Wildcard
1
Setidaknya 3 digit angka! Ingat Y2K.
waltinator

Jawaban:

12

Bergantung pada lingkungan Anda, Anda dapat menggunakan ls -vdengan GNU coreutils, misalnya:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls -v)

Atau jika Anda menggunakan FreeBSD atau OpenBSD versi terbaru:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls | sort -V)
Thor
sumber
ls -vakan natural sort of (version) numbers within textsehingga dapat digunakan juga ...
Sundeep
@Sundeep: Memang, tapi ini tampaknya hanya solusi GNU coreutils.
Thor
yeah, sepertinya GNU spesifik - pubs.opengroup.org/onlinepubs/9699919799
Sundeep
1
@ Simpan: -VFitur sortjuga tidak ditentukan oleh POSIX. Namun, tampaknya telah menyebar lebih jauh, misalnya FreeBSD dan OpenBSD sortmendukungnya.
Thor
oh ok, bisakah kamu menambahkan rincian ini juga? Saya menemukan jawaban ini ketika mencari masalah yang sama (glob dalam urutan numerik) dan melihat lsdigunakan saya memeriksa apakah ada pilihan dengan sendirinya daripada pipa untuk mengurutkan :)
Sundeep
23

Sekali lagi, kualifikasi global zsh datang untuk menyelamatkan.

echo *.pdf(n)
Gilles 'SANGAT berhenti menjadi jahat'
sumber
12

Jika semua file yang dimaksud memiliki awalan yang sama (yaitu, teks sebelum nomor; cdalam hal ini), Anda dapat menggunakan

gs   ... args ...   c? .pdf c ??. pdf

c?.pdfmengembang ke c0.pdf c1.pdf... c9.pdfc??.pdfmemperluas ke c10.pdf c11.pdf... c20.pdf (dan hingga c99.pdf, sebagaimana berlaku). Sementara setiap kata baris perintah yang berisi karakter ekspansi pathname diperluas ke daftar nama file yang diurutkan (disusun) sesuai dengan LC_COLLATEvariabel, daftar yang dihasilkan dari perluasan wildcard yang berdekatan (gumpalan) tidak digabungkan; mereka hanya digabungkan. (Sepertinya saya ingat bahwa halaman shell manual pernah menyatakan ini secara eksplisit, tetapi saya tidak dapat menemukannya sekarang.)

Tentu saja jika file dapat naik c999.pdf, Anda harus menggunakan c?.pdf c??.pdf c???.pdf. Memang, ini bisa membosankan jika Anda memiliki banyak digit. Anda bisa menyingkatnya sedikit; misalnya, untuk (hingga) lima digit, Anda dapat menggunakan c?{,?{,?{,?{,?}}}}.pdf. Jika daftar nama file Anda jarang (misalnya, ada a c0.pdfdan a c12345.pdf, tetapi tidak harus setiap angka di antaranya), Anda mungkin harus mengatur nullglobopsi. Kalau tidak, jika (misalnya) Anda tidak memiliki file dengan angka dua digit, Anda akan mendapatkan c??.pdfargumen literal yang diteruskan ke program Anda.

Jika Anda memiliki beberapa prefiks (misalnya, , , dan , dengan angka satu atau dua digit), Anda dapat menggunakan jelas, pendekatan kekerasan:a<number>.pdfb<number>.pdf c<number>.pdf

a?.pdf a??.pdf b?.pdf b??.pdf c?.pdf c??.pdf

atau runtuh ke {a,b,c}?{,?}.pdf.

G-Man Mengatakan 'Reinstate Monica'
sumber
1
Ini adalah jawaban terbaik karena itu berada di luar klaim penggunaan samar dari ls, stat, atau apa pun; dan juga bekerja di bash seperti yang diminta.
Kyle
5

Jika tidak ada kesenjangan , hal-hal berikut ini bisa membantu (walaupun samar dan tidak kuat tentang kasus tepi dan umum) - hanya untuk mendapatkan ide:

FILES="c0.pdf"
for i in $(seq 1 20); do FILES="${FILES} c${i}.pdf"; done
gs [...args...] $FILES

Jika ada mungkin kesenjangan, beberapa [ -f c${i}.pdf ]cek dapat ditambahkan.

Sunting juga lihat jawaban ini , yang menurut Anda dapat (menggunakan Bash) gunakan

gs [..args..] c{1..20}.pdf
sr_
sumber
Biasanya adalah ide yang bagus untuk mengutip referensi variabel shell Anda (misalnya, "$FILES"dan "$i") kecuali Anda memiliki alasan yang baik untuk tidak melakukannya, dan Anda yakin Anda tahu apa yang Anda lakukan. (Sebaliknya, sementara kawat gigi bisa penting, mereka tidak sepenting tanda kutip, jadi, misalnya, "c$i.pdf"cukup baik.) Perintah seperti , di mana berisi daftar file yang dipisahkan ruang, mungkin tampak seperti alasan yang baik untuk gunakan tanpa mengutipnya (karena tidak akan berfungsi dalam konteks itu). … (Lanjutan)gs  [ …args… ]  $FILES$FILES$FILES"$FILES"
G-Man Mengatakan 'Reinstate Monica'
(Lanjutkan) ... Tetapi lihat implikasi keamanan lupa mengutip variabel dalam bash / POSIX shells , khususnya, jawaban saya untuk itu , untuk catatan tentang bagaimana menangani variabel multi-kata sebagai array dalam bash (misalnya, FILES=("c0.pdf")dan FILES+=("c$i.pdf")); juga jawaban ini , yang menggunakan teknik yang saya sarankan.
G-Man Mengatakan 'Reinstate Monica'
1

Hanya mengutip dan memperbaiki jawaban Thor ... JANGAN PERNAH menguraikan!

Anda dapat menggunakan sort -V(ekstensi non-POSIX untuk mengurutkan):

printf '%s\0' ./* | sort -zV \
    | xargs -0 gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH \
        -sDEVICE=pdfwrite -sOutputFile=out.pdf

(untuk beberapa perintah, tampaknya untuk gs adalah perintah seperti itu, Anda perlu "./ " bukannya " " ... jika yang satu tidak berfungsi, coba yang lain)

Peter
sumber
1
The tidak parse ls keluaran adalah karena ls menampilkan nama file baris baru-dipisahkan sementara baris baru adalah sebagai berlaku sebagai apapun dalam nama file, tapi di sini Anda melakukan hal yang sama dengan stattetapi menambahkan beberapa isu lainnya (seperti masalah dengan nama file mulai dengan -, masalah jika ada terlalu banyak file, statmenjadi perintah non-portabel). Dan karena Anda menggunakan operator split + glob tanpa menyesuaikan IFS atau menonaktifkan gumpalan, Anda masih akan memiliki masalah dengan nama file dengan spasi atau tab atau karakter wildcard.
Stéphane Chazelas
Untuk menggunakan GNU sort -Vandal, Anda akan perlu ${(z)"$(printf '%s\0' * | sort -zV)"}di zsh(meskipun zshmemiliki (n)untuk jenis numerik sudah) atau readarray -td '' files < <(printf '%s\0' * | sort -zV)di bash4.4+.
Stéphane Chazelas
@ StéphaneChazelas terima kasih, dan Anda benar bahwa baris baru dapat menjadi perhatian, tetapi itu bukan satu-satunya alasan untuk tidak mem-parsing ls. Dan ya saya malas dan tidak menambahkan - juga. Tapi saya seharusnya menggunakan printf ... Saya akan mengubahnya.
Peter
untuk lssendiri (yang tanpa -l), apa yang menjadi perhatian lain ? Perhatikan bahwa --tidak akan membantu untuk file yang dipanggil -.
Stéphane Chazelas
@ StéphaneChazelas ada perbedaan lain antara versi ... seperti beberapa cetak "total 0" di sana, dan versi ls terbaru bahkan menempelkan tanda kutip di sekitar hal-hal di mana Anda tidak menginginkannya ... touch \"test\"; ls -1misalnya menunjukkan '"test"'pada ls saya. Ini tidak dimaksudkan untuk diuraikan ... ini adalah antarmuka pengguna, bukan perintah skrip.
Peter