Jalankan perintah pada semua file dalam direktori

290

Bisakah seseorang tolong berikan kode untuk melakukan hal berikut: Asumsikan ada direktori file, yang semuanya perlu dijalankan melalui program. Program mengeluarkan hasil ke standar keluar. Saya membutuhkan skrip yang akan masuk ke direktori, menjalankan perintah pada setiap file, dan menyatukan output menjadi satu file output besar.

Misalnya, untuk menjalankan perintah pada 1 file:

$ cmd [option] [filename] > results.out
themaestro
sumber
3
Saya ingin menambahkan pertanyaan. Bisakah itu dilakukan menggunakan xargs? mis., ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
Ozair Kafray
2
Itu bisa, tetapi Anda mungkin tidak ingin menggunakanls untuk mengemudi xargs. Jika cmdditulis dengan kompeten, mungkin Anda bisa melakukannya cmd <wildcard>.
tripleee

Jawaban:

425

Kode bash berikut akan mengirimkan $ file ke perintah di mana $ file akan mewakili setiap file dalam / dir

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Contoh

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt
Andrew Logvinov
sumber
23
Jika tidak ada file di dalamnya /dir/, maka loop masih berjalan sekali dengan nilai '*' untuk $file, yang mungkin tidak diinginkan. Untuk menghindari ini, aktifkan nullglob selama durasi loop. Tambahkan baris ini sebelum loop shopt -s nullglobdan baris ini setelah loop shopt -u nullglob #revert nullglob back to it's normal default state.
Stew-au
43
+1, Dan itu hanya membebani saya seluruh koleksi wallpaper saya. semua orang setelah saya, gunakan doublequotes. "$ file"
Behrooz
Jika file output sama di dalam loop, itu jauh lebih efisien untuk mengarahkan ulang di luar loop done >results.out(dan mungkin Anda dapat menimpa alih-alih menambahkan, seperti yang saya asumsikan di sini).
tripleee
Bagaimana Anda mendapatkan file hasil individual yang diberi nama khusus untuk file input mereka?
Timothy Swan
1
hati-hati dengan menggunakan perintah ini untuk sejumlah besar file dalam dir. Gunakan temukan -exec sebagai gantinya.
kolisko
181

Bagaimana dengan ini:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1Argumen mencegah penemuan turun secara rekursif ke subdirektori mana pun. (Jika Anda ingin direktori bersarang tersebut diproses, Anda dapat mengabaikan ini.)
  • -type -f menetapkan bahwa hanya file biasa yang akan diproses.
  • -exec cmd option {}memintanya untuk menjalankan cmddengan ditentukan optionuntuk setiap file yang ditemukan, dengan nama file diganti{}
  • \; menunjukkan akhir dari perintah.
  • Akhirnya, output dari semua cmdeksekusi individu diarahkan ke results.out

Namun, jika Anda peduli tentang urutan file diproses, Anda mungkin lebih baik menulis satu lingkaran. Saya pikir findmemproses file dalam urutan inode (meskipun saya bisa salah tentang itu), yang mungkin bukan yang Anda inginkan.

Jim Lewis
sumber
1
Ini adalah cara yang benar untuk memproses file. Menggunakan for for rawan kesalahan karena banyak alasan. Penyortiran juga dapat dilakukan dengan menggunakan perintah lain seperti statdan sort, yang tentu saja tergantung pada apa yang menyortir kriteria.
tuxdna
1
jika saya ingin menjalankan dua perintah, bagaimana saya akan menautkannya setelah -execopsi? Apakah saya harus membungkusnya dengan tanda kutip tunggal atau sesuatu?
frei
findselalu merupakan opsi terbaik karena Anda dapat memfilter menurut pola nama file dengan opsi -namedan Anda dapat melakukannya dalam satu perintah.
João Pimentel Ferreira
3
@frei jawaban untuk pertanyaan Anda ada di sini: stackoverflow.com/a/6043896/1243247 tetapi pada dasarnya hanya menambahkan -execopsi:find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
João Pimentel Ferreira
2
bagaimana Anda bisa merujuk nama file sebagai opsi?
Toskan
54

Saya melakukan ini pada pi raspberry saya dari baris perintah dengan menjalankan:

for i in *;do omxplayer "$i";done
robgraves
sumber
7

Jawaban yang diterima / terpilih adalah bagus, tetapi mereka kekurangan beberapa detail yang rumit. Posting ini membahas kasus-kasus tentang bagaimana menangani dengan lebih baik ketika ekspansi shell-path-name (glob) gagal, ketika nama-nama file mengandung baris baru / simbol dasbor dan memindahkan output perintah re-direction keluar dari for-loop saat menulis hasil ke mengajukan.

Ketika menjalankan ekspansi shell glob menggunakan *ada kemungkinan ekspansi gagal jika tidak ada file yang ada dalam direktori dan string glob yang tidak diperluas akan diteruskan ke perintah yang akan dijalankan, yang bisa memiliki hasil yang tidak diinginkan. The bashshell menyediakan pilihan shell diperpanjang untuk menggunakan ini nullglob. Jadi loop pada dasarnya menjadi sebagai berikut di dalam direktori yang berisi file-file Anda

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done

Ini memungkinkan Anda keluar dari loop for aman ketika ekspresi ./*tidak mengembalikan file apa pun (jika direktori kosong)

atau dengan cara POSIX compliant ( nullglobadalah bashkhusus)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done

Ini memungkinkan Anda masuk ke dalam loop ketika ekspresi gagal untuk sekali dan kondisi [ -f "$file" ]memeriksa apakah string yang tidak diperluas ./*adalah nama file yang valid di direktori itu, yang tidak akan. Jadi pada kegagalan kondisi ini, menggunakan continuekami melanjutkan kembali ke forloop yang tidak akan berjalan selanjutnya.

Perhatikan juga penggunaan --sesaat sebelum melewati argumen nama file. Ini diperlukan karena seperti yang disebutkan sebelumnya, nama file shell dapat berisi tanda hubung di mana saja dalam nama file. Beberapa perintah shell menafsirkannya dan memperlakukannya sebagai opsi perintah ketika nama tidak dikutip dengan benar dan mengeksekusi perintah berpikir jika bendera disediakan.

Tanda- --tanda akhir opsi baris perintah dalam kasus yang berarti, perintah tidak boleh mengurai string di luar titik ini sebagai flag perintah tetapi hanya sebagai nama file.


Mengutip nama file dengan benar memecahkan kasus ketika nama tersebut mengandung karakter gumpal atau spasi putih. Tetapi nama file * nix juga dapat berisi baris baru di dalamnya. Jadi kami membatasi nama file dengan satu-satunya karakter yang tidak dapat menjadi bagian dari nama file yang valid - byte nol ( \0). Karena secara bashinternal menggunakan Cstring gaya di mana byte nol digunakan untuk menunjukkan akhir string, itu adalah kandidat yang tepat untuk ini.

Jadi menggunakan printfopsi shell untuk membatasi file dengan byte NULL ini menggunakan -dopsi readperintah, bisa kita lakukan di bawah ini

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

The nullglobdan printfmelilit (..)yang berarti mereka pada dasarnya dijalankan dalam sub-shell (shell anak), karena untuk menghindari nullglobpilihan untuk merenungkan shell orang tua, setelah keluar perintah. The -d ''pilihan untuk readperintah tidak POSIX compliant, sehingga membutuhkan bashshell untuk ini harus dilakukan. Menggunakan findperintah ini dapat dilakukan sebagai

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)

Untuk findimplementasi yang tidak mendukung -print0(selain implementasi GNU dan FreeBSD), ini dapat ditiru dengan menggunakanprintf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

Perbaikan penting lainnya adalah memindahkan re-direction dari for-loop untuk mengurangi jumlah file I / O yang tinggi. Ketika digunakan di dalam loop, shell harus menjalankan system-calls dua kali untuk setiap iterasi for-loop, sekali untuk membuka dan sekali untuk menutup deskriptor file yang terkait dengan file. Ini akan menjadi leher botol pada kinerja Anda untuk menjalankan iterasi besar. Saran yang disarankan adalah memindahkannya di luar loop.

Memperluas kode di atas dengan perbaikan ini, bisa Anda lakukan

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out

yang pada dasarnya akan meletakkan isi perintah Anda untuk setiap iterasi input file Anda ke stdout dan ketika loop berakhir, buka file target sekali untuk menulis isi stdout dan simpan. Setara findversi yang sama akan

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
Inian
sumber
1
+1 untuk memeriksa apakah file tersebut ada. Jika mencari di dir yang tidak ada, $ file berisi string regex "/ invald_dir / *" bukan nama file yang valid.
cdalxndr
3

Salah satu cara cepat dan kotor yang terkadang menyelesaikan pekerjaan adalah:

find directory/ | xargs  Command 

Misalnya untuk menemukan jumlah baris di semua file di direktori saat ini, Anda dapat melakukan:

find . | xargs wc -l
Rahul
sumber
8
@ Hubert Mengapa Anda memiliki baris baru di nama file Anda ?!
musicin3d
2
ini bukan pertanyaan "mengapa", ini pertanyaan tentang kebenaran - nama file tidak harus menyertakan karakter yang dapat dicetak, mereka bahkan tidak harus menjadi urutan UTF-8 yang valid. Juga, apa yang baris baru sangat tergantung pengkodean, satu pengkodean ♀ adalah baris baru yang lain. Lihat halaman kode 437
Hubert Kario
2
cmon, benarkah? ini bekerja 99,9% dari waktu, dan dia mengatakan "cepat dan kotor"
Edoardo
Saya bukan penggemar skrip Bash "cepat dan kotor" (AKA "rusak"). Cepat atau lambat itu berakhir dengan hal-hal seperti terkenal "Pindah ~/.local/share/steam. Berlayar. Ini menghapus semua yang ada di sistem yang dimiliki oleh pengguna." laporan bug.
Mengurangi aktivitas
Ini juga tidak akan berfungsi dengan file yang memiliki spasi di namanya.
Shamas S - Pasang kembali Monica
2

Saya perlu menyalin semua file .md dari satu direktori ke direktori lain, jadi inilah yang saya lakukan.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Yang cukup sulit dibaca, jadi mari kita jabarkan.

cd pertama ke direktori dengan file Anda,

for i in **/*.md; untuk setiap file dalam pola Anda

