Powłoki, takie jak Bash i Zsh, zamieniają symbol wieloznaczny na argumenty, tyle argumentów ile odpowiada wzorowi:
$ echo *.txt
1.txt 2.txt 3.txt
Ale co jeśli chcę tylko zwrócić pierwszy mecz, a nie wszystkie mecze?
$ echo *.txt
1.txt
Nie mam nic przeciwko rozwiązaniom specyficznym dla powłoki, ale chciałbym rozwiązania, które działa z białymi spacjami w nazwach plików.
Odpowiedzi:
Jednym z solidnych sposobów bash jest rozwinięcie do tablicy i wyprowadzenie tylko pierwszego elementu:
(Możesz nawet po prostu
echo $files
, brakujący indeks jest traktowany jako [0].)To bezpiecznie obsługuje spację / tab / znak nowej linii i inne metaznaki podczas rozwijania nazw plików. Pamiętaj, że obowiązujące ustawienia regionalne mogą zmienić to, co jest „pierwsze”.
Możesz to również zrobić interaktywnie za pomocą funkcji uzupełniania bash :
Powoduje to powiązanie
_echo
funkcji z uzupełnieniem argumentówecho
polecenia (zastępując normalne zakończenie). Dodatkowy „*” jest dołączony do powyższego kodu, możesz po prostu nacisnąć tabulację na częściowej nazwie pliku i mam nadzieję, że coś dobrego się wydarzy.Kod jest nieco zawiły, zamiast ustawiania lub zakładania
nullglob
(shopt -s nullglob
) sprawdzamy, czycompgen -G
można rozszerzyć glob do niektórych dopasowań, następnie bezpiecznie rozwijamy do tablicy, a na końcu ustawiamy COMPREPLY, aby cytowanie było solidne.Możesz częściowo to zrobić (programowo rozwinąć glob) za pomocą bash
compgen -G
, ale nie jest to niezawodne, ponieważ wypisywane jest nie przywołane na standardowe wyjście.Jak zwykle, uzupełnianie jest dość obciążone, co przerywa uzupełnianie innych rzeczy, w tym zmiennych środowiskowych (zobacz tutaj
_bash_def_completion()
funkcję , aby uzyskać szczegółowe informacje na temat emulacji domyślnego zachowania).Możesz także użyć
compgen
poza funkcją uzupełniania:Należy zauważyć, że „~” nie jest globem, jest obsługiwane przez bash na osobnym etapie ekspansji, podobnie jak zmienne $ i inne rozszerzenia.
compgen -G
po prostu dokonuje globowania nazw plików, alecompgen -W
zapewnia wszystkie domyślne rozszerzenia bash, choć być może zbyt wiele rozszerzeń (w tym``
i$()
). W przeciwieństwie do-G
,-W
jest bezpiecznie cytowany (nie umiem wyjaśnić różnicy). Ponieważ chodzi o-W
to, że rozwija on tokeny, oznacza to, że rozwinie „a” do „a”, nawet jeśli taki plik nie istnieje, więc być może nie jest idealny.Jest to łatwiejsze do zrozumienia, ale może mieć niepożądane skutki uboczne:
Następnie:
touch $'curious \n filename'
echo curious*
tabZwróć uwagę na użycie,
printf %q
aby bezpiecznie podać wartości.Ostatnią opcją jest użycie wyjścia rozdzielanego cyframi 0 w narzędziach GNU (zobacz bash FAQ ):
Ta opcja daje ci nieco większą kontrolę nad kolejnością sortowania (kolejność przy rozszerzaniu globu będzie zależała od twoich ustawień regionalnych /
LC_COLLATE
i może, ale nie musi składać), ale poza tym jest dość dużym młotem dla tak małego problemu ;-)źródło
W Zsh użyj
[1]
kwalifikatora glob . Zauważ, że chociaż ten szczególny przypadek zwraca co najwyżej jedno dopasowanie, to wciąż jest to lista, a globusy nie są rozwijane w kontekstach, które oczekują pojedynczego słowa, takiego jak przypisania (oprócz przypisań tablicowych).W ksh lub bash możesz upchnąć całą listę dopasowań w tablicy i użyć pierwszego elementu.
W dowolnej powłoce możesz ustawić parametry pozycyjne i użyć pierwszego.
Spowoduje to zablokowanie parametrów pozycji. Jeśli tego nie chcesz, możesz użyć podpowłoki.
Możesz także użyć funkcji, która ma swój własny zestaw parametrów pozycyjnych.
źródło
*.txt([1,n])
Próbować:
Uwaga: rozszerzenie nazwy pliku jest sortowane zgodnie z kolejnością zestawiania obowiązującą w bieżących ustawieniach narodowych.
źródło
Proste rozwiązanie:
Lub użyj,
printf
jeśli wolisz.źródło
Natknąłem się na to stare pytanie, zastanawiając się nad tym samym. Skończyło się na tym:
echo $(ls *.txt | head -n1)
Można, oczywiście, należy wymienić
head
ztail
i-n1
z żadnym innym numerem.Powyższe nie zadziała, jeśli pracujesz z plikami, które mają nazwy nowego wiersza. Aby pracować z nowymi liniami, możesz użyć dowolnego z tych:
ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'
(Nie działa na BSD)ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
echo -e "$(ls -b *.txt | head -n1)"
(Działa z dowolnym znakiem specjalnym)źródło
Przypadkiem użycia, z którym często się spotykam, jest zdefiniowanie katalogu górnego / dolnego po rozszerzeniu globalnym (np. Katalog pełen wersjonowanych zestawów SDK lub narzędzi do budowania). W tej sytuacji zwykle chcę zapisać nazwę katalogu w zmiennej, aby użyć jej w kilku miejscach w skrypcie powłoki.
To polecenie zwykle robi to dla mnie:
Uwaga: Rozszerzenie Glob nie posortuje folderów według semver; Zostałeś ostrzeżony. Jest to świetne, jeśli masz
Dockerfile
tylko jedną wersję katalogu, ale ta wersja katalogów może różnić się w zależności od obrazu 🤷źródło
mkdir "$(echo one; echo two)"
i zobacz, co mam na myśli.tail
?dirname
przyjmuje tylko jedną ścieżkę, więc nie można polegać na wielu ścieżkach, chyba że wiesz, że obsługuje ją Twoja konkretna implementacja.