Błąd skryptu Bash z ciągami ze ścieżkami zawierającymi spacje i symbole wieloznaczne

Mam problem ze zrozumieniem podstaw obsługi skryptów Bash. Oto, co mam do tej pory:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Chcę tylko wyświetlić listę wszystkich plików .txt w pętli for, więc mogę z nimi robić rzeczy. Ale spacja w my directory i gwiazdka w *.txt po prostu nie grają ładnie. Próbowałem go używać z cudzysłowami i bez cudzysłowów, z nawiasami klamrowymi i bez nich w nazwach zmiennych i nadal nie mogę wydrukować wszystkich plików .txt.

To jest bardzo podstawowa sprawa, ale wciąż mam problemy, ponieważ jestem zmęczony i nie potrafię jasno myśleć.

Co robię źle?

Udało mi się pomyślnie złożyć wniosek powyższy skrypt, jeśli moje PLIKI nie mają spacji ani gwiazdki … Musiałem poeksperymentować z użyciem podwójnych cudzysłowów i nawiasów klamrowych lub bez nich, aby to zadziałało. Ale w chwili, gdy mam zarówno spacje, jak i gwiazdkę, wszystko psuje.

Odpowiedź

W cudzysłowie * nie rozwinie się do listy plików. Aby z powodzeniem użyć takiego symbolu wieloznacznego, musi on znajdować się poza cudzysłowami.

Nawet jeśli znak wieloznaczny się rozwinął, wyrażenie "${FILES}" dałoby pojedynczy ciąg, a nie lista plików.

Jednym z podejść, które by działało, byłoby:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

W powyższym przypadku nazwy plików ze spacjami lub innymi trudnymi znaki będą obsługiwane poprawnie.

Bardziej zaawansowane podejście mogłoby wykorzystać tablice basha:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

W tym przypadku FILES to tablica nazw plików. Pareny otaczające definicję sprawiają, że jest to tablica. Zwróć uwagę, że * jest poza cudzysłowami. Konstrukcja "${FILES[@]}" jest przypadkiem specjalnym: rozwinie się do listy ciągów, gdzie każdy ciąg jest jedną z nazw plików. Nazwy plików ze spacjami lub innymi trudnymi znakami będą obsługiwane poprawnie.

Komentarze

  • świetnie, że działało
  • To ' warto zauważyć, że jeśli ' przekazujesz takie ścieżki przez funkcje, musisz upewnić się, że zmienna jest cytowana samodzielnie zamiast łączyć go jako część większego ciągu: for f in "$DIR"/*.txt = dobrze for f in "$DIR/*.txt" = przerwy

Odpowiedź

Podczas gdy używanie tablic, jak pokazuje John1024, ma o wiele więcej sensu, tutaj możesz również użyć operatora split + glob (pozostawiając zmienna skalarna bez cudzysłowu).

Ponieważ potrzebujesz tylko części glob tego operatora, musisz wyłączyć część split :

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Odpowiedź

Możesz pozostawić tylko symbole wieloznaczne poza cudzysłowami.
Coś w rodzaju:
dla a in „pliki ze spacjami” * „. txt „
wykonaj
przetwarzanie
gotowe
Jeśli same symbole wieloznaczne rozszerzają się do spacji, nie musisz używać metody pliku w wierszu, na przykład użyć ls -l do wygenerowania listy plików i użyj odczytu bash, aby pobrać każdy plik.

Odpowiedź

Rozwiązanie oparte na jednej linii (do uruchomienia w Terminalu):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

w przypadku / OP „zmień "./" na "/home/john/my directory/"

Aby użyć w pliku skryptu:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

Powyższą funkcjonalność można osiągnąć również w ten (zalecany) sposób:

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

Krótki / Krótki OPIS:

"./": to jest katalog bieżący. określ ścieżkę do katalogu.
-not -type d: tutaj -not konfiguruje go, aby pominąć następny wymieniony typ, & następny wymieniony -type to d = katalogi, więc pominie katalogi. Użyj f zamiast d, aby pominąć pliki. Użyj l zamiast d, aby pominąć pliki-dowiązań symbolicznych.
-maxdepth 1: konfiguruje go tak, aby znajdował pliki tylko w bieżącym (aka: jednym) poziomie katalogu. Aby znaleźć plik w każdym & poziomie pierwszego podkatalogu, ustaw maxdepth na 2. Jeśli -maxdepth nie jest używane, przeszukiwanie będzie rekurencyjne (wewnątrz podkatalogów) itd.
-iname "*.jpg": tutaj -iname konfiguruje go tak, aby znajdował pliki i ignorował (górny / dolny) -case w nazwie / rozszerzeniu pliku. -name nie ignoruje wielkości liter. -lname znajduje linki symboliczne. itp.
-print0: wypisuje ścieżkę bieżącego pliku na standardowe wyjście, po której następuje znak ASCII NUL (kod znaku 0), który później wykryć za pomocą read w while.
IFS=: tutaj jest używany w przypadku, gdy nazwa pliku kończy się spacją. Używamy NUL / "" / \0 z IFS do wykrywania każdej znalezionej nazwy pliku. Ponieważ ” find ” jest skonfigurowany tak, aby rozdzielał je \0, który jest tworzony przez -print0.
read -r -d $"\0" fileName: $"\0" to "" / NUL. -r został użyty w przypadku, gdy nazwa pliku zawiera ukośnik odwrotny.
przeczytaj [-ers] [-a nazwa] [-d ogranicznik] [-i tekst] [-n liczbaznaków] [-N liczbaznaków] [-p zachęta] [-t limit czasu] [-u fd] [nazwa. ..] […]
-r Lewy ukośnik nie działa jako znak zmiany znaczenia. Ukośnik odwrotny jest uważany za część wiersza. W szczególności para odwrotny ukośnik-znak nowej linii nie może być używana jako kontynuacja wiersza.
done < <(...): Podstawienie procesu używane tutaj do wysyłania / potokowe wyjście ” znajdź ” w ” przeczytaj ” z ” a ” -loop. Więcej informacji: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


W innej odpowiedzi @ John1024 pokazał świetne rozwiązanie bazujące na bashu, które nie używa ” znajdź „, zewnętrzne narzędzie.
” znajdź ” jest bardzo skuteczny & szybki, wolę go, gdy jest zbyt wiele plików do obsłużenia.

w rozwiązaniu @ John1024 wypisze linia reguły dopasowania, gdy w katalogu nie ma pliku, więc poniżej [ ! -e "${f}" ]... wiersz służy do pominięcia tego,
tutaj jest rozwiązanie jednowierszowe do bezpośredniego użycia w Terminalu:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Oto skrypt:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Uwaga: jeśli katalog w DIR ma "/" ukośnik (wskaźnik katalogu) na końcu, to w regule dopasowania , ponownie użycie "/" nie jest konieczne,
Lub zrób odwrotnie: w DIR nie używaj "/" na końcu więc użyj go w regule dopasowania "$DIR"/*.txt


To dodatkowe sprawdzenie za pomocą [ ! -e "${f}" ]... można uniknąć, jeśli poniżej opcja-powłoki (aka: ” shopt „) jest używany lub włączony:
shopt -s nullglob

Jeśli stan sklepu został zmieniony przez skrypt, to w innym programie skryptowym opartym na bash powoduje to nieoczekiwane / nieprzewidziane problemy.

Aby mieć spójne zachowanie we wszystkich skryptach, które używają bash, stan opcji bash-powłoki powinien zostać zarejestrowany / zapisany w skrypcie, a kiedy skończysz używać podstawowych funkcji w swoim skrypcie, to opcja powłoki powinna zostać przywrócona do poprzednich ustawień.

Używamy odwrotnego klawisza (aka: grave-accent, aka: backquote) `...` podstawianie poleceń (dla wewnętrznych kodów poleceń bash itp.), aby nie tworzyć nowej podpowłoki, zachowaj dosłowne znaczenie odwrotnego ukośnika, dla szerszej obsługi (aka: przenośność) itp., ponieważ wewnętrzne polecenia bash oparte na backticku itp. mogą być często wykonywane w tej samej powłoce co skrypt itp., A więc jest trochę szybszy & lepiej, a także lepiej w celu, który tutaj obsługujemy. Jeśli wolisz $(...) podstawianie poleceń, użyj tego, aby każdy miał swobodę & prawo wyboru tego, co wolał, czego unikać itp. Więcej informacji : tutaj .

Tak więc powyższy skrypt jest ponownie wyświetlany, & tym razem z przywróconymi poprzednimi ustawieniami sklepu, przed zakończeniem skryptu:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Dane wyjściowe shopt -p shoptName (na przykład: shopt -p dotglob) może być
albo: shopt -u shoptName (u to unset / disabled / off / 0)
lub to: shopt -s shoptName ( s to set / enabled / on / 1)
Pozycja litery "s" lub "u" jest zawsze w 7 (ponieważ w bash, a ciąg znaków „pozycja litery zaczyna się od 0, czyli pierwsza litera ciągu znajduje się na pozycji 0)
Mamy może uzyskać ten "u" lub i przechowuj go w zmiennej, abyśmy mogli użyć jej do przywrócenia poprzedniego stanu.
A jeśli zastosujemy ten (wspomniany powyżej) sposób do zapisania / przywrócenia stanu sklepu, możemy unikaj używania zewnętrznego narzędzia "grep".

Aby wyświetlić "txt" plik zaczynający się od ".", to znaczy aby wyświetlić ukryty plik "txt", musimy włączyć "dotglob" shopt.

Więc tym razem poniżej "dotglob" jest włączone &, aby wyświetlać HIDDEN "txt" pliki:

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Istnieje prostszy sposób zapisywania / przywracania shopt opcja / wartość.
Isaac opublikował tutaj , jak zapisać i przywrócić Env / Zmienna Shopt / stan / wartość opcji.

Zapisywanie stanu sklepu ” nullglob „:

... # your primary-function codes/commands, etc lines
Przywracanie poprzedniego stanu sklepu ” nullglob „, przed wyjściem ze skryptu:
eval "$p_nullglob" ;

W ten sposób można zapisać wiele stanów sklepu:
p_multipleShopt="`shopt -p nullglob dotglob`";
i proces przywracania jest taki sam jak poprzednio:
eval "$p_multipleShopt" ;

Zapisz WSZYSTKIE stany Shopt w ten sposób:
p_allShopt="`shopt -p`";
i proces przywracania jest taki sam jak poprzednio:
eval "$p_allShopt" ;

Oto kolejne rozwiązanie oparte na bashu:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Użycie eval jest bezpieczne w powyższym przypadku, ponieważ zmienna "$p_allShopt" nie zawiera danych dostarczonych przez użytkownika lub danych, które nie są sanitized, ta zmienna przechowuje dane wyjściowe wewnętrznego polecenia bash shopt.
Jeśli nadal chcesz uniknąć eval, użyj poniżej, więc lution:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Niewiele (innych) godnych uwagi & powiązane SKLEP , które mogą być przydatne, to:

  • nocaseglob: Jeśli ustawione, Bash dopasowuje nazwy plików w moda bez rozróżniania wielkości liter podczas wykonywania rozwijania nazw plików.
  • nocasematch: Jeśli jest ustawiona, Bash dopasowuje wzorce bez uwzględniania wielkości liter podczas dopasowywania podczas wykonywania case lub [[ polecenia warunkowe, podczas wykonywania interpretacji słów z podstawiania wzorców lub podczas filtrowania możliwych uzupełnień w ramach programowalnego uzupełniania.
  • dotglob: Jeśli jest ustawiona, Bash uwzględnia nazwy plików zaczynające się od ‘.’ w wynikach rozwijania nazw plików. Nazwy plików ‘.’ i ‘..’ muszą być zawsze dopasowane jawnie, nawet jeśli ustawiono dotglob.
  • nullglob: Jeśli ustawione , Bash umożliwia rozwinięcie wzorców nazw plików, które nie pasują do żadnych plików, do łańcucha zerowego zamiast do siebie samych.
  • extglob: Jeśli jest ustawiona, rozszerzone funkcje dopasowywania wzorców opisane powyżej (patrz Dopasowywanie wzorców ).
  • globstar: Jeśli ustawione, wzorzec ‘**’ użyty w kontekście rozszerzania nazwy pliku będzie pasował do wszystkich plików i zero lub więcej katalogów i podkatalogów. Jeśli po wzorcu występuje ‘/’, pasują tylko katalogi i podkatalogi.

Odpowiedź

Jeśli chcesz przetworzyć zestaw plików, weź pod uwagę, że w ich nazwie może znajdować się spacja lub inny kod scape, więc przed rozpoczęciem procesu, na przykład for loop lub find command ustaw IFS bash env variable na:

IFS=$(echo -en "\n\b") 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *