Sprawdź, czy istnieją pliki pasujące do wzorca, aby wykonać skrypt


29

Próbuję napisać ifinstrukcję, aby sprawdzić, czy są jakieś pliki pasujące do określonego wzorca. Jeśli w katalogu znajduje się plik tekstowy, powinien on uruchomić dany skrypt.

Mój kod obecnie:

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

Proszę podać kilka pomysłów; Chcę uruchomić skrypt tylko, jeśli jest .txtw katalogu.


3
Czy na pewno ma to być „katalog” /? Ponadto brakuje Ci wcześniej średnika fi.
depquid

Najczystszym i niezawodnym rozwiązaniem, jakie napotkałem, jest użycie, findjak wyjaśniono tutaj na temat przepełnienia stosu .
Joshua Goldberg

Odpowiedzi:


39
[ -f /*.txt ]

zwróciłoby wartość prawdy tylko wtedy, gdy istnieje jeden (i tylko jeden) plik, /którego nazwa się nie kończy, .txti jeśli jest to zwykły plik lub dowiązanie symboliczne do zwykłego pliku.

Jest tak, ponieważ symbole wieloznaczne są rozszerzane przez powłokę przed przekazaniem do polecenia (tutaj [).

Więc jeśli istnieje /a.txti /b.txt, [zostanie przekazany 5 argumentów: [, -f, /a.txt, /b.txti ]. [skarżyłby się wtedy na -fzbyt wiele argumentów.

Jeśli chcesz sprawdzić, czy *.txtwzorzec rozwija się do co najmniej jednego nie ukrytego pliku (zwykłego lub nie):

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 nullglobjest bashspecyficzny, ale muszle podoba ksh93, zsh, yash, tcshmają te same oświadczenia.

Zauważ, że znajduje te pliki, czytając zawartość katalogu, wcale nie próbuje uzyskać dostępu do tych plików, co czyni go bardziej wydajnym niż rozwiązania, które wywołują polecenia takie jak lslub statna tej liście plików obliczonych przez powłokę.

Standardowy shodpowiednik to:

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

Problem polega na tym, że w przypadku powłok Bourne lub POSIX, jeśli wzorzec nie pasuje, rozwija się do siebie. Jeśli więc zostanie *.txtrozwinięty *.txt, nie wiesz, czy to dlatego, że nie ma .txtpliku w katalogu, czy dlatego, że istnieje jeden plik o nazwie *.txt. Korzystanie [*].txt *.txtpozwala na rozróżnienie między nimi.


[ -f /*.txt ]jest dość szybki w porównaniu do compgen.
Daniel Böhmer,

@ DanielBöhmer [ -f /*.txt ]byłby w błędzie, ale w moim teście katalog zawierający 3425pliki, 94z których nie są ukrytymi plikami txt, compgen -G "*.txt" > /dev/null 2>&1wydaje się być tak szybki jak set -- *.txt; [ "$#" -gt 0 ](20,5 sekundy dla obu, jeśli powtórzę 10000 razy w moim przypadku).
Stéphane Chazelas

11

Zawsze możesz użyć find:

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

Wyjaśnienie:

  • find . : przeszukaj bieżący katalog
  • -maxdepth 1: nie przeszukuj podkatalogów
  • -type f : przeszukuj tylko zwykłe pliki
  • name "*.txt" : wyszukaj pliki z rozszerzeniem .txt
  • 2>/dev/null : przekieruj komunikaty o błędach na /dev/null
  • | grep -q . : grep dla dowolnego znaku, zwróci false, jeśli nie znaleziono znaków.
  • && ./script: Wykonaj ./scripttylko, jeśli poprzednie polecenie zakończyło się pomyślnie ( &&)

2
findzwraca fałsz tylko wtedy, gdy ma problemy ze znalezieniem plików, a nie, jeśli nie znajdzie żadnego pliku. Chcesz potokować wyjście, grep -q .aby sprawdzić, czy coś znajdzie.
Stéphane Chazelas,

@StephaneChazelas oczywiście masz rację. Dziwne, przetestowałem to i wydawało się, że działa. Musiał zrobić coś dziwnego, ponieważ już tego nie robi. Kiedy znajdzie „problem ze znalezieniem plików”?
terdon

@terdon, na przykład gdy jakiś katalog jest niedostępny, błędy We / Wy lub wszelkie błędy zwrócone przez każde wywołane przez niego wywołanie systemowe. W takim przypadku spróbuj po chmod a-x ..
Stéphane Chazelas,

8

Możliwym rozwiązaniem jest także wbudowane Bash compgen. To polecenie zwraca wszystkie możliwe dopasowania wzorca globowania i ma kod wyjścia wskazujący, czy jakieś pliki pasują.

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

Znalazłem to pytanie, szukając szybszych rozwiązań.


1
Dobre znalezisko! Jeśli korzystasz z wielobajtowych ustawień regionalnych, możesz to nieco poprawić za pomocą LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas

7

Oto jeden linijka, aby to zrobić:

$ ls
file1.pl  file2.pl

pliki istnieją

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

pliki nie istnieją

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

To podejście wykorzystuje operatory ||i &&w bash. Są to operatory „lub” i „i”.

Więc jeśli polecenie stat zwróci wartość $?równą 0, wówczas echowywoływane jest pierwsze , jeśli zwraca 1, echoto wywoływane jest drugie .

zwraca wyniki ze stat

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

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

To pytanie jest szeroko omówione przy przepełnieniu stosu:


1
Po co używać niestandardowego, statkiedy ls -dmożna to zrobić?
Stéphane Chazelas,

Myślałem, że ls -dwyświetla katalog? Wydawało się, że nie działa, gdy próbowałem ls -d *.plna przykład wyświetlić katalog z plikami .
slm

Możesz zastąpić instrukcję po lewej stronie &&przez ls *.txti ona również będzie działać. Upewnij się, że wysłałeś stdout i stderr na /dev/nulladres sugerowany przez @slm.
unxnut

1
Jeśli używasz ls *.txti nie ma żadnych plików obecny w katalogu to zwróci $? = 2, który będzie nadal działać z jeśli potem, ale był to jeden z powodów, dla których wybiera statsię ls. Chciałem 0 dla sukcesu, a 1 dla niepowodzenia.
slm

ls -djest lista katalogów zamiast ich zawartości. Tak ls -dsamo dzieje lstatsię z plikiem, tak jak GNU stat. To, jakie niezerowe komendy zakończenia działania zwracają się po awarii, jest specyficzne dla systemu, nie ma sensu przyjmować na nich założeń.
Stéphane Chazelas,

4

Jak zauważa Chazelas, skrypt nie powiedzie się, jeśli interpretacja symboli wieloznacznych pasuje do więcej niż jednego pliku.

Istnieje jednak pewna sztuczka, której używam ( nawet nie bardzo mi się to podoba ) do poruszania się:

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

Jak to działa?

Rozwijanie symboli wieloznacznych będzie pasować do tablicy nazw plików, otrzymamy pierwszą, jeśli istnieją, w przeciwnym razie zerowa, jeśli nie będzie pasować.


IMO to najmniej zła odpowiedź tutaj. Wszystkie wydają się dość okropne, jakby w języku brakowało podstawowej funkcji.
plugwash

@plugwash to celowe ... * Skrypty powłoki nix mają podstawową kontrolę przepływu i kilka innych drobiazgów, ale pod koniec dnia zadaniem jest sklejenie innych poleceń. Jeśli bash jest do bani ... to dlatego, że używane przez niego polecenia są do bani
cb88

2
To zła logika (i brakuje ci cytatów). To sprawdza, czy pierwszy pasujący plik jest zwykłym plikiem. Mogą to być pliki nieregularne, ale może być kilka innych .txtplików typu regularnego . Spróbuj na przykład później mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas


0

Podoba mi się poprzednie rozwiązanie macierzowe, ale mogłoby to stać się marnotrawstwem przy dużej liczbie plików - powłoka użyłaby dużej ilości pamięci do zbudowania tablicy i tylko pierwszy element byłby testowany.

Oto alternatywna struktura, którą przetestowałem wczoraj:

$ 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

Wydaje się, że wartość wyjściowa z grep określa ścieżkę przez strukturę kontrolną. To również sprawdza przy użyciu wyrażeń regularnych, a nie wzorców powłoki. Niektóre z moich systemów mają polecenie „pcregrep”, które pozwala na znacznie bardziej wyrafinowane dopasowanie wyrażeń regularnych.

(Edytowałem tę odpowiedź, aby usunąć „ls” w podstawieniu polecenia po przeczytaniu powyższej krytyki za parsowanie).


-2

jeśli chcesz użyć klauzuli if, oceń liczbę:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.