bash iterate daftar file, kecuali ketika kosong

33

Saya pikir ini akan sederhana - tetapi terbukti lebih kompleks dari yang saya harapkan.

Saya ingin mengulang semua file dari jenis tertentu dalam direktori, jadi saya menulis ini:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

Ini berfungsi selama ada setidaknya satu file yang cocok dalam direktori. Namun jika tidak ada file yang cocok, saya mendapatkan ini:

current file is *.zip

Saya kemudian mencoba:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

Sementara badan loop tidak dieksekusi ketika tidak ada file, saya mendapatkan kesalahan dari ls:

ls: *.zip: No such file or directory

Bagaimana cara menulis loop yang dengan bersih menangani tidak ada file yang cocok?

symcbean
sumber
7
Tambahkan shopt -s nullglobsebelum menjalankan loop for.
cuonglm
@cuolnglm: dengan seram ini menghasilkan ls mengembalikan nama skrip pelaksana daripada daftar kosong pada kotak RHEL5 ini (bash 3.2.25) jika saya melakukan FILES=ls * .zip ; for fname in "${FILES}"...tetapi berfungsi seperti yang diharapkan denganfor fname in *.zip ; do....
symcbean
3
Gunakan for file in *.zip, bukan `ls ...`. Saran @ cuonglm adalah agar tidak *.zipberkembang ketika pola tidak cocok dengan file apa pun. lstanpa argumen daftar direktori saat ini.
Stéphane Chazelas
1
Pertanyaan ini membahas mengapa parsing output pada lsumumnya harus dihindari: Mengapa tidak parsing ls? ; juga melihat tautan di dekat bagian atas halaman itu ke artikel ParsingLs BashGuide .
PM 2Ring

Jawaban:

34

Di bash, Anda dapat mengatur nullglobopsi sehingga pola yang tidak cocok dengan apa pun "menghilang", daripada diperlakukan sebagai string literal:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

Dalam skrip POSIX shell, Anda hanya memverifikasi yang fnameada (dan pada saat yang sama dengan [ -f ], periksa itu adalah file biasa (atau symlink ke file biasa) dan bukan jenis lain seperti direktori / fifo / perangkat ...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

Ganti [ -f "$fname" ]dengan [ -e "$fname" ] || [ -L "$fname ]jika Anda ingin mengulang semua file (tidak disembunyikan) yang namanya berakhir .zipterlepas dari jenisnya.

Ganti *.zipdengan .*.zip .zip *.zipjika Anda juga ingin mempertimbangkan file tersembunyi yang namanya berakhir .zip.

chepner
sumber
1
shopt -s nullglobtidak bekerja untuk saya di Ubuntu 17.04, tetapi [ -f "$fname" ] || continuebekerja dengan baik.
koppor
@koppor Sepertinya Anda tidak benar-benar menggunakan bash.
chepner
2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

Dalam komentar di sini Anda menyebutkan memohon suatu fungsi ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *
mikeserv
sumber
2

Gunakan temukan

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

Anda HARUS mengekspor fungsi shell Anda export -fagar ini berfungsi. Sekarang findjalankan bashyang mengeksekusi fungsi shell Anda, dan tetap pada level dir saat ini saja.

Dani_l
sumber
Yang berulang melalui subdirektori, dan saya ingin menjalankan fungsi bash (bukan skrip) untuk pertandingan.
symcbean
@symcbean Saya telah mengedit untuk membatasi dir tunggal dan menangani fungsi bash
Dani_l
-3

Dari pada:

FILES=`ls *.zip`

Mencoba:

FILES=`ls * | grep *.zip`

Dengan cara ini jika ls gagal (yang tidak dalam kasus Anda) itu akan menerima output yang gagal dan kembali sebagai variabel kosong.

current file is      <---Blank Here

Anda dapat menambahkan beberapa logika untuk membuatnya mengembalikan "Tidak Ada File Ditemukan"

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

Dengan cara ini jika perintah sebelumnya berhasil (keluar dengan nilai 0) maka itu akan mencetak file saat ini, jika tidak maka akan mencetak "Tidak Ada File Ditemukan"

Kip K
sumber
1
Saya pikir ini ide yang buruk untuk menambahkan satu proses lagi ( grep) daripada mencoba memperbaiki masalah dengan menggunakan alat yang lebih baik ( find) atau mengubah pengaturan yang relevan untuk solusi saat ini (dengan shopt -s nullglob)
Thomas Baruchel
1
Menurut komentar OP pada posting asli mereka, shopt -s nullglobitu tidak berfungsi. Saya mencoba findsambil memverifikasi jawaban saya dan terus gagal. Saya pikir karena ekspor, kata Dani.
Kip K