mengulang melalui `ls` menghasilkan skrip bash shell

93

Apakah ada yang punya skrip shell template untuk melakukan sesuatu dengan lsdaftar nama direktori dan perulangan masing-masing dan melakukan sesuatu?

Saya berencana melakukan ls -1d */untuk mendapatkan daftar nama direktori.

Daniel A. White
sumber

Jawaban:

110

Diedit untuk tidak menggunakan ls di mana gumpalan akan melakukan, seperti yang disarankan @ shawn-j-goff dan lainnya.

Cukup gunakan satu for..do..donelingkaran:

for f in *; do
  echo "File -> $f"
done

Anda dapat mengganti *dengan *.txtatau glob lain yang mengembalikan daftar (file, direktori, atau apa pun dalam hal ini), perintah yang menghasilkan daftar, misalnya $(cat filelist.txt), atau benar-benar menggantinya dengan daftar.

Di dalam doloop, Anda cukup merujuk ke variabel loop dengan awalan tanda dolar (jadi $fdalam contoh di atas). Anda dapat echomelakukannya atau melakukan hal lain yang Anda inginkan.

Misalnya, untuk mengganti nama semua .xmlfile di direktori saat ini menjadi .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Atau bahkan lebih baik, jika Anda menggunakan Bash Anda dapat menggunakan ekspansi parameter Bash daripada memunculkan subkulit:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
CoverosGene
sumber
22
Bagaimana jika nama file memiliki spasi di dalamnya?
Daniel A. White
4
Sayangnya, seperti kata Daniel, kode dalam jawaban ini akan pecah jika ada file atau folder yang berisi spasi atau baris baru di namanya. Ini menunjukkan penyalahgunaan forloop yang sangat umum , dan perangkap khas mencoba mengurai output ls. @ DanielA.White, Anda mungkin mempertimbangkan untuk tidak menerima jawaban ini jika itu tidak membantu (atau berpotensi menyesatkan), karena seperti yang Anda katakan, Anda bertindak berdasarkan direktori. Jawaban Shawn J. Goff harus memberikan solusi yang lebih kuat dan berfungsi untuk masalah Anda.
slhck
4
-∞ Anda tidak harus mengurai lsoutput , Anda tidak harus membaca output dalam satu forlingkaran , Anda harus menggunakan $()alih-alih `` dan Use More Quotes ™ . Saya yakin @CoverosGene bermaksud baik, tetapi ini hanya mengerikan.
l0b0
1
Alternatif yang lebih baik jika Anda benar-benar ingin menggunakannya lsadalah ls -1 | while read line; do stuff; done. Setidaknya itu tidak akan rusak untuk ruang putih.
ejoubaud
1
dengan mv -vAnda tidak perluecho "moved $x -> $t"
DmitrySandalov
68

Menggunakan output lsuntuk mendapatkan nama file adalah ide yang buruk . Ini dapat menyebabkan skrip yang tidak berfungsi dan bahkan berbahaya. Ini karena nama file dapat berisi karakter apa pun kecuali /dan nullkarakternya, dan lstidak menggunakan salah satu dari karakter tersebut sebagai pembatas, jadi jika nama file memiliki spasi atau baris baru, Anda akan mendapatkan hasil yang tidak terduga.

Ada dua cara yang sangat baik untuk mengulang file. Di sini, saya hanya menggunakan echountuk menunjukkan melakukan sesuatu dengan nama file; Anda bisa menggunakan apa saja.

Yang pertama adalah menggunakan fitur globbing asli shell.

for dir in */; do
  echo "$dir"
done

Shell mengembang */menjadi argumen terpisah yang fordibaca loop; bahkan jika ada spasi, baris baru, atau karakter aneh lainnya dalam nama file, forakan melihat setiap nama lengkap sebagai unit atom; itu tidak menguraikan daftar dengan cara apa pun.

