Bir kürenin bash'ta herhangi bir eşleşmesi olup olmadığını test edin


223

Tek bir dosyanın varlığını kontrol etmek istersem, test -e filenameveya öğesini kullanarak test edebilirim [ -e filename ].

Varsayalım bir glob var ve isimleri glob ile eşleşen herhangi bir dosya olup olmadığını bilmek istiyorum. Glob 0 dosyayla eşleşebilir (bu durumda hiçbir şey yapmam gerekir) veya 1 veya daha fazla dosyayla eşleşebilir (bu durumda bir şey yapmam gerekir). Bir kürenin herhangi bir eşleşmesi olup olmadığını nasıl test edebilirim? (Kaç tane eşleşme olduğunu umursamıyorum ve bunu tek bir ifdeyim ve döngüler olmadan yapabilsem iyi olurdu (sadece bunu en okunabilir bulduğum için).

( test -e glob*glob birden fazla dosyayla eşleşirse başarısız olur.)


3
Aşağıdaki cevabımın, diğerlerinin bir tür hackleme tarzında 'açıkça doğru' olduğundan şüpheleniyorum. Sonsuza kadar süren ve 'bu özel iş için amaçlanan araç' gibi görünen tek satırlı bir kabuk yapılı. Kullanıcıların burada kabul edilen yanıta yanlışlıkla atıfta bulunacağından endişe duyuyorum. Herkes beni düzeltmekten çekinmeyin ve yorumumu buradan geri çekeceğim, yanlış olduğum ve ondan ders aldığım için çok mutluyum. Aradaki fark bu kadar sert olmasaydı, bu konuyu gündeme getirmezdim.
Brian Chrisman

1
Bu soruya sevdiğim çözümleridir bulmak komutu herhangi kabuğu (hatta olmayan Bourne kabukları) çalışır ancak GNU bulmak gerektirir ve compgen komut açıkça Bashism olduğunu. Her iki yanıtı da kabul edemiyorum çok kötü.
Ken Bloom

Not: Bu soru sorulduğundan beri düzenlenmiştir. Orijinal başlık "Bir kürenin bash ile eşleşip eşleşmediğini test edin" idi. Cevabımı yayınladıktan sonra söz konusu mermi 'bash' sorudan düştü. Soru başlığının düzenlenmesi cevabımın hatalı olduğunu gösteriyor. Umarım birisi bu değişikliği değiştirebilir veya en azından değiştirebilir.
Brian Chrisman

Yanıtlar:


178

Bash'e özel çözüm:

compgen -G "<glob-pattern>"

Desenden kaçın yoksa eşleşmelere önceden genişler.

Çıkış durumu:

  • Eşleşmeyenler için 1,
  • 'Bir veya daha fazla eşleşme' için 0

stdoutglob ile eşleşen dosyaların bir listesidir .
Bence bu, özlü olma ve potansiyel yan etkileri en aza indirme açısından en iyi seçenek.

GÜNCELLEME : Örnek kullanım istendi.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi

9
Not compgenbir olan bash komut yerleşik e özgü ve yerleşik Belirttiğiniz komutları POSIX standardı Unix kabuğunun bir parçası değildir. pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/… Bu nedenle, diğer kabuklara taşınabilirliğin önemli olduğu komut dosyalarında kullanmaktan kaçının.
Diomidis Spinellis

1
Bana öyle geliyor ki bash yerleşikler olmadan benzer bir etkisi bir glob üzerinde hareket ve ls gibi hiçbir dosya eşleşmezse başarısız başka bir komut kullanmak olacaktır: if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi- belki kod golf için yararlı? Eğer glob ile eşleşmemesi gereken glob ile aynı adlı bir dosya varsa başarısız olur, ancak bu durumda muhtemelen daha büyük sorunlarınız vardır.
Dewi Morgan

4
@DewiMorgan Bu daha basit:if ls /tmp/*Files &> /dev/null; then echo exists; fi
Clay Bridges

İlgili ayrıntılar için compgenbkz man bashveyahelp compgen
el-teedee

2
Evet, alıntı yap yoksa dosya adı joker karakteri genişletilir. compgen "dir / *. ext"
Brian Chrisman

169

Nullglob kabuk seçeneği gerçekten bir bashizmdir.

Nullglob durumunun sıkıcı bir şekilde kaydedilmesi ve geri yüklenmesi ihtiyacını önlemek için, sadece glob'u genişleten alt kabuğun içine yerleştirirdim:

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

Daha iyi taşınabilirlik ve daha esnek globbing için find komutunu kullanın:

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

Açık -print -quit eylemler için kullanılan find varsayılan örtük yerine -print böylece eylem bulmak arama kriterleri ilk dosyayı bulur en kısa sürede çıkılacak. Çok sayıda dosya eşleştiğinde, bu echo glob*veya daha hızlı çalışmalıdır ls glob*ve ayrıca genişletilmiş komut satırını aşırı doldurma olasılığını da önler (bazı kabuklarda 4K uzunluk sınırı vardır).

Eğer bulmak overkill gibi hissediyor ve muhtemelen maç için dosya sayısı azdır, kullanım istatistik:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi

10
findtam olarak doğru gibi görünüyor. Köşe kasaları yoktur, çünkü kabuk genişleme yapmaz (ve başka bir komuta genişletilmemiş bir glob geçirir), kabuklar arasında taşınabilir (görünüşte kullandığınız tüm seçenekler POSIX tarafından belirtilmemiş olsa da) ve ls -d glob*(bir önceki kabul edilen cevap) ilk maça ulaştığında durur.
Ken Bloom

1
shopt -u failglobBu seçenekler bir şekilde çakışıyor gibi göründüğü için bu cevabın a gerektirebileceğini unutmayın .
Calimo

findÇözelti yanı yok glob karakterleri ile bir dosya adı eşleşir. Bu durumda, istediğim buydu. Sadece farkında olmak için bir şey.
Hepimiz Monica

1
Görünüşe göre başka biri bunu söylemek için cevabımı düzenlemeye karar verdiğinden.
flabdablet

1
unix.stackexchange.com/questions/275637/… , -maxdepthPOSIX bulma seçeneğinin nasıl değiştirileceğini tartışır .
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

2
nullglobTek bir sonucun desenin kendisine eşit olup olmadığını kontrol etmek yerine, yanlış bir “eşleşme yok” kümesinin oluşmasını önlemek için . Bazı paternler, paternin kendisine tam olarak eşit olan isimlerle eşleşebilir (örneğin a*b; örn. a?bVeya or [a]).
Chris Johnsen

Sanırım bu aslında glob adlı bir dosya olması çok düşük bir olasılıkla başarısız oluyor . (örneğin birisi koştu touch '*py'), ama bu beni başka bir iyi yönde gösteriyor.
Ken Bloom

Bunu en genel sürüm olarak beğendim.
Ken Bloom

Ve ayrıca en kısa. Yalnızca bir eşleşme bekliyorsanız, "$M"kısayol olarak kullanabilirsiniz "${M[0]}". Aksi takdirde, bir dizi değişkeninde glob genişlemesine zaten sahipsiniz, bu yüzden glob'u yeniden genişletmek yerine onu liste olarak başka şeylere aktarmak için gtg'siniz.
Peter Cordes

Güzel. M'yi daha hızlı bir şekilde test edebilirsiniz (daha az bayt ve bir [işlem if [[ $M ]]; then ...
üretmeden

22

severim

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

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

Bu hem okunabilir hem de verimlidir (çok sayıda dosya olmadığı sürece).
Ana dezavantajı göründüğünden çok daha ince ve bazen uzun bir yorum eklemek zorunda hissediyorum.
Bir eşleşme varsa "glob*", kabuk tarafından genişletilir ve ilk eşleşmeler exists()kontrol edilir ve gerisi yoksayılır.
Eğer eşleşme yoksa, oraya da "glob*"geçilir exists()ve orada olmadığı tespit edilir.

Düzenleme: yanlış bir pozitif olabilir, yoruma bakın


13
Eğer glob şunun gibi bir şeyse *.[cC]( pozitif cveya Cdosya olabilir, ancak bir dosya olabilir *.[cC]) yanlış pozitif veya bundan genişletilmiş ilk dosya örneğin varolmayan bir dosyaya veya erişiminiz olmayan dizini (eklemek istediğiniz şekilde || [ -L "$1" ]).
Stephane Chazelas

İlginç. Shellcheck, globbing'in sadece -e0 veya 1 maç olduğunda çalıştığını bildirir . Birden fazla maç için işe yaramaz, çünkü bu olur [ -e file1 file2 ]ve başarısız olur. Gerekçe ve önerilen çözümler için ayrıca github.com/koalaman/shellcheck/wiki/SC2144 adresine bakın .
Thomas Praxl

10

Globfail setiniz varsa, bu deliği kullanabilirsiniz (ki gerçekten yapmamalısınız)

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

veya

q=( * ) && echo 0 || echo 1

2
Başarısız bir noop kullanımı. Asla kullanılmamalı ... ama gerçekten güzel. :)
Brian Chrisman

Atışı parensin içine koyabilirsiniz. Bu şekilde sadece testi etkiler:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet

8

test -e kırık sembolik bağların mevcut olmadığını düşündüğü talihsiz bir uyarıya sahiptir. Yani bunları da kontrol etmek isteyebilirsiniz.

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

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

4
Stephane Chazelas'ın Dan Bloch'un cevabına işaret ettiği gibi, içinde özel karakterler olan dosya adlarındaki yanlış pozitifleri hala düzeltmiyor. (nullglob ile maymun olmadıkça).
Peter Cordes

3
Sen önlemek sınırlandırmak -ove -aiçinde test/ ' [. Örneğin, burada, çoğu uygulamada $1ise başarısız olur =. [ -e "$1" ] || [ -L "$1" ]Bunun yerine kullanın .
Stephane Chazelas

4

MYYN'nin cevabını, fikrine dayanarak bir şekilde basitleştirmek için:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi

4
Kapat, ancak eşleştiriyorsanız [a], adlı bir dosyaya sahip olursunuz [a], ancak adlı bir dosya yoksa ne olur a? Bunu hala seviyorum nullglob. Bazıları bunu bilgiçliksel olarak görebilir, ancak makul olduğu kadar tamamen doğru olabiliriz.
Chris Johnsen

@ sondra.kinsey Bu yanlış; glob [a]yalnızca agerçek dosya adı ile eşleşmemelidir [a].
üçlü

4

Flabdablet'in cevabına dayanarak, benim için en kolay (mutlaka en hızlı değil) sadece kendini bulmak için kullanmak gibi görünüyor , kabuk üzerinde glob genişlemesi bırakıyor:

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

Veya şu şekilde if:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi

Bu tam olarak stat kullanarak sunduğum sürüm gibi çalışır; nasıl stat bulmak "kolay" emin değilim.
flabdablet

3
Yönlendirmenin bir bashizm olduğunu ve diğer kabuklarda sessizce yanlış şeyi yapacağını unutmayın.
flabdablet

Bu, flabdablet'in cevabından daha iyi gibi görünmektedir findçünkü globdaki yolları kabul eder ve daha keskindir ( -maxdepthvb. Gerektirmez ). Ayrıca statcevabından daha iyi görünüyor çünkü stather ek glob maçında fazladan oynamaya devam etmiyor . Herkesin işe yaramadığı köşe davalarına katkıda bulunup bulunamayacağını takdir ediyorum.
drwatsoncode

1
Daha fazla düşündükten sonra ekleyeceğim -maxdepth 0çünkü ekleme koşullarında daha fazla esneklik sağlıyor. Örneğin, sonucu yalnızca eşleşen dosyalarla sınırlandırmak istediğinizi varsayalım. Denemek find $glob -type f -quit, ama glob bir dosyayla eşleşmedi, ancak (hatta özyinelemeli) bir dosya içeren bir dizin eşleşti, bu doğru dönecekti . Buna karşılık find $glob -maxdepth 0 -type f -quit, globun kendisi en az bir dosyayla eşleşirse true değerini döndürür. maxdepthGlob'un bir dizin bileşenine sahip olmasını engellemediğini unutmayın . (FYI 2>yeterlidir. Gerek yok &>)
drwatsoncode

2
Kullanmanın noktası findilk kabuk oluşturmak ve Glob eşleşmelerin potansiyel olarak büyük sıralamak önlemek için; find -name ... -quiten fazla bir dosya adıyla eşleşecek. Eğer bir komut dosyası kabuk tarafından oluşturulan bir glob eşleşmeleri listesine geçmeye dayanırsa find, çağırmak findgereksiz süreç başlatma yükünden başka bir şey yapamaz. Elde edilen listeyi doğrudan boşluk olmaması için test etmek daha hızlı ve daha net olacaktır.
flabdablet

4

Başka bir çözüm daha var:

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

Bu benim için iyi çalışıyor. Kaçırdığım bazı köşe vakaları var mı?


2
Dosyanın gerçekten 'glob *' olarak adlandırılması dışında çalışır.
Ian Kelling

glob'a değişken olarak geçmek için çalışır - birden fazla eşleşme olduğunda "çok fazla argüman" hatası verir. "$ (echo $ GLOB)" tek bir dize
döndürmüyor

@DKebler: çift tırnak içine alındığı için tek dize olarak yorumlanmalıdır.
user1934428

3

Bash'te bir diziye girebilirsiniz; glob eşleşmezse, diziniz mevcut bir dosyaya karşılık gelmeyen tek bir giriş içerir:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

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

Not: nullglobayarladıysanız, scriptsboş bir dizi olur ve bunun yerine [ "${scripts[*]}" ]veya ile test etmeniz gerekir [ "${#scripts[*]}" != 0 ]. Birlikte veya onsuz çalışması gereken bir kütüphane yazıyorsanız nullglob,

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

Bu yaklaşımın bir avantajı, glob işlemini tekrarlamak yerine, çalışmak istediğiniz dosyaların listesine sahip olmanızdır.


Nullglob seti ve dizi muhtemelen boşken, neden hala test yapamıyorsunuz if [ -e "${scripts[0]}" ]...? Ayrıca kabuk seçeneği isim kümesi ayarlanmasına izin veriyor musunuz ?
johnraff

@johnraff, evet, normalde nounsetaktif olduğunu varsayıyorum . Ayrıca, dizenin boş olmadığını test etmek bir dosyanın varlığını kontrol etmekten (biraz) daha ucuz olabilir. Ancak, sadece bir glob gerçekleştirdiğimiz göz önüne alındığında, dizin içeriğinin işletim sisteminin önbelleğinde taze olması gerektiği anlamına gelir.
Toby Speight

1

Bu iğrençlik işe yarıyor gibi görünüyor:

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

Muhtemelen sh değil bash gerektirir.

Nullglob seçeneği, hiçbir eşleşme yoksa glob boş bir dize için değerlendirme neden olur çünkü bu çalışır. Böylece echo komutundan boş olmayan herhangi bir çıktı, globun bir şeyle eşleştiğini gösterir.


Kullanmalısınızif [ "`echo *py`" != "*py"]
yegle

1
Adlı bir dosya olsaydı bu düzgün çalışmazdı *py.
Ryan C. Thompson

İle hiçbir dosya sonu varsa py, `echo *py`hiç değerlendirmek olacaktır *py.
yegle

1
Adlı tek bir dosya varsa Evet, ama aynı zamanda bunu yapacağız *pyyanlış sonucudur.
Ryan

Yanlışsam beni düzeltin, ancak eşleşen bir dosya yoksa *py, betiğiniz "Glob eşleşti" mi?
yegle

1

Bu cevabı görmedim, bu yüzden oraya koyacağımı düşündüm:

set -- glob*
[ -f "$1" ] && echo "found $@"

1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

açıklama

İçin bir eşleşme olmadığı zaman glob*, o $1içerecektir 'glob*'. Dosya -f "$1"doğru olmadığından test doğru glob*olmayacak.

Bu neden alternatiflerden daha iyidir?

Bu, sh ile çalışır ve ksh ve bash türetir. Herhangi bir alt kabuk oluşturmaz. $(..)ve `...`komutlar bir alt kabuk oluşturur; bir süreci çatallarlar ve bu nedenle bu çözümden daha yavaştırlar.


1

Bunun gibi (aşağıdakileri içeren test dosyaları 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

Şundan çok daha iyi compgen -G: çünkü daha fazla ve daha kesin olarak ayrımcılık yapabiliriz.

Yalnızca bir joker karakterle çalışabilir *


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

Çok fazla eşleşme varsa veya dosya erişimi yavaşsa bunun çok zaman alacağını unutmayın.


1
[a]Dosya [a]mevcut olduğunda ve dosya yoksa benzer bir desen kullanılırsa, bu yanlış yanıtı verecektir a. Eşleşmesi gereken tek dosya aaslında mevcut olmasa da “bulundu” diyecektir .
Chris Johnsen

Bu sürüm sıradan bir POSIX / bin / sh'de (bashisms olmadan) çalışmalı ve buna ihtiyacım olması durumunda, glob zaten parantez içermiyor ve çok patolojik. Ama herhangi bir dosyanın bir glob ile eşleşip eşleşmediğini test etmenin iyi bir yolu olmadığını düşünüyorum.
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

2
Bu sürüm tam olarak bir dosya eşleştiğinde başarısız olur, ancak nullglobkabuk seçeneğini kullanarak FOUND = -1 çamurdan kaçınabilirsiniz .
Ken Bloom

@Ken: Hmm, çamur demem nullglob. Tek bir sonucun orijinal kalıp ile karşılaştırılması bir çamurdur (ve yanlış sonuçlara eğilimlidir), kullanmak nullglobdeğildir.
Chris Johnsen

@Chris: Bence yanlış anladın. nullglobÇamur demedim .
Ken Bloom

1
@Ken: Gerçekten yanlış okudum. Lütfen geçersiz eleştirilerim için özür dilerim.
Chris Johnsen

-1
(ls glob* &>/dev/null && echo Files found) || echo No file found

5
Eşleşen dizinler de varsa yanlış döndürür glob*ve örneğin bu dizinleri listelemek için yazma yok.
Stephane Chazelas

-1

ls | grep -q "glob.*"

En etkili çözüm değil (dizinde bir ton dosya varsa yavaşlayabilir), ancak basit, okunması kolay ve normal ifadelerin düz bash glob kalıplarından daha güçlü olması avantajına sahiptir.


-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true

1
Daha iyi bir yanıt için kodunuza bir açıklama eklemeyi deneyin.
Mesud Rahimi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.