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


14

Informacja o systemie

System operacyjny: OS X

bash: GNU bash, wersja 3.2.57 (1) - wydanie (x86_64-apple-darwin16)

tło

Chcę, aby wehikuł czasu wykluczył zestaw katalogów i plików ze wszystkich moich projektów git / nodejs. Moi katalogów projektu są ~/code/private/i ~/code/public/tak staram się używać bash pętli wykonać tmutil.

Kwestia

Krótka wersja

Jeśli mam obliczoną zmienną łańcuchową k, jak sprawić, by była globalna w pętli for lub tuż przed nią:

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. Nie mogę więc zakodować ciągu w pętli 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

Wynik

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

Pojedyncze cudzysłowy zatrzymują interpolację powłoki w Bash, więc możesz spróbować podwójnego cytowania swojej zmiennej.
Thomas N,

@ThomasN nie, to nie działa. kjest ciągiem obliczonym i potrzebuję, aby został w pętli. Proszę sprawdzić moją długą wersję.
John Siu,

@ThomasN Zaktualizowałem krótką wersję, aby była jaśniejsza.
John Siu,

Odpowiedzi:


18

Możesz wymusić kolejną rundę oceny eval, ale tak naprawdę nie jest to konieczne. (I evalzaczyna mieć poważne problemy, gdy nazwy plików zawierają znaki specjalne, takie jak $.) Problem nie dotyczy globowania, ale rozszerzenia tyld.

Globowanie następuje po rozwinięciu zmiennej, jeśli zmienna nie jest cytowana, jak tutaj (*) :

$ x="/tm*" ; echo $x
/tmp

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:

$ i="~/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
~/public/*/*.launch

Jest to wyraźnie udokumentowane dla Bash:

Kolejność rozszerzeń jest następująca: nawias klamrowy; ekspansja tyldy, ekspansja 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 $HOMEzamiast tego pełnej ścieżki.

(* rozszerzanie globów ze zmiennych zwykle nie jest tym, czego chcesz)


Inna rzecz:

Kiedy zapętlasz wzory, jak tutaj:

exclude="foo *bar"
for j in $exclude ; do
    ...

zauważ, że jak $excludeto nie jest cytowane, jest zarówno podzielone, jak i globowane w tym momencie. Jeśli więc bieżący katalog zawiera coś pasującego do wzorca, zostanie rozwinięty 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 łańcucha:

$ 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

Jako dodatkowy bonus wpisy tablicy mogą również zawierać białe znaki bez problemów z podziałem.


Można zrobić coś podobnego find -path, jeśli nie przeszkadza ci, na jakim poziomie katalogu powinny znajdować się pliki docelowe. 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ć $HOMEzamiast ~tego samego powodu jak poprzednio, i $dirsmusi być nienotowane na findlinii poleceń więc robi rozłam, ale $patternpowinny być cytowane, więc nie jest przypadkowo rozszerzony przez powłokę.

(Myślę, że możesz grać -maxdepthGNU find, aby ograniczyć zasięg wyszukiwania, jeśli cię to obchodzi, ale to trochę inna kwestia).


Czy jesteś tą jedyną odpowiedzią find? Właściwie też badam tę trasę, ponieważ pętla for staje się skomplikowana. Ale mam trudności z „-path”.
John Siu,

Podziękowania dla Ciebie, ponieważ informacje o tyldach „~” są bardziej bezpośrednie w głównej kwestii. Ostateczny skrypt i wyjaśnienie opublikuję w innej odpowiedzi. Ale pełne uznanie dla Ciebie: D
John Siu

@JohnSiu, tak, najpierw przyszło mi na myśl find. Może być także użyteczny, w zależności od dokładnej potrzeby. (lub lepiej, dla niektórych zastosowań.)
ilkkachu

1
@kevinarpe, myślę, że tablice są w zasadzie przeznaczone tylko do tego, i tak, "${array[@]}"(z cytatami!) jest udokumentowane (patrz tutaj i tutaj ), aby rozwinąć elementy jako odrębne słowa bez dalszego ich podziału.
ilkkachu

1
@sixtyfive, cóż, [abc]jest standardową częścią globalnych wzorców , na przykład ?, nie sądzę, że konieczne jest omówienie ich wszystkich tutaj.
ilkkachu

4

Możesz zapisać go jako tablicę zamiast łańcucha, aby użyć go później w wielu przypadkach i pozwolić na tworzenie globowania podczas jego definiowania. W twoim przypadku na przykład:

k=(~/code/public/*/*.launch)
for i in "${k[@]}"; do

lub w późniejszym przykładzie będziesz potrzebować evalniektó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

