xargs dengan pengalihan stdin / stdout

21

Saya ingin menjalankan:

./a.out < x.dat > x.ans

untuk setiap * Dat file dalam direktori A .

Tentu, itu bisa dilakukan dengan skrip bash / python / apa pun, tapi saya suka menulis seksi satu baris. Yang bisa saya capai adalah (masih tanpa stdout):

ls A/*.dat | xargs -I file -a file ./a.out

Tapi -a dalam xargs tidak mengerti ganti-str 'file'.

Terima kasih atas bantuan Anda.

Nikolay Vyahhi
sumber
Selain jawaban bagus lainnya di sini, Anda dapat menggunakan opsi -a dari GNU xargs.
James Youngman

Jawaban:

30

Pertama-tama, jangan gunakan lsoutput sebagai daftar file . Gunakan ekspansi shell atau find. Lihat di bawah ini untuk konsekuensi potensial dari penyalahgunaan ls + xargs dan contoh xargspenggunaan yang tepat .

1. Cara sederhana: untuk loop

Jika Anda ingin memproses hanya file-file di bawah A/, maka forloop sederhana harus cukup:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Kenapa tidak   ls | xargs ?

Berikut ini adalah contoh bagaimana hal-hal buruk bisa berubah jika Anda menggunakan lsdengan xargsuntuk pekerjaan itu. Pertimbangkan skenario berikut:

  • pertama, mari kita buat beberapa file kosong:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • lihat file dan tidak mengandung apa pun:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • jalankan perintah ajaib menggunakan xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • hasil:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Jadi, Anda baru saja berhasil menimpa keduanya mypreciousfile.datdan mypreciousfile.dat.ans. Jika ada konten dalam file itu, itu akan dihapus.


2. Menggunakan   xargs : cara yang tepat dengan  find 

Jika Anda ingin tetap menggunakan xargs, gunakan -0(nama yang dihentikan nol):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Perhatikan dua hal:

  1. dengan cara ini Anda akan membuat file dengan .dat.ansakhiran;
  2. ini akan rusak jika beberapa nama file berisi tanda kutip ( ").

Kedua masalah dapat diselesaikan dengan cara doa shell yang berbeda:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Semua dilakukan di dalam find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Ini, sekali lagi, menghasilkan .dat.ansfile dan akan pecah jika nama file berisi ". Untuk melakukannya, gunakan bashdan ubah cara pemanggilannya:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
sumber
+1 untuk menyebutkan tidak mem-parsing output ls.
rahmu
2
Opsi 2 rusak ketika nama file mengandung ".
thiton
2
Poin yang sangat bagus, terima kasih! Saya akan memperbarui sesuai.
rozcietrzewiacz
Saya hanya ingin menyebutkan, bahwa jika zshdigunakan sebagai shell (dan SH_WORD_SPLIT tidak disetel), semua case khusus jahat (spasi putih, "dalam nama file, dll.) Tidak perlu dipertimbangkan. Sepele for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; doneberfungsi dalam semua kasus.
jofel
-1 karena saya mencoba mencari cara melakukan xargs dengan stdin dan pertanyaan saya tidak ada hubungannya dengan file atau find.
Mehrdad
2

Coba lakukan sesuatu seperti ini (sintaks mungkin sedikit berbeda tergantung pada shell yang Anda gunakan):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done

rahmu
sumber
Itu tidak akan berhasil. Itu akan mencoba untuk beroperasi pada hal-hal seperti somefile.dat.datdan mengarahkan semua output ke satu file.
rozcietrzewiacz
Kamu benar. Saya mengedit solusi untuk memperbaikinya.
rahmu
OK - Hampir bagus :) Hanya somefile.dat.ansmenampilkan barang-barang akan terlihat tidak begitu baik.
rozcietrzewiacz
1
Diedit! Saya tidak tahu tentang '%'. Ini bekerja seperti pesona, terima kasih atas tipnya.
rahmu
1
Menambahkan -type fileakan menyenangkan (tidak bisa < directory), dan ini membuat peri-nama-peri-biasa sedih.
Mat
2

Untuk pola sederhana, for loop sesuai:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Untuk kasus yang lebih kompleks di mana Anda memerlukan utilitas lain untuk membuat daftar file (zsh atau bash 4 memiliki pola yang cukup kuat yang jarang perlu Anda temukan, tetapi jika Anda ingin tetap berada dalam shell POSIX atau menggunakan shell cepat seperti dash, Anda perlu mencari apa saja non-sepele), saat membaca paling tepat:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Ini akan menangani spasi, karena membaca (secara default) berorientasi garis. Itu tidak akan menangani baris baru dan tidak akan menangani garis miring terbalik, karena secara default ia menafsirkan urutan melarikan diri (yang sebenarnya memungkinkan Anda untuk melewati baris baru, tetapi menemukan tidak dapat menghasilkan format itu). Banyak shell memiliki -0opsi untuk membaca sehingga Anda dapat menangani semua karakter, tetapi sayangnya itu bukan POSIX.

Jan Hudec
sumber
2

Gunakan GNU Parallel:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Bonus tambahan: Anda menyelesaikan pemrosesan secara paralel.

Tonton video intro untuk mempelajari lebih lanjut: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
sumber
1

Saya pikir Anda memerlukan setidaknya doa shell di xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Sunting: Perlu dicatat bahwa pendekatan ini tidak berfungsi ketika nama file mengandung spasi. Tidak bisa bekerja Bahkan jika Anda menggunakan find -0 dan xargs -0 untuk membuat xargs memahami spasi dengan benar, panggilan shell -c akan mengacaukannya. Namun, OP secara eksplisit meminta solusi xargs, dan ini adalah solusi xargs terbaik yang saya dapatkan. Jika spasi putih dalam nama file mungkin menjadi masalah, gunakan find -exec atau shell loop.

thiton
sumber
@rozcietrzewiacz: Secara umum, tentu, tapi saya berasumsi seseorang mencoba melakukan xargs, voodoo tahu ini. Karena nama file ini mengalami ekspansi shell lain, mereka harus tetap berperilaku baik.
thiton
1
Anda salah tentang ekspansi shell. lsmengeluarkan hal-hal seperti spasi tanpa melarikan diri dan itulah masalahnya.
rozcietrzewiacz
@rozcietrzewiacz: Ya, saya mengerti masalah itu. Sekarang anggap saja Anda akan benar-benar keluar dari ruang-ruang ini dan memasukkannya ke dalam xargs: Mereka diganti dalam string sh's -c, sh tokenizes string -c, dan semuanya rusak.
thiton
Iya nih. Anda lihat sekarang, penguraian lstidak baik. Tetapi xargs dapat digunakan dengan aman dengan menemukan - lihat saran No 2 dalam jawaban saya.
rozcietrzewiacz
1

Tidak perlu menyulitkannya. Anda bisa melakukannya dengan forloop:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

yang ${file%.dat}.anssedikit akan menghapus .datakhiran nama file dari nama file di $filedan bukannya menambahkan .anssampai akhir.

Kusalananda
sumber