Jika Anda ingin pergi secara rekursif ke subdirektori, maka ini tidak akan melakukan kecuali shell Anda memiliki beberapa fitur globbing diperpanjang (seperti bash's globstar. Jika shell Anda tidak memiliki fitur ini, atau jika Anda ingin memastikan bahwa script Anda akan bekerja pada berbagai sistem, maka opsi selanjutnya adalah menggunakan find.

find . -type d -exec echo '{}' \;

Di sini, findperintah akan memanggil echodan memberikan argumen nama file. Ini melakukan ini sekali untuk setiap file yang ditemukannya. Seperti contoh sebelumnya, tidak ada penguraian daftar nama file; sebaliknya, nama file dikirim sepenuhnya sebagai argumen.

Sintaks -execargumen terlihat sedikit lucu. findmengambil argumen pertama setelah -execdan menganggap itu sebagai program untuk dijalankan, dan setiap argumen berikutnya, dibutuhkan sebagai argumen untuk lolos ke program itu. Ada dua argumen khusus yang -execperlu dilihat. Yang pertama adalah {}; argumen ini diganti dengan nama file yang finddihasilkan oleh bagian sebelumnya . Yang kedua adalah ;, yang memberi findtahu ini adalah akhir dari daftar argumen untuk diteruskan ke program; findmembutuhkan ini karena Anda dapat melanjutkan dengan lebih banyak argumen yang ditujukan untuk finddan tidak ditujukan untuk program eksekutif. Alasan untuk itu \adalah bahwa shell juga memperlakukan;khususnya - ia mewakili akhir dari sebuah perintah, jadi kita perlu menghindarinya sehingga shell memberikannya sebagai argumen untuk findtidak menggunakannya untuk dirinya sendiri; Cara lain untuk mendapatkan shell untuk tidak memperlakukannya secara khusus adalah memasukkannya ke dalam tanda kutip: ';'berfungsi sama baiknya \;untuk tujuan ini.

Shawn J. Goff
sumber
2
+1 ini jelas merupakan cara yang harus dilakukan ketika Anda perlu membuat daftar file dan menggunakannya dalam suatu perintah. find -exec dibatasi dengan hanya dapat menjalankan satu perintah. Dengan loop, Anda dapat menyalurkan ke isi hati Anda.
MaQleod
+1. Saya berharap lebih banyak orang akan menggunakan find. Ada keajaiban yang bisa dilakukan, dan bahkan keterbatasan yang dirasakan -execdapat diselesaikan. The -print0pilihan juga berharga untuk digunakan dengan xargs.
ghoti
Opsi loop tidak akan menampilkan direktori tersembunyi. Opsi find tidak akan menampilkan symlink.
jinawee
16

Untuk file dengan spasi di Anda harus memastikan untuk mengutip variabel seperti:

 for i in $(ls); do echo "$i"; done; 

atau, Anda dapat mengubah variabel lingkungan pemisah bidang input (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Akhirnya, tergantung pada apa yang Anda lakukan, Anda bahkan mungkin tidak memerlukan ls:

 for i in *; do echo "$i"; done;
john
sumber
baik penggunaan subkulit dalam contoh pertama
Jeremy L
Mengapa IFS membutuhkan $ setelah penugasan dan sebelum karakter baru?
Andy Ibanez
3
Nama file juga dapat berisi baris baru. Melanggar \ntidak cukup. Tidak pernah merupakan ide yang bagus untuk merekomendasikan parsing output dari ls.
ghoti
4

Hanya untuk menambahkan jawaban CoverosGene, berikut adalah cara untuk mendaftar hanya nama direktori:

for f in */; do
  echo "Directory -> $f"
done
n3o
sumber
1

Mengapa tidak mengatur IFS ke baris baru, lalu menangkap output lsdalam array? Pengaturan IFS ke baris baru harus menyelesaikan masalah dengan karakter lucu dalam nama file; menggunakan lsbisa menyenangkan karena memiliki fasilitas sortir bawaan.

(Dalam pengujian saya mengalami kesulitan pengaturan IFS untuk \ntetapi pengaturannya untuk backspace karya baris baru, seperti yang disarankan di tempat lain di sini):

Misalnya (dengan asumsi lspola pencarian yang diinginkan diteruskan $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Ini sangat berguna pada OS X, misalnya, untuk mengambil daftar file yang diurutkan berdasarkan tanggal pembuatan (terlama ke terbaru), lsperintahnya adalah ls -t -U -r.

orang kaya
sumber
2
Nama file juga dapat berisi baris baru, dan itu sering dilakukan ketika pengguna diizinkan untuk menamai file mereka sendiri. Melanggar \ntidak cukup. Satu-satunya solusi yang valid menggunakan loop for dengan ekspansi pathname, atau find.
ghoti
Satu-satunya cara yang dapat diandalkan untuk mentransfer daftar nama file adalah dengan memisahkannya dengan karakter NUL, karena ini adalah satu-satunya yang pasti tidak terkandung dalam path file.
glglgl
-3

Ini adalah bagaimana saya melakukannya, tetapi mungkin ada cara yang lebih efisien.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt
POHON
sumber
6
Tetap berpegang pada pipa di tempat file:>ls | while read i; do echo filename: $i; done
Jeremy L
Keren. Saya harus mengatakan bahwa Anda juga dapat menggunakan $ EDITOR filelist.txt di antara dua perintah. Banyak hal yang dapat Anda lakukan di editor yang lebih mudah daripada di baris perintah. Namun, tidak relevan dengan pertanyaan ini.
TREE
Solusi Anda sama sekali tidak mengatasi masalah dengan nama file yang mengandung baris baru dan hal-hal mewah lainnya.
glglgl