Informacje o systemie
OS: OS X
bash: GNU bash, wersja 3.2.57 (1) -release (x86_64-apple-darwin16)
Tło
Chcę, aby wehikuł czasu wykluczył zestaw katalogów i plików ze wszystkich moich projektów git / nodejs. Moje katalogi projektów znajdują się w ~/code/private/
i ~/code/public/
, więc próbuję użyj pętli basha, aby wykonać tmutil
.
Problem
Krótka wersja
Jeśli mam obliczona zmienna łańcuchowa k
, jak ustawić ją glob w lub tuż przed for loop:
i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" for i in $k # I need $k to glob here do echo $i done
W długiej wersji poniżej zobaczysz k=$i/$j
. Dlatego nie mogę zakodować na stałe ciągu pętla for.
Długa wersja
#!/bin/bash exclude=" *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" ~/code/private/* ~/code/public/* " for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done
Dane wyjściowe
Nie są globowane. Nie to, czego chcę.
~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings
Komentarze
Odpowiedź
Możesz wymusić kolejną rundę oceny za pomocą eval
, ale w rzeczywistości nie jest to konieczne. (I eval
zaczyna się mają poważne problemy w chwili, gdy nazwy plików zawierają znaki specjalne, takie jak $
.) Problem nie dotyczy globowania, ale rozwijania tyldy.
Globbing się dzieje po rozwinięciu zmiennej, jeśli zmienna nie jest cytowana, jak tutaj (*) :
$ x="/tm*" ; echo $x /tmp
A więc, w tym samym duchu jest to podobne do tego, co zrobiłeś i działa:
$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch
Ale z tyldą nie „t:
$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch
To jest jasno udokumentowane dla Bash:
Kolejność rozwinięć jest następująca: interpretacja nawiasów; interpretacja tyldy, interpretacja parametrów i zmiennych, …
Rozwinięcie tyldy następuje przed rozwinięciem zmiennej, więc tyldy wewnątrz zmiennych nie są rozwijane. Łatwym obejściem jest użycie $HOME
lub pełnej ścieżki zamiast tego.
(* rozwijanie globów ze zmiennych zwykle nie jest tym, czego chcesz)
Kolejna rzecz:
Kiedy zapętlasz wzorce, jak tutaj:
exclude="foo *bar" for j in $exclude ; do ...
zwróć uwagę, że ponieważ $exclude
nie jest cytowany, w tym momencie jest zarówno podzielony, jak i globowany. Jeśli więc bieżący katalog zawiera coś pasującego do wzorca, jest rozwijany do tego:
$ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # split and glob, no match echo "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # split and glob, matches in current dir! echo "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result
Aby obejść ten problem, użyj zmiennej tablicowej zamiast podzielonego ciągu:
$ exclude=("*.launch") $ exclude+=("something else") $ for j in "${exclude[@]}" ; do echo "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/something else
Dodatkową korzyścią jest to, że wpisy w tablicy mogą również zawierać spacje bez problemów z dzieleniem.
Coś podobnego można zrobić z find -path
, jeśli nie masz nic przeciwko, na jakim poziomie katalogu powinny znajdować się docelowe pliki. Np. aby znaleźć ścieżkę kończącą się na /e2e/*.js
:
$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js
Musimy użyć $HOME
zamiast ~
z tego samego powodu co poprzednio, a $dirs
nie może być cytowany w find
, więc zostanie podzielony, ale $pattern
powinien być cytowany, aby nie został przypadkowo rozwinięty przez powłokę.
(Myślę, że mógłbyś pobawić się -maxdepth
na GNU, aby ograniczyć głębokość wyszukiwania, jeśli cię to obchodzi, ale to trochę inna kwestia.)
Komentarze
- Czy jesteś jedyną odpowiedzią z
find
? Właściwie to też badam tę trasę, ponieważ pętla for staje się skomplikowana. Ale mam problem z ' -ścieżką '. - Podziękowania dla Ciebie jako informacji o tyldie ' ~ ' jest bardziej bezpośrednie do głównego problemu. Ostateczny scenariusz i wyjaśnienie zamieszczę w innej odpowiedzi. Ale całe uznanie dla ciebie: D
- @JohnSiu, tak, użycie find było tym, co najpierw przyszło mi do głowy. Może się również przydać, w zależności od dokładnej potrzeby. (lub lepiej, do niektórych zastosowań).
- @kevinarpe, myślę, że tablice są przeznaczone właśnie do tego i tak,
"${array[@]}"
(w cudzysłowie! ) jest udokumentowana (patrz tutaj i tutaj ), aby rozwinąć elementy jako odrębne słowa bez dzieląc je dalej. - @sixtyfive, cóż,
[abc]
to standardowa część wzorców glob , na przykład?
, nie ' nie sądzę, aby ' omówić je wszystkie tutaj .
Odpowiedź
Możesz zapisać ją jako tablicę zamiast łańcucha do późniejszego wykorzystania w wielu przypadkach i pozwól, by globbing się wydarzył, kiedy go zdefiniujesz. W Twoim przypadku, na przykład:
k=(~/code/public/*/*.launch) for i in "${k[@]}"; do
lub w późniejszym przykładzie „Będę potrzebował eval
niektórych ciągów
dirs=(~/code/private/* ~/code/public/*) for i in "${dirs[@]}"; do for j in $exclude; do eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done" done done
Komentarze
- Zwróć uwagę, że
$exclude
zawiera symbole wieloznaczne, ' d należy wyłączyć globbing przed użyciem na nim operatora split + glob i przywrócić go dla$i/$j
i nie używaćeval
, ale użyj"$i"/$j
- Zarówno ty, jak i ilkkachu udzielicie dobrej odpowiedzi. Jednak jego odpowiedź zidentyfikowała problem. do niego.
Odpowiedź
@ilkkachu odpowiedź rozwiązała główny problem z globowaniem. Pełne uznanie dla niego.
V1
Jednakże, ponieważ exclude
zawiera wpisy zarówno z symbolem wieloznacznym (*), jak i bez, a także mogą one nie istnieć Podsumowując, potrzebne jest dodatkowe sprawdzenie po globowaniu $i/$j
. Udostępniam tutaj moje ustalenia.
#!/bin/bash exclude=" *.launch .DS_Store .classpath .sass-cache .settings Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " # loop $dirs for i in $dirs; do for j in $exclude ; do for k in $i/$j; do echo -e "$k" if [ -f $k ] || [ -d $k ] ; then # Only execute command if dir/file exist echo -e "\t^^^ Above file/dir exist! ^^^" fi done done done
Wyjaśnienie wyników
Poniżej przedstawiono częściowe dane wyjściowe wyjaśniające sytuację.
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store ^^^ Above file/dir exist! ^^^
Powyższe nie wymagają wyjaśnień.
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist
Powyższe pojawia się, ponieważ pozycja wykluczająca ($j
) nie ma symbolu wieloznacznego, $i/$j
staje się zwykłym połączeniem ciągów. Jednak plik / katalog nie istnieje.
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map
Powyższe jest wyświetlane jako pozycja wykluczająca ($j
) zawiera symbol wieloznaczny, ale nie ma dopasowania do pliku / katalogu, globowanie $i/$j
po prostu zwraca oryginalny ciąg.
V2
V2 używa pojedynczego cudzysłowu, eval
i shopt -s nullglob
, aby uzyskać czysty wynik. Żadne ostateczne sprawdzenie pliku / katalogu nie jest wymagane.
#!/bin/bash exclude=" *.launch .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " for i in $dirs; do for j in $exclude ; do shopt -s nullglob eval "k=$i/$j" for l in $k; do echo $l done shopt -u nullglob done done
Komentarze
- Jednym z problemów jest to, że
for j in $exclude
, globy w$exclude
mogły zostać rozwinięte w czasie tego$exclude
rozwinięcia (i wywołaniaeval
na tym, że prosi o kłopoty). ' chcesz włączyć globbing dlafor i in $dir
ifor l in $k
, ale nie dlafor j in $exclude
. ' chcesz miećset -f
przed drugim iset +f
dla drugiego. Mówiąc bardziej ogólnie, ' chcesz dostroić operator split + glob przed jego użyciem. W każdym razie nie ' nie chcesz podzielić + glob dlaecho $l
, więc$l
powinny być tam cytowane. - @St é phaneChazelas odnosisz się do wersji 1 czy 2? W wersji 2 zarówno
exclude
, jak idirs
są ujęte w pojedynczy cudzysłów (), so no globbing till
eval`. - Globbing ma miejsce po rozwinięciu niecytowanej zmiennej w kontekstach listowych , czyli pozostawieniu zmiennej bez cytowania czasami wywołuj operator split + glob . ' nie ma żadnego globowania w przypisaniach do zmiennych skalarnych.
foo=*
ifoo='*'
to to samo. Aleecho $foo
iecho "$foo"
nie są (w powłokach takich jakbash
, to ' zostało naprawione w muszlach takich jak zsh, fish lub rc, zobacz także link powyżej). Tutaj zrobić chcę użyć tego operatora, ale w niektórych miejscach tylko część podzielona, a w innych tylko część glob. - @St é phaneChazelas Dzięki za informacje !!! Zajęło mi to trochę czasu, ale teraz rozumiem problem. To bardzo cenne! Dziękuję !!!
Odpowiedź
Z zsh
:
exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }
${^array}string
ma zostać rozwinięte jako $array[1]string $array[2]string...
. $=var
ma wykonać podział na słowa w zmiennej (coś, co inne powłoki robią domyślnie!), $~var
robi globbing na zmiennej (coś innego powłoki również domyślnie (jeśli generalnie tego nie chcesz, musiałbyś zacytować $f
powyżej w innych powłokach)).
(N)
to kwalifikator glob, który włącza nullglob dla każdej z tych globów wynikających z tego $^array1/$^array2
rozszerzenie. To sprawia, że globy rozwijają się do zera, gdy nie pasują. To również zmienia nie-glob, taki jak ~/code/private/foo/Thumbs.db
, w jeden, co oznacza, że jeśli ten konkretny nie istnieje, nie jest uwzględniony.
Komentarze
- To jest naprawdę fajne. Testowałem i działa. Jednak wydaje się, że zsh jest bardziej wrażliwy na znak nowej linii, gdy używając pojedynczego cudzysłowu. Sposób ujęcia
exclude
ma wpływ na wynik. - @JohnSiu, o tak, ' w porządku. Wygląda na to, że podział + glob i
$^array
należy wykonać w dwóch osobnych krokach, aby upewnić się, że puste elementy zostały odrzucone (patrz edycja). Wygląda to trochę jak błąd wzsh
, ' podniosę ten problem na ich liście mailingowej. - Wymyślam wersję 2 dla basha, który jest bardziej przejrzysty, ale nadal nie jest tak zwarty jak twój skrypt zsh, lol
Odpowiedź
Stary post, ale natknąłem się tutaj, więc t Pomyślałem, że może to pomóc innym 🙂
W bash jest funkcja kinda-glob () … nazywa się ona compgen -G Wyprowadza wartość w każdym wierszu, więc do działania potrzebuje tablicy readarray.
Spróbuj tego:
i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done
k
to ciąg obliczeniowy i potrzebuję go sta y w ten sposób, aż do pętli. Sprawdź moją długą wersję.