mkdir -p ../docs/"$i"buat direktori itu di folder dokumen di luar folder yang berisi file Anda. Yang membuat folder ekstra dengan nama yang sama dengan file itu.

rm -r ../docs/"$i" hapus folder tambahan yang dibuat sebagai akibat dari mkdir -p

cp "$i" "../docs/$i" Salin file yang sebenarnya

echo "$i -> ../docs/$i" Echo apa yang kamu lakukan

; done Hidup bahagia selamanya

Eric Wooley
sumber
Catatan: agar **dapat bekerja, globstaropsi shell harus diatur:shopt -s globstar
Hubert Kario
2

Kamu bisa memakai xarg

ls | xargs -L 1 -d '\n' your-desired-command

-L 1 menyebabkan lulus 1 item sekaligus

-d '\n'membuat output lsdibagi berdasarkan baris baru.

Al Mamun
sumber
1

Berdasarkan pendekatan @ Jim Lewis:

Berikut ini adalah solusi cepat menggunakan finddan juga menyortir file berdasarkan tanggal modifikasinya:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

Untuk penyortiran, lihat:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time

tuxdna
sumber
ini tidak akan berfungsi jika file memiliki baris baru dalam nama mereka
Hubert Kario
1
@HubertKario Anda mungkin ingin membaca lebih lanjut tentang -print0untuk finddan -0untuk xargsyang menggunakan karakter null bukan spasi (termasuk baris baru).
tuxdna
ya, menggunakan -print0adalah sesuatu yang membantu, tetapi seluruh pipa perlu menggunakan sesuatu seperti ini, dan sortbukan
Hubert Kario
1

saya pikir solusi sederhana adalah:

sh /dir/* > ./result.txt
yovie
sumber
2
Apakah Anda memahami pertanyaan dengan benar? Ini hanya akan mencoba menjalankan setiap file dalam direktori melalui shell - seolah-olah itu adalah skrip.
rdas
1

Maxdepth

Saya menemukan ini bekerja dengan baik dengan jawaban Jim Lewis tambahkan sedikit seperti ini:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Sortir Pesanan

Jika Anda ingin mengeksekusi dalam urutan, modifikasi seperti ini:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Sebagai contoh, ini akan dijalankan dengan urutan sebagai berikut:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Kedalaman tidak terbatas

Jika Anda ingin mengeksekusi di kedalaman yang tidak terbatas dengan kondisi tertentu, Anda dapat menggunakan ini:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

kemudian letakkan di atas setiap file di direktori anak seperti ini:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

dan di suatu tempat di badan file induk:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
Chetabahana
sumber