Uji apakah ada file yang cocok dengan pola untuk menjalankan skrip

29

Saya mencoba menulis ifpernyataan untuk menguji apakah ada file yang cocok dengan pola tertentu. Jika ada file teks dalam direktori itu harus menjalankan skrip yang diberikan.

Kode saya saat ini:

if [ -f /*.txt ]; then ./script fi

Tolong beri beberapa ide; Saya hanya ingin menjalankan skrip jika ada .txtdi direktori.

pengguna40952
sumber
3
Apakah Anda yakin "direktori" itu seharusnya /? Juga, Anda kehilangan titik koma sebelumnya fi.
depquid
Solusi kuat terbersih yang saya temui adalah menggunakan findseperti yang dijelaskan di sini di stackoverflow .
Joshua Goldberg

Jawaban:

39
[ -f /*.txt ]

akan mengembalikan true hanya jika ada satu (dan hanya satu) file tidak tersembunyi /yang namanya berakhir .txtdan jika file itu adalah file biasa atau symlink ke file biasa.

Itu karena wildcard diperluas oleh shell sebelum diteruskan ke perintah (di sini [).

Jadi jika ada /a.txtdan /b.txt, [akan diteruskan 5 argumen: [, -f, /a.txt, /b.txtdan ]. [kemudian akan mengeluh karena -fdiberi terlalu banyak argumen.

Jika Anda ingin memeriksa apakah *.txtpolanya meluas ke setidaknya satu file yang tidak disembunyikan (biasa atau tidak):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobadalah bashspesifik, tapi kerang seperti ksh93, zsh, yash, tcshmemiliki pernyataan setara.

Perhatikan bahwa ia menemukan file-file itu dengan membaca konten direktori, itu tidak mencoba dan mengakses file-file itu sama sekali yang membuatnya lebih efisien daripada solusi yang memanggil perintah seperti lsatau statpada daftar file yang dihitung oleh shell.

shSetara standar akan menjadi:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Masalahnya adalah bahwa dengan cangkang Bourne atau POSIX, jika suatu pola tidak cocok, itu mengembang sendiri. Jadi jika *.txtdiperluas *.txt, Anda tidak tahu apakah itu karena tidak ada .txtfile di direktori atau karena ada satu file yang dipanggil *.txt. Menggunakan [*].txt *.txtmemungkinkan untuk membedakan keduanya.

Stéphane Chazelas
sumber
[ -f /*.txt ]cukup cepat dibandingkan dengan compgen.
Daniel Böhmer
@ DanielBöhmer [ -f /*.txt ]akan salah, tetapi dalam pengujian saya pada direktori yang berisi 3425file, 94di antaranya adalah file txt yang tidak tersembunyi, compgen -G "*.txt" > /dev/null 2>&1nampak secepat set -- *.txt; [ "$#" -gt 0 ](20,5 detik untuk keduanya ketika diulang 10.000 kali dalam kasus saya).
Stéphane Chazelas
11

Anda selalu dapat menggunakan find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Penjelasan:

  • find . : cari direktori saat ini
  • -maxdepth 1: jangan mencari subdirektori
  • -type f : hanya mencari file biasa
  • name "*.txt" : cari file yang diakhiri dengan .txt
  • 2>/dev/null : redirect pesan kesalahan ke /dev/null
  • | grep -q . : grep untuk karakter apa pun, akan mengembalikan false jika tidak ada karakter yang ditemukan.
  • && ./script: Jalankan ./scripthanya jika perintah sebelumnya berhasil ( &&)
terdon
sumber
2
findhanya mengembalikan false jika mengalami kesulitan mencari file, tidak jika tidak menemukan file apa pun. Anda ingin menyalurkan output grep -q .untuk memeriksa apakah ia menemukan sesuatu.
Stéphane Chazelas
@StephaneChazelas Anda tentu saja benar. Aneh, saya sudah mengujinya dan sepertinya berhasil. Pasti melakukan sesuatu yang aneh karena tidak lagi. Kapan akan menemukan "kesulitan menemukan file"?
terdon
@terdon, seperti ketika beberapa direktori tidak dapat diakses, atau kesalahan I / O atau kesalahan apa pun yang dikembalikan oleh panggilan sistem apa pun yang dibuatnya. Dalam hal ini, coba sesudahnya chmod a-x ..
Stéphane Chazelas
8

Solusi yang mungkin juga Bash builtin compgen. Perintah itu mengembalikan semua kemungkinan kecocokan untuk pola globbing dan memiliki kode keluar yang menunjukkan apakah ada file yang cocok.

compgen -G "/*.text" > /dev/null && ./script

Saya menemukan pertanyaan ini sambil mencari solusi yang lebih cepat.

Daniel Böhmer
sumber
1
Bagus temukan! Jika Anda berada di lokal multi-byte, Anda dapat sedikit meningkatkannya LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas
7

Berikut ini satu liner untuk melakukannya:

$ ls
file1.pl  file2.pl

file ada

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

file tidak ada

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Pendekatan ini memanfaatkan ||dan &&operator di bash. Ini adalah operator "atau" dan "dan".

Jadi jika perintah stat mengembalikan $?sama dengan 0 maka yang pertama echodipanggil, jika mengembalikan 1, maka yang kedua echodisebut.

kembalikan hasil dari stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Pertanyaan ini banyak dibahas pada stackoverflow:

slm
sumber
1
Mengapa menggunakan standar non statbila ls -dbisa melakukan hal yang sama?
Stéphane Chazelas
Saya pikir ls -ddaftar direktori? Sepertinya tidak berfungsi ketika saya baru saja mencoba daftar direktori dengan file di dalamnya ls -d *.plmisalnya.
slm
Anda dapat mengganti pernyataan di sebelah kiri &&oleh ls *.txtdan itu akan berfungsi juga. Pastikan Anda mengirim stdout dan stderr ke /dev/nullseperti yang disarankan oleh @slm.
unxnut
1
Jika Anda menggunakan ls *.txtdan tidak ada file hadir dalam direktori ini akan kembali $? = 2, yang masih akan bekerja dengan jika kemudian, tapi ini adalah salah satu alasan saya untuk memilih statlebih ls. Saya ingin 0 untuk sukses, dan 1 untuk kegagalan.
slm
ls -dadalah untuk mendaftar direktori alih-alih isinya. Jadi ls -dlakukan saja lstatpada file, seperti halnya GNU stat. Perintah status keluar non-nol yang dikembalikan pada kegagalan adalah spesifik sistem, tidak masuk akal untuk membuat asumsi pada mereka.
Stéphane Chazelas
4

Seperti yang ditunjukkan Chazelas, skrip Anda akan gagal jika ekspansi wildcard cocok dengan lebih dari satu file.

Namun, ada trik yang saya gunakan ( bahkan saya tidak terlalu menyukainya ) untuk berkeliling:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Bagaimana itu bekerja?

Ekspansi wildcard akan cocok dengan array nama file, kami mendapatkan yang pertama jika ada beberapa, jika tidak, nol jika tidak ada kecocokan.

Tuan Pei
sumber
IMO ini adalah jawaban yang paling buruk di sini. Mereka semua tampak sangat mengerikan, seolah-olah fitur dasar hilang dari bahasa.
plugwash
@plugwash itu disengaja ... * skrip nix shell memiliki beberapa kontrol aliran dasar dan beberapa peluang dan akhir lainnya tetapi pada akhirnya tugasnya adalah menyatukan perintah-perintah lain. Jika bash menyebalkan ... itu karena perintah yang Anda gunakan menghisap
cb88
2
Itu logika yang salah (dan Anda kehilangan penawaran). Itu memeriksa apakah file yang cocok pertama adalah file biasa. Ini mungkin file yang tidak biasa tetapi mungkin ada beberapa .txtfile lain yang bertipe biasa . Coba misalnya setelah mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas
1

Sederhana seperti:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l menghitung garis dalam wildcard yang diperluas.

Kim Johansson
sumber
1
Downvote: Ini mengumpulkan sejumlah besar antipattern pemula dalam sejumlah kecil kode. Anda tidak boleh menguraikan lsoutput dan hampir tidak pernah memeriksa $?secara langsung karena ifsudah melakukan itu. Juga, menggunakan wcuntuk melihat apakah sesuatu terjadi juga salah arah.
tripleee
0

Saya suka solusi array sebelumnya, tapi itu bisa menjadi boros dengan banyak file - shell akan menggunakan banyak memori untuk membangun array, dan hanya elemen pertama yang akan diuji.

Berikut adalah struktur alternatif yang saya uji kemarin:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

Nilai keluar dari grep tampaknya menentukan jalur melalui struktur kontrol. Ini juga menguji dengan ekspresi reguler daripada pola shell. Beberapa sistem saya memiliki perintah "pcregrep" yang memungkinkan pertandingan regex yang jauh lebih canggih.

(Saya memang mengedit jawaban ini untuk menghapus "ls" dalam substitusi perintah setelah membaca kritik di atas karena menguraikannya.)

Charlie
sumber
-2

jika Anda ingin menggunakan klausa if, evaluasi penghitungan:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Rusty75
sumber