Jak uczynić bash glob zmienną łańcuchową?

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

  • Pojedyncze cudzysłowy zatrzymują interpolację powłoki w Bash, więc możesz spróbować podwójnie cytować zmienną.
  • @ThomasN nie, to nie działa. k to ciąg obliczeniowy i potrzebuję go sta y w ten sposób, aż do pętli. Sprawdź moją długą wersję.
  • @ThomasN Zaktualizowałem krótką wersję, aby była jaśniejsza.

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łania eval na tym, że prosi o kłopoty). ' chcesz włączyć globbing dla for i in $dir i for l in $k, ale nie dla for j in $exclude. ' chcesz mieć set -f przed drugim i set +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 dla echo $l, więc $l powinny być tam cytowane.
  • @St é phaneChazelas odnosisz się do wersji 1 czy 2? W wersji 2 zarówno exclude, jak i dirs 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=* i foo='*' to to samo. Ale echo $foo i echo "$foo" nie są (w powłokach takich jak bash, 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 w zsh, ' 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 

Dodaj komentarz

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