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
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")
for f in "$DIR"/*.txt
= dobrzefor f in "$DIR/*.txt"
= przerwy