Rozszerzenie z * .txt w powłoce nie działa, jeśli nie istnieje plik .txt

10

Bawiłem się ekspansją i zauważyłem osobliwe zachowanie. Próbowałem zrobić:

echo ./*.txt

I nie miałem żadnego pliku .txt w moim bieżącym katalogu. Otrzymałem wynik:

./*.txt

Jestem tylko ciekawy: dlaczego to dostałem? Spodziewałem się, że nie otrzymam żadnych wyników.

PS: Kiedy miałem .txtplik, rozszerzenie było poprawnie interpretowane. Innymi słowy, powiedzmy, że miałem plik, smthn.txtecho faktycznie odbiło się echem current_directory/smthn.txt.

Ignorant Wędrowiec
źródło

Odpowiedzi:

15

Na podstawie strony podręcznika powłoki bash,

bash skanuje każde słowo w poszukiwaniu znaków *,? i [. Jeśli pojawi się jeden z tych znaków, to słowo jest traktowane jako wzór i zastępowane alfabetyczną listą nazw plików pasujących do wzorca. Jeśli nie znaleziono pasujących nazw plików, a opcja powłoki nullglob nie jest włączona, słowo pozostaje niezmienione. Jeśli ustawiono opcję nullglob i nie znaleziono żadnych dopasowań, słowo zostanie usunięte.

W tym przypadku zakładam, że nullglob nie jest włączony, więc słowo pozostaje niezmienione - stąd wynik, który widzisz.

Colin B.
źródło
2
Możesz zmienić zachowanie w następujący sposób: shopt -s nullglobda puste ciągi dla niedopasowanych wzorów i shopt -u nullglob(ustawienie standardowe) da sam wzór.
PerlDuck
13

Spodziewałem się, że nie otrzymam żadnych wyników.

Gdyby nullglobbyły domyślne, wiele poleceń zachowywałoby się dość nieoczekiwanie, ponieważ (być może niestety) często polecenia traktują sprawę zerowych argumentów nazw plików w jakościowo odmienny sposób niż przypadek jednego lub więcej argumentów nazw plików.

Załóżmy, że masz włączoną funkcję nullglob( shopt -s nullglob) i jesteś w katalogu, w którym nie ma pasujących plików *.txt. Wtedy *.txtrzeczywiście rozwinie się do zera - nie pustego pola, ale żadnych pól - zgodnie z oczekiwaniami. Ale to przyniosłoby następujące wyniki:

  • ls *.txtwyświetlałby listę wszystkich plików w bieżącym katalogu (oprócz plików ukrytych), ponieważ dzieje się lstak, gdy nie przekaże się żadnych argumentów nazw plików.
  • cat *.txtczytałby ze standardowego wejścia , ponieważ gdy catnie ma argumentów nazwy pliku, to tak jakbyś uruchomił cat -. Jeśli działa interaktywnie, siedzi i czeka na dane wejściowe. Wiele poleceń zachowuje się w ten sposób.
  • cp *.txt dest/nie powiedzie się z powodu błędu cp: missing destination file operand after 'dest/'. To nie jest katastrofa, ale jest myląca i zupełnie inna niż cichy sukces, który prawdopodobnie jest pożądany.
  • file *.txt, i różne inne programy bez specjalnego zachowania w przypadku argumentów zerowej nazwy pliku, nadal nie powiodłyby się z komunikatem o błędzie lub użytkowaniu, gdy żaden nie zostanie przekazany.
  • Nawet przypadki, które intuicyjnie czują, że powinny działać, często by tego nie zrobiły. printf 'Got file: "%s"\n' *.txtwydrukuje Got file: ""zamiast niczego.
  • Przypadkowe uszkodzenie zacytować wystąpień *, ?i [które nie są przeznaczone do rozszerzony przez powłokę będzie częściej produkują oczywiście błędne wyniki, ale w sposób, który może być trudny do rozszyfrowania. Na przykład, jeśli żadna nazwa pliku w bieżącym katalogu nie zaczyna się od gedit, wtedy apt list gedit*(gdzie apt list 'gedit*'zamierzano) stałby się sprawiedliwy apt listi wyświetlałby listę wszystkich dostępnych pakietów.

Dobrze więc, że nie dostajesz tego zachowania bez żądania go. Prawdopodobnie najczęstszą praktyczną sytuacją, która jest w rzeczywistości uproszczona, nullglobjest for f in *.txt. Zobacz także to pytanie (z którym łączy się odpowiedź Siergieja Kolodyazhnyya ).

Trudniejsze pytanie brzmi: dlaczego - failglobw przypadku błędu rozszerzenia brak globu, który nie pasuje do żadnego pliku - nie jest domyślny w bash. Uważam, że odpowiedź Siergieja Kolodyazhnyya zawiera przyczynę tego, nawet bez bezpośredniego odesłania . Zatrzymywanie nierozwijających się globów bez powodowania błędu ekspansji jest (być może niestety) znormalizowanym zachowaniem, a także zachowaniem tradycyjnym, a zatem oczekiwanym. Chociaż bash nie próbuje być w pełni zgodny z POSIX, chyba że zostanie wywołany z nazwą shlub nie prześle --posixopcji, wiele z jego opcji projektowych, nawet gdy nie jest w trybie POSIX, następuje bezpośrednio po POSIX. Musieli wybrać pewne zachowanie i są wady związane z nieprzestrzeganiem oczekiwań użytkowników.


Myślę, że jest to najmniej wpływowy historycznie aspekt tej sprawy, więc zostawiłem to na koniec ... ale warto wspomnieć, że w zachowaniu jest coś dziwnie koncepcyjnego nullglob.

nullglobna pierwszy rzut oka wydaje się elegancki, ponieważ pod względem składniowym traktuje przypadek zero pasujących plików nie inaczej niż przypadek jednego, dwóch lub dowolnej innej liczby. Uruchamiane przez nas polecenia, dla których globusy zamieniają się w argumenty, nie traktują ich tak samo, jak opisano powyżej. Ale syntaktycznie wydaje się to co najmniej słuszne, co myślę, że jest motywacją do twojego pytania.

A jednak istnieje inna, bardziej subtelna niespójność, nullglobktóra nie rozwiązuje problemu - a która nasila. Przypadek zerowania znaków globowania („symboli wieloznacznych”) jest traktowany zupełnie inaczej niż w przypadku jednej, dwóch lub dowolnej innej liczby. Na przykład, shopt -s nullglobjeśli ab?d?fnie pasuje do żadnego pliku, jest usuwany; jeśli ab?dnie pasuje do żadnego pliku, jest usuwany; ale jeśli abnie pasuje do żadnego pliku (tzn. jeśli nie ma pliku o dokładnie takiej nazwie ab), nadal nie jest usuwany. Oczywiście byłoby katastrofą, gdyby został usunięty, ponieważ może nie być przeznaczony do odwoływania się do istniejącego pliku w bieżącym katalogu; może nawet nie odnosić się do pliku. Ale to wciąż eliminuje wszelką nadzieję na całkowitą spójność.

Trzy zachowania, które zapewnia bash - domyślne traktowanie globów, które nie pasują do żadnych plików, tak jakby nie były globami, i przekazywanie ich jako nierozwinięte, zachowanie, którego się spodziewałeś po ich traktowaniu (jeśli wybaczysz ten dziwny zwrot frazy) jako oznaczające wszystkie zero plików, które pasują do siebie ( nullglob), oraz bezpieczne zachowanie polegające na uznaniu ich za błędy ( failglob) - wszystkie reprezentują różne podejścia do dwuznaczności związanej z powłoką, która nie jest w stanie stwierdzić, czy jakieś konkretne słowo ma być Nazwa pliku. Powłoka wykonuje ekspansje bez wiedzy o tym, jak poszczególne polecenia, które wywołujesz, będą traktować ich argumenty.

Jest to jeden z wielu przypadków rozdzielenia obaw . W systemach, których projekt jest zgodny z filozofią Uniksa, każda część ma na celu zrobienie jednej rzeczy i wykonanie jej dobrze . Powłoka przetwarza tekst na polecenia i argumenty i wywołuje te polecenia, z których większość jest zewnętrzna względem samej powłoki. Jest to zwykle o wiele ładniejsze i bardziej wszechstronne niż systemy, w których polecenia zewnętrzne są same odpowiedzialne za wykonywanie tych transformacji (jak w przypadku tradycyjnych procesorów poleceń w DOS i Windows). Ale ma to czasem swoje wady.

Eliah Kagan
źródło
Najwyraźniej bash-4.3.39 (2) nie ma failglob. Dlatego nie może być domyślną wartością, ponieważ nie zawsze była obsługiwana.
Ruslan
6

Głównym powodem jest to, ponieważ jest to zachowanie standardowe określony przez POSIX - średnia, która obejmuje powłoki języka poleceń i wśród innych rzeczy pasujące do wzorca (takich jak muszle bash, dashShell - domyślnie Ubuntu /bin/shi kshśledzić ten standard). Z sekcji 2.13.3 Wzorce używane do rozszerzenia nazw plików :

Jeśli wzorzec nie pasuje do żadnej z istniejących nazw plików lub ścieżek, ciąg wzorca pozostawia się bez zmian.

Ma to oczywiście efekt uboczny - dopasowanie nazwy pliku, które może być dosłownie *.txt. nullglobOpcja w bashi zshmoże pomóc: jeśli ta opcja jest włączona poprzez shopt -s nullglob(i to nie jest domyślnie włączona, odnoszący się do tej kwestii), a następnie globstar zostanie poszerzona do pustej struny, gdy nie znaleziono żadnych pasujących nazwach. ksh93ma własny zaawansowany mechanizm dopasowywania wzorów, który zapewnia ten sam efekt~(N)*.txt

Zobacz także Dlaczego nullglob nie jest domyślny?

Sergiy Kolodyazhnyy
źródło