Uji apakah sebuah bola memiliki kecocokan di bash

223

Jika saya ingin memeriksa keberadaan satu file, saya dapat mengujinya menggunakan test -e filenameatau [ -e filename ].

Andaikata saya memiliki bola dunia dan saya ingin tahu apakah ada file yang namanya cocok dengan bola itu. Glob dapat mencocokkan 0 file (dalam hal ini saya tidak perlu melakukan apa pun), atau dapat mencocokkan 1 file atau lebih (dalam hal ini saya perlu melakukan sesuatu). Bagaimana saya bisa menguji apakah ada gumpalan yang cocok? (Saya tidak peduli berapa banyak pertandingan yang ada, dan akan lebih baik jika saya bisa melakukan ini dengan satu ifpernyataan dan tidak ada loop (hanya karena saya menemukan yang paling mudah dibaca).

( test -e glob*gagal jika glob cocok dengan lebih dari satu file.)

Ken Bloom
sumber
3
Saya menduga jawaban saya di bawah ini 'benar benar' dengan cara yang semua orang peretasan. Ini adalah shell-built-line satu baris yang telah ada selamanya dan tampaknya menjadi 'alat yang dimaksudkan untuk pekerjaan khusus ini'. Saya khawatir bahwa pengguna akan secara keliru merujuk jawaban yang diterima di sini. Siapa saja, jangan ragu untuk mengoreksi saya dan saya akan menarik komentar saya di sini, saya senang salah dan belajar darinya. Jika perbedaannya tidak tampak begitu drastis, saya tidak akan mengangkat masalah ini.
Brian Chrisman
1
Solusi favorit saya untuk pertanyaan ini adalah perintah find yang bekerja di semua shell (bahkan shell non-Bourne) tetapi membutuhkan GNU find, dan perintah compgen yang jelas-jelas merupakan Bashism. Sayang sekali saya tidak bisa menerima kedua jawaban.
Ken Bloom
Catatan: Pertanyaan ini telah diedit sejak ditanya. Judul aslinya adalah "Uji apakah sebuah bola memiliki kecocokan di bash". Shell spesifik, 'bash', dijatuhkan dari pertanyaan setelah saya menerbitkan jawaban saya. Pengeditan judul pertanyaan membuat jawaban saya keliru. Saya harap seseorang dapat mengubah atau setidaknya mengatasi perubahan ini.
Brian Chrisman

Jawaban:

178

Solusi spesifik Bash :

compgen -G "<glob-pattern>"

Keluar dari pola atau itu akan diperluas menjadi pertandingan.

Status yang keluar adalah:

  • 1 untuk tidak cocok,
  • 0 untuk 'satu atau lebih yang cocok'

stdoutadalah daftar file yang cocok dengan glob .
Saya pikir ini adalah pilihan terbaik dalam hal keringkasan dan meminimalkan potensi efek samping.

UPDATE : Contoh penggunaan diminta.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi
Brian Chrisman
sumber
9
Perhatikan bahwa itu compgenadalah perintah built-in khusus bash dan bukan bagian dari perintah built-in standar Unix shell standar POSIX. pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/... Oleh karena itu, hindari menggunakannya dalam skrip di mana portabilitas ke kulit lain menjadi perhatian.
Diomidis Spinellis
1
Sepertinya saya bahwa efek yang sama tanpa bash builtins adalah dengan menggunakan perintah lain yang bertindak pada gumpal dan gagal jika tidak ada file yang cocok, seperti ls: if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi- mungkin berguna untuk kode golf? Gagal jika ada file dengan nama yang sama dengan glob, yang seharusnya tidak cocok dengan glob, tetapi jika itu masalahnya Anda mungkin memiliki masalah yang lebih besar.
Dewi Morgan
4
@DewiMorgan Ini lebih sederhana:if ls /tmp/*Files &> /dev/null; then echo exists; fi
Clay Bridges
Untuk detailnya compgen, lihat man bashatau denganhelp compgen
el-teedee
2
ya, kutip atau wildcard nama file akan diperluas. compgen "dir / *. ext"
Brian Chrisman
169

Opsi shell nullglob memang bashism.

Untuk menghindari perlunya menyimpan dan memulihkan keadaan nullglob yang membosankan, saya hanya akan menyetelnya di dalam subkulit yang memperluas glob:

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

Untuk portabilitas yang lebih baik dan globbing yang lebih fleksibel, gunakan temukan:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

Eksplisit -print -digunakan tindakan untuk menemukan alih-alih tindakan implisit- print default sehingga menemukan akan berhenti segera setelah menemukan file pertama yang cocok dengan kriteria pencarian. Di mana banyak file cocok, ini harus berjalan lebih cepat daripada echo glob*atau ls glob*dan juga menghindari kemungkinan melebih-lebihkan baris perintah yang diperluas (beberapa shell memiliki batas panjang 4K).

Jika menemukan terasa seperti berlebihan dan jumlah file yang cocok mungkin kecil, gunakan stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi
flabdablet
sumber
10
findtampaknya benar sekali. Ia tidak memiliki case corner, karena shell tidak melakukan ekspansi (dan mengirimkan glob yang tidak diekspansi ke beberapa perintah lain), ia bisa dibawa-bawa di antara shell (walaupun tampaknya tidak semua opsi yang Anda gunakan ditentukan oleh POSIX), dan ini lebih cepat daripada ls -d glob*(jawaban yang diterima sebelumnya) karena berhenti ketika mencapai pertandingan pertama.
Ken Bloom
1
Perhatikan bahwa jawaban ini mungkin memerlukan a shopt -u failglobkarena opsi-opsi ini tampaknya saling bertentangan.
Calimo
The findsolusi akan cocok dengan nama file tanpa karakter segumpal juga. Dalam hal ini, itulah yang saya inginkan. Hanya sesuatu yang harus diperhatikan.
We Are All Monica
1
Karena orang lain memutuskan untuk mengedit jawaban saya untuk membuatnya mengatakan itu, tampaknya.
flabdablet
1
unix.stackexchange.com/questions/275637/… membahas cara mengganti -maxdepthopsi untuk menemukan POSIX.
Ken Bloom
25
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi
miku
sumber
2
Untuk menghindari kemungkinan kesalahan “tidak ada kecocokan” nullglobdaripada memeriksa untuk melihat apakah satu hasil sama dengan pola itu sendiri. Beberapa pola dapat mencocokkan nama yang persis sama dengan pola itu sendiri (misalnya a*b; tetapi tidak misalnya a?batau [a]).
Chris Johnsen
Saya kira ini gagal pada kesempatan yang sangat tidak mungkin bahwa sebenarnya ada file bernama seperti glob. (misalnya seseorang berlari touch '*py'), tetapi ini memang menunjukkan saya ke arah lain yang baik.
Ken Bloom
Saya suka yang ini sebagai versi paling umum.
Ken Bloom
Dan juga yang terpendek. Jika Anda hanya mengharapkan satu pertandingan, Anda dapat menggunakan "$M"singkatan untuk "${M[0]}". Jika tidak, Anda sudah memiliki ekspansi glob dalam sebuah variabel array, jadi Anda harus mengalihkannya ke hal-hal lain sebagai daftar, alih-alih membuatnya mengembangkan kembali glob.
Peter Cordes
Bagus. Anda dapat menguji M lebih cepat (lebih sedikit byte dan tanpa memunculkan [proses) denganif [[ $M ]]; then ...
Tobia
22

saya suka

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

Ini bisa dibaca dan efisien (kecuali ada banyak file).
Kelemahan utama adalah jauh lebih halus daripada tampilannya, dan kadang-kadang saya merasa harus menambahkan komentar panjang.
Jika ada kecocokan, "glob*"diperluas oleh shell dan semua kecocokan diteruskan ke exists(), yang memeriksa yang pertama dan mengabaikan sisanya.
Jika tidak ada kecocokan, "glob*"diteruskan ke exists()dan ditemukan juga tidak ada.

Sunting: mungkin ada positif palsu, lihat komentar

Dan Bloch
sumber
13
Ini dapat mengembalikan false positive jika glob adalah sesuatu seperti *.[cC](mungkin tidak ada catau Cfile, tetapi file bernama *.[cC]) atau false negative jika file pertama diperluas dari itu misalnya symlink ke file yang tidak ada atau ke file dalam direktori tempat Anda tidak memiliki akses (Anda ingin menambahkan || [ -L "$1" ]).
Stephane Chazelas
Menarik. Shellcheck melaporkan bahwa globbing hanya berfungsi -ejika ada 0 atau 1 kecocokan. Itu tidak berfungsi untuk beberapa pertandingan, karena itu akan menjadi [ -e file1 file2 ]dan ini akan gagal. Lihat juga github.com/koalaman/shellcheck/wiki/SC2144 untuk alasan dan solusi yang disarankan.
Thomas Praxl
10

Jika Anda memiliki set globfail Anda dapat menggunakan ini gila (yang Anda benar-benar tidak boleh)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

atau

q=( * ) && echo 0 || echo 1
James
sumber
2
Penggunaan noop gagal. Jangan pernah digunakan ... tapi sangat indah. :)
Brian Chrisman
Anda dapat menempatkan shopt di dalam parens. Dengan begitu itu hanya mempengaruhi tes:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet
8

test -e memiliki peringatan malang yang menganggap tautan simbolis yang rusak tidak ada. Jadi, Anda mungkin ingin memeriksa juga.

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi
NerdMachine
sumber
4
Itu masih belum memperbaiki false-positive pada nama file yang memiliki karakter khusus glob di dalamnya, seperti yang ditunjukkan Stephane Chazelas untuk jawaban Dan Bloch. (kecuali jika Anda monyet dengan nullglob).
Peter Cordes
3
Anda harus menghindari -odan -adi test/ [. Misalnya, di sini, itu gagal jika $1adalah =dengan sebagian besar implementasi. Gunakan [ -e "$1" ] || [ -L "$1" ]sebagai gantinya.
Stephane Chazelas
4

Untuk menyederhanakan jawaban The MYYN agak, berdasarkan idenya:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi
Ken Bloom
sumber
4
Tutup, tetapi bagaimana jika Anda cocok [a], memiliki file bernama [a], tetapi tidak ada file bernama a? Saya masih suka nullglobuntuk ini. Beberapa orang mungkin menganggap hal ini sebagai sesuatu yang tidak masuk akal, tetapi kita mungkin benar sepenuhnya sebagaimana masuk akal.
Chris Johnsen
@ sondra.kinsey Itu salah; glob [a]hanya cocok a, bukan nama file literal [a].
tripleee
4

Berdasarkan jawaban flabdablet , bagi saya sepertinya paling mudah (tidak harus tercepat) hanya menggunakan find sendiri, sambil meninggalkan ekspansi glob pada shell, seperti:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

Atau dalam ifsuka:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi
queria
sumber
Ini berfungsi persis seperti versi saya sudah disajikan menggunakan stat; tidak yakin bagaimana menemukan itu "lebih mudah" daripada stat.
flabdablet
3
Ketahuilah bahwa pengalihan &> adalah bashism, dan diam-diam akan melakukan hal yang salah di shell lain.
flabdablet
Ini tampaknya lebih baik daripada findjawaban flabdablet karena ia menerima jalur di glob dan lebih singkat (tidak memerlukan -maxdepthdll). Tampaknya juga lebih baik daripada statjawabannya karena tidak terus melakukan penambahan statpada setiap pertandingan glob tambahan. Saya akan menghargai jika ada yang bisa berkontribusi kasus sudut di mana ini tidak berhasil.
drwatsoncode
1
Setelah pertimbangan lebih lanjut, saya akan menambahkan -maxdepth 0karena memungkinkan lebih banyak fleksibilitas dalam menambahkan kondisi. mis. asumsikan saya ingin membatasi hasil hanya untuk file yang cocok. Saya mungkin mencoba find $glob -type f -quit, tetapi itu akan kembali benar jika glob TIDAK cocok dengan file, tetapi cocok dengan direktori yang berisi file (bahkan secara rekursif). Sebaliknya find $glob -maxdepth 0 -type f -quithanya akan mengembalikan true jika glob itu sendiri cocok dengan setidaknya satu file. Catatan yang maxdepthtidak mencegah glob dari memiliki komponen direktori. (FYI 2>sudah cukup. Tidak perlu untuk &>)
drwatsoncode
2
Tujuan penggunaan finddi tempat pertama adalah untuk menghindari shell menghasilkan dan mengurutkan daftar pertandingan glob yang berpotensi besar; find -name ... -quitakan cocok dengan paling banyak satu nama file. Jika sebuah skrip bergantung pada mengirimkan daftar pencocokan glob yang dihasilkan oleh shell find, memohon untuk findmencapai apa pun kecuali overhead proses-startup yang tidak perlu. Cukup menguji daftar yang dihasilkan secara langsung untuk non-kekosongan akan lebih cepat dan lebih jelas.
flabdablet
4

Saya punya solusi lain:

if [ "$(echo glob*)" != 'glob*' ]

Ini bekerja dengan baik untuk saya. Apakah ada beberapa kasus sudut yang saya lewatkan?

SaschaZorn
sumber
2
Bekerja kecuali jika file tersebut sebenarnya bernama 'glob *'.
Ian Kelling
berfungsi untuk meneruskan glob sebagai variabel - memberikan kesalahan "terlalu banyak argumen" ketika ada lebih dari satu kecocokan. "$ (echo $ GLOB)" tidak mengembalikan satu string atau setidaknya itu tidak ditafsirkan sebagai single tunggal sehingga kesalahan argumen terlalu banyak
DKebler
@DKebler: itu harus ditafsirkan sebagai string tunggal, karena dibungkus dengan tanda kutip ganda.
user1934428
3

Di Bash, Anda bisa menggumpal ke array; jika glob tidak cocok, array Anda akan berisi satu entri yang tidak sesuai dengan file yang ada:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

Catatan: jika Anda telah nullglobmenetapkan, scriptsakan menjadi array kosong, dan Anda harus menguji dengan [ "${scripts[*]}" ]atau dengan [ "${#scripts[*]}" != 0 ]sebagai gantinya. Jika Anda sedang menulis perpustakaan yang harus bekerja dengan atau tanpa nullglob, Anda akan menginginkannya

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

Keuntungan dari pendekatan ini adalah Anda kemudian memiliki daftar file yang ingin Anda kerjakan, daripada harus mengulangi operasi glob.

Toby Speight
sumber
Mengapa, dengan set nullglob, dan array mungkin kosong, Anda tidak bisa mengujinya if [ -e "${scripts[0]}" ]...? Apakah Anda juga memungkinkan untuk kemungkinan setel nounset opsi shell ?
johnraff
@ Johnraff, ya, saya biasanya menganggap nounsetaktif. Juga, mungkin (sedikit) lebih murah untuk menguji string tidak kosong daripada memeriksa keberadaan file. Meskipun demikian, tidak mungkin, mengingat kami baru saja melakukan glob, artinya isi direktori harus segar di cache OS.
Toby Speight
1

Kekejian ini tampaknya berhasil:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

Mungkin membutuhkan bash, bukan sh.

Ini berfungsi karena opsi nullglob menyebabkan glob mengevaluasi ke string kosong jika tidak ada kecocokan. Jadi setiap output yang tidak kosong dari perintah echo menunjukkan bahwa glob cocok dengan sesuatu.

Ryan C. Thompson
sumber
Anda harus menggunakanif [ "`echo *py`" != "*py"]
yegle
1
Itu tidak akan berfungsi dengan benar jika ada file yang dipanggil *py.
Ryan C. Thompson
Jika tidak ada akhir file dengan py, `echo *py`akan dievaluasi *py.
yegle
1
Ya, tetapi itu juga akan melakukannya jika ada satu file yang dipanggil *py, yang merupakan hasil yang salah.
Ryan C. Thompson
Koreksi saya jika saya salah, tetapi jika tidak ada file yang cocok *py, skrip Anda akan menggema "Glob cocok"?
yegle
1

Saya tidak melihat jawaban ini, jadi saya pikir saya akan meletakkannya di sana:

set -- glob*
[ -f "$1" ] && echo "found $@"
Brad Howes
sumber
1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

Penjelasan

Ketika tidak ada kecocokan untuk glob*, maka $1akan mengandung 'glob*'. Tes -f "$1"tidak akan benar karena glob*file tidak ada.

Mengapa ini lebih baik daripada alternatif

Ini bekerja dengan sh dan turunannya: ksh dan bash. Itu tidak membuat sub-shell. $(..)dan `...`perintah membuat sub-shell; mereka melakukan proses, dan karenanya lebih lambat dari solusi ini.

joseyluis
sumber
1

Seperti ini (file uji yang mengandung pattern):

shopt -s nullglob
compgen -W *pattern* &>/dev/null
case $? in
    0) echo "only one file match" ;;
    1) echo "more than one file match" ;;
    2) echo "no file match" ;;
esac

Ini jauh lebih baik daripada compgen -G: karena kita dapat membedakan lebih banyak kasus dan lebih tepatnya.

Dapat bekerja dengan hanya satu wildcard *

Gilles Quenot
sumber
0
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

Perhatikan bahwa ini bisa sangat memakan waktu jika ada banyak kecocokan atau akses file lambat.

Florian Diesch
sumber
1
Ini akan memberikan jawaban yang salah jika pola suka [a]digunakan saat file [a]ada dan file atidak ada. Itu akan mengatakan "ditemukan" meskipun satu-satunya file yang cocok,, asebenarnya tidak ada.
Chris Johnsen
Versi ini seharusnya bekerja dalam POSIX / bin / sh (tanpa bashisme) biasa, dan jika saya membutuhkannya, glob tidak memiliki tanda kurung, dan saya tidak perlu khawatir dengan case yang sangat patologis. Tapi saya kira tidak ada satu cara yang baik untuk menguji apakah ada file yang cocok dengan bola dunia.
Ken Bloom
0
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi
Peter Lyons
sumber
2
Versi ini gagal ketika tepat satu file cocok, tetapi Anda dapat menghindari FOUND = -1 kludge dengan menggunakan nullglobopsi shell.
Ken Bloom
@ Ken: Hmm, saya tidak akan menyebut nullglobkludge. Membandingkan hasil tunggal dengan pola asli adalah kludge (dan cenderung hasil salah), menggunakan nullglobtidak.
Chris Johnsen
@ Chris: Saya pikir Anda salah membaca. Saya tidak menelepon nullglobkludge.
Ken Bloom
1
@ Ken: Memang, saya salah baca. Mohon terima permintaan maaf saya untuk kritik saya yang tidak valid.
Chris Johnsen
-1
(ls glob* &>/dev/null && echo Files found) || echo No file found
Damodharan R
sumber
5
Juga akan mengembalikan false jika ada direktori yang cocok glob*dan misalnya Anda tidak memiliki tulis untuk mendaftar direktori tersebut.
Stephane Chazelas
-1

ls | grep -q "glob.*"

Bukan solusi yang paling efisien (jika ada satu ton file di direktori itu mungkin lambat), tapi itu sederhana, mudah dibaca dan juga memiliki keuntungan bahwa regex lebih kuat daripada pola bash glob.

Jesjimher
sumber
-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true
otocan
sumber
1
Untuk jawaban yang lebih baik cobalah menambahkan beberapa penjelasan ke kode Anda.
Masoud Rahimi