Jeśli chcę sprawdzić, czy istnieje pojedynczy plik, mogę go przetestować za pomocą test -e filename
lub [ -e filename ]
.
Załóżmy, że mam glob i chcę wiedzieć, czy istnieją jakieś pliki, których nazwy pasują do globu. Glob może dopasować 0 plików (w takim przypadku nie muszę nic robić) lub może dopasować 1 lub więcej plików (w takim przypadku muszę coś zrobić). Jak mogę sprawdzić, czy glob ma jakieś dopasowania? (Nie obchodzi mnie, ile jest dopasowań, i najlepiej byłoby, gdybym mógł to zrobić za pomocą jednej if
instrukcji i bez pętli (po prostu dlatego, że uważam to za najbardziej czytelne).
( test -e glob*
kończy się niepowodzeniem, jeśli glob pasuje do więcej niż jednego pliku).
Odpowiedzi:
Uciekaj przed schematem, bo zostanie wstępnie rozszerzony na mecze.
Status wyjścia to:
stdout
to lista plików pasujących do globu .Myślę, że jest to najlepsza opcja pod względem zwięzłości i minimalizacji potencjalnych skutków ubocznych.
AKTUALIZACJA : Wymagane przykładowe użycie.
źródło
compgen
jest to wbudowane polecenie specyficzne dla bash i nie jest częścią wbudowanych poleceń uniksowego standardu powłoki POSIX. pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/… Dlatego unikaj używania go w skryptach, w których problemem jest możliwość przenoszenia do innych powłok.if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi
- może przydatny do gry w golfa? Nie powiedzie się, jeśli istnieje plik o nazwie takiej samej jak glob, który glob nie powinien był dopasować, ale w takim przypadku prawdopodobnie masz większe problemy.if ls /tmp/*Files &> /dev/null; then echo exists; fi
compgen
, patrzman bash
lub zhelp compgen
Opcja powłoki nullglob jest rzeczywiście bastyzmem.
Aby uniknąć konieczności żmudnego zapisywania i przywracania stanu nullglob, umieściłem go tylko w podpowłoce, która rozszerza glob:
Aby uzyskać lepszą przenośność i bardziej elastyczne globowanie, użyj find:
Jawne -print -quit są używane dla find zamiast domyślnej niejawnej akcji -print, dzięki czemu find zakończy działanie, gdy tylko znajdzie pierwszy plik spełniający kryteria wyszukiwania. Tam, gdzie pasuje wiele plików, powinno to działać znacznie szybciej niż
echo glob*
lub,ls glob*
a także pozwala uniknąć przeciążenia rozszerzonej linii poleceń (niektóre powłoki mają limit długości 4K).Jeśli find wydaje się przesadny, a liczba plików, które mogą się zgadzać, jest niewielka, użyj statystyki:
źródło
find
wydaje się być dokładnie poprawne. Nie ma przypadków narożnych, ponieważ powłoka nie wykonuje ekspansji (i przekazuje nierozwiniętą glob do jakiegoś innego polecenia), jest przenośna między powłokami (choć najwyraźniej nie wszystkie używane opcje są określone przez POSIX) i jest szybsza niżls -d glob*
(poprzednio zaakceptowana odpowiedź), ponieważ zatrzymuje się, gdy osiągnie pierwszy mecz.shopt -u failglob
ponieważ te opcje wydają się w jakiś sposób kolidować.find
Rozwiązanie pozwoli dopasować nazwę pliku bez znaków glob, jak również. W tym przypadku właśnie tego chciałem. Tylko coś, o czym należy pamiętać.-maxdepth
opcję znalezienia POSIX.źródło
nullglob
zamiast sprawdzania, czy pojedynczy wynik jest równy samemu wzorowi. Niektóre wzorce mogą pasować do nazw, które są dokładnie równe samemu wzorowi (np.a*b
Ale nie np.a?b
Lub[a]
).touch '*py'
), ale to wskazuje mi inny dobry kierunek."$M"
jako skrótu"${M[0]}"
. W przeciwnym razie masz już rozszerzenie glob w zmiennej tablicowej, więc jesteś gtg za przekazanie go do innych rzeczy jako listy, zamiast zmuszania ich do ponownego rozwinięcia globu.[
procesu) za pomocąif [[ $M ]]; then ...
podoba mi się
Jest to zarówno czytelne, jak i wydajne (chyba że istnieje ogromna liczba plików).
Główną wadą jest to, że jest znacznie bardziej subtelny, niż się wydaje, i czasami czuję się zmuszony do dodania długiego komentarza.
Jeśli istnieje dopasowanie,
"glob*"
jest ono rozszerzane przez powłokę, a wszystkie dopasowania są przekazywane doexists()
, który sprawdza pierwszy i ignoruje resztę.Jeśli nie ma dopasowania,
"glob*"
zostaje przekazaneexists()
i nie istnieje.Edycja: może być fałszywie pozytywny, patrz komentarz
źródło
*.[cC]
(może nie byćc
lubC
plik, ale plik o nazwie*.[cC]
) lub fałszywie ujemne jeśli pierwszy plik rozbudowany od tego jest na przykład dowiązanie do pliku unexistent lub do pliku w katalog, do którego nie masz dostępu (możesz dodać a|| [ -L "$1" ]
).-e
wtedy, gdy występuje 0 lub 1 dopasowania. Nie działa w przypadku wielu dopasowań, ponieważ tak się stanie,[ -e file1 file2 ]
a to się nie powiedzie. Zobacz także uzasadnienie i sugerowane rozwiązania na github.com/koalaman/shellcheck/wiki/SC2144 .Jeśli masz ustawiony globfail, możesz użyć tego szalonego (czego naprawdę nie powinieneś)
lub
źródło
(shopt -s failglob; : *) 2>/dev/null && echo exists
test -e ma niefortunne zastrzeżenie, które uważa, że zerwane łącza symboliczne nie istnieją. Więc możesz też sprawdzić te.
źródło
-o
i-a
wtest
/[
. Na przykład tutaj nie powiedzie się, jeśli$1
jest=
z większością implementacji. Użyj[ -e "$1" ] || [ -L "$1" ]
zamiast tego.Aby nieco uprościć odpowiedź MYYN, w oparciu o jego pomysł:
źródło
[a]
, masz plik o nazwie[a]
, ale nie ma nazwy plikua
? Nadal lubięnullglob
to. Niektórzy mogą postrzegać to jako pedantyczne, ale równie dobrze możemy mieć rację.[a]
powinien tylko pasowaća
, a nie dosłowna nazwa pliku[a]
.Opierając się na odpowiedzi flabdablet , dla mnie wygląda na to, że najłatwiejszym (niekoniecznie najszybszym) jest po prostu użycie find , pozostawiając globalną ekspansję na powłoce, na przykład:
Lub w
if
:źródło
find
odpowiedź flabdablet, ponieważ akceptuje ścieżki w glob i jest bardziej zwięzła (nie wymaga-maxdepth
itp.). Wydaje się również lepsza niż jegostat
odpowiedź, ponieważ nie kontynuujestat
dopełniania każdego dodatkowego dopasowania globalnego. Byłbym wdzięczny, gdyby ktoś mógł wnieść wkład w sprawy narożne, w których to nie działa.-maxdepth 0
ponieważ pozwala to na większą elastyczność w dodawaniu warunków. np. załóżmy, że chcę ograniczyć wynik tylko do pasujących plików. Mógłbym spróbowaćfind $glob -type f -quit
, ale zwróciłoby to wartość true, gdyby glob NIE pasował do pliku, ale pasował do katalogu zawierającego plik (nawet rekurencyjnie). W przeciwieństwie dofind $glob -maxdepth 0 -type f -quit
tego zwróci prawdę tylko wtedy, gdy sam glob pasuje do co najmniej jednego pliku. Zauważ, żemaxdepth
to nie uniemożliwia globowi posiadania komponentu katalogu. (FYI2>
jest wystarczająca. Nie ma potrzeby&>
)find
jest, aby uniknąć generowania przez powłokę i sortowania potencjalnie ogromnej listy dopasowań globalnych;find -name ... -quit
dopasuje co najwyżej jedną nazwę pliku. Jeśli skrypt polega na przekazaniu wygenerowanej przez powłokę listy dopasowań globalnych dofind
, wywoływanie powodujefind
jedynie zbędne obciążenie związane z uruchomieniem procesu. Proste testowanie wynikowej listy pod kątem braku pustki będzie szybsze i bardziej przejrzyste.Mam jeszcze inne rozwiązanie:
To mi ładnie działa. Czy brakuje mi niektórych narożnych skrzynek?
źródło
W Bash możesz globować do tablicy; jeśli glob nie pasuje, twoja tablica będzie zawierać pojedynczy wpis, który nie odpowiada istniejącemu plikowi:
Uwaga: jeśli
nullglob
ustawiłeś,scripts
będzie pusta tablica i zamiast tego powinieneś przetestować za pomocą[ "${scripts[*]}" ]
lub za pomocą[ "${#scripts[*]}" != 0 ]
. Jeśli piszesz bibliotekę, która musi pracować z lub beznullglob
, będziesz tego potrzebowaćZaletą tego podejścia jest to, że masz listę plików, z którymi chcesz pracować, zamiast konieczności powtarzania operacji glob.
źródło
if [ -e "${scripts[0]}" ]...
? Czy zezwalasz również na możliwość ustawienia zestawu rzeczowników w opcji powłoki ?nounset
jest aktywny. Ponadto może być (nieco) tańsze przetestowanie łańcucha niepustego niż sprawdzenie obecności pliku. Mało prawdopodobne, biorąc pod uwagę, że właśnie wykonaliśmy glob, co oznacza, że zawartość katalogu powinna być świeża w pamięci podręcznej systemu operacyjnego.Ta obrzydliwość wydaje się działać:
Prawdopodobnie wymaga bash, a nie sh.
Działa to, ponieważ opcja nullglob powoduje, że glob ocenia na pusty łańcuch, jeśli nie ma żadnych dopasowań. Zatem wszelkie niepuste dane wyjściowe polecenia echo wskazują, że glob coś pasuje.
źródło
if [ "`echo *py`" != "*py"]
*py
.py
,`echo *py`
zostanie oszacowane na*py
.*py
, co jest złym wynikiem.*py
, twój skrypt wyświetli echo „Glob dopasowany”?Nie widziałem tej odpowiedzi, więc pomyślałem, że ją tam opublikuję:
źródło
Wyjaśnienie
Jeśli nie ma dopasowania
glob*
, wówczas$1
będzie zawierać'glob*'
. Test-f "$1"
nie będzie prawdziwy, ponieważglob*
plik nie istnieje.Dlaczego jest to lepsze niż alternatywy
Działa to z sh i pochodnymi: ksh i bash. Nie tworzy żadnej podpowłoki.
$(..)
a`...`
polecenia tworzą podpowłokę; rozwidlają proces i dlatego są wolniejsze niż to rozwiązanie.źródło
Tak to w grzmotnąć(pliki testowe zawierające
pattern
):Jest to o wiele lepsze niż
compgen -G
: ponieważ możemy dyskryminować więcej przypadków i dokładniej.Może działać tylko z jednym znakiem wieloznacznym
*
źródło
Pamiętaj, że może to być bardzo czasochłonne, jeśli istnieje wiele dopasowań lub dostęp do pliku jest wolny.
źródło
[a]
jest używany, gdy plik[a]
jest obecny, a plika
jest nieobecny. Powie „znaleziony”, mimo że jedyny plik, który powinien pasować,a
nie jest tak naprawdę obecny.źródło
nullglob
opcji powłoki.nullglob
kludge. Porównywanie pojedynczego wyniku do oryginalnego wzoru jest kludge (i podatne na fałszywe wyniki), użycienullglob
nie jest.nullglob
do kludge.źródło
glob*
i na przykład nie masz zapisu, aby wyświetlić te katalogi.ls | grep -q "glob.*"
Nie jest to najbardziej wydajne rozwiązanie (jeśli w katalogu jest mnóstwo plików, może być powolne), ale jest proste, łatwe do odczytania, a ponadto ma tę zaletę, że wyrażenia regularne są bardziej wydajne niż zwykłe globalne wzorce bash.
źródło
źródło