1
Zwróć uwagę, jak $excludezawierają symbole wieloznaczne, musisz wyłączyć globowanie przed użyciem na nim operatora split + glob i przywrócić go do $i/$ji nie używać, evalale używać"$i"/$j
Stéphane Chazelas

Zarówno ty, jak i ilkkachu dajecie dobrą odpowiedź. Jednak jego odpowiedź zidentyfikowała problem. Podziękowania dla niego.
John Siu,

2

Odpowiedź @ilkkachu rozwiązała główny problem globbing. Pełne uznanie dla niego.

V1

Jednak ze względu na to, że excludezawiera wpisy zarówno z symbolem wieloznacznym, jak i bez niego (*), a także mogą one nie istnieć we wszystkich przypadkach, po dodaniu globbing konieczne jest dodatkowe sprawdzenie $i/$j. Tutaj dzielę się swoimi odkryciami.

#!/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 znajduje się częściowy wynik wyjaśniający 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 są oczywiste.

/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ż exclude entry ( $j) nie ma symboli wieloznacznych, $i/$jstaje się zwykłym łączeniem łańcuchó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 wyświetlają się jako wykluczenie wpisu ( $j) zawierają symbol wieloznaczny, ale nie pasuje do pliku / katalogu, globbing po $i/$jprostu zwraca oryginalny ciąg.

V2

V2 użyć pojedynczego cudzysłowu, evala shopt -s nullglobaby uzyskać czysty wynik. Nie wymaga sprawdzania końcowego pliku / katalogu.

#!/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

Jednym z problemów jest to, że w tym for j in $excludeczasie globusy $excludemogą się rozszerzać w czasie tej $excludeekspansji (a wywołanie evaltego wymaga kłopotów). Chcesz włączyć globowanie dla for i in $diri for l in $k, ale nie dla for j in $exclude. Chciałbyś set -fprzed tym drugim i set +fdrugim. Mówiąc bardziej ogólnie, przed użyciem należy dostroić operatora split + glob. W każdym razie nie chcesz, aby split + glob był echo $l, więc $lpowinieneś się tam zacytować.
Stéphane Chazelas,

@ StéphaneChazelas odnosi się do v1 lub v2? Dla v2, zarówno excludei dirssą w pojedynczy cudzysłów ( ), so no globbing till eval`.
John Siu

Globowanie odbywa się po niekwotowanym rozwinięciu zmiennej w kontekście listy , co (pozostawiając zmienną bez cudzysłowu) jest czasem tym, co nazywamy operatorem split + glob . W przypisaniach do zmiennych skalarnych nie ma globowania. foo=*i foo='*'jest taki sam. Ale echo $fooi echo "$foo"tak nie jest (w takich powłokach bash, zostało to naprawione w powłokach takich jak zsh, fish lub rc, patrz także link powyżej). Tu nie chcą korzystać z tego operatora, ale w niektórych miejscach tylko część podziału, aw innych tylko część glob.
Stéphane Chazelas,

@ StéphaneChazelas Dzięki za informację !!! Zajęło mi to kiedyś, ale teraz rozumiem obawy. To bardzo cenne !! Dziękuję Ci!!!
John Siu,

1

Z zsh:

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}stringjest rozwinąć jako $array[1]string $array[2]string.... $=varpolega na dzieleniu słów na zmienną (coś, co inne powłoki domyślnie robią!), $~varwykonuje globowanie na zmiennej (coś, co również inne powłoki domyślnie (gdy generalnie nie chcesz, żebyś musiał to zrobić cytat $fpowyżej) inne muszle)).

(N)to kwalifikator globu, który włącza nullglob dla każdej z globusów wynikających z tego $^array1/$^array2rozszerzenia. To sprawia, że ​​globusy rozszerzają się do zera, gdy się nie zgadzają. Zdarza się również, że zamienia nie-glob podobny ~/code/private/foo/Thumbs.dbdo jednego, co oznacza, że ​​jeśli ten konkretny nie istnieje, nie zostanie uwzględniony.


To jest naprawdę miłe. Testowałem i działa. Jednak wydaje się, że zsh jest bardziej wrażliwy na znak nowej linii, gdy używasz pojedynczego cudzysłowu. Sposób, w jaki excludejest zamknięty, wpływa na wynik.
John Siu,

@JohnSiu, o tak, masz rację. Wydaje się, że split + glob i $^arraynależy to zrobić w dwóch osobnych krokach, aby upewnić się, że puste elementy zostaną odrzucone (patrz edycja). Wygląda to trochę jak błąd. zshPodniosę problem na ich liście mailingowej.
Stéphane Chazelas,

Wymyślam v2 dla bash, która jest czystsza, ale wciąż nie tak kompaktowa jak twój skrypt zsh, lol
John Siu
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.