Jak udělat bash glob řetězcovou proměnnou?

Informace o systému

OS: OS X

bash: GNU bash, verze 3.2.57 (1) -release (x86_64-apple-darwin16)

Pozadí

Chci, aby stroj času vyloučil ze všech mých projektů git / nodejs sadu adresářů a souborů. Adresáře mých projektů jsou v ~/code/private/ a ~/code/public/ , takže se snažím použijte smyčku bash k provedení tmutil.

Vydání

Krátká verze

Pokud mám vypočítaná řetězcová proměnná k, jak to udělám globálně nebo přímo před 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 

V dlouhé verzi níže uvidíte k=$i/$j. Řetězec tedy nemohu napevno smyčka for.

Dlouhá verze

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

Výstup

Nejsou označeny globusy. To není to, co chci.

~/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 

Komentáře

  • Jednoduché uvozovky zastaví interpolaci prostředí v Bash, takže můžete zkusit uvést proměnnou dvakrát.
  • @ThomasN ne, to nefunguje. k je vypočítaný řetězec a já ho potřebuji Tímto způsobem až do smyčky. Zkontrolujte moji dlouhou verzi.
  • @ThomasN Aktualizoval jsem krátkou verzi, aby byla jasnější.

Odpovědět

Další kolo hodnocení můžete vynutit pomocí eval, ale to ve skutečnosti není nutné. (A eval začíná mít vážné problémy v okamžiku, kdy názvy souborů obsahují speciální znaky, jako je $.) Problém není s globováním, ale s expanzí tildy.

Globbing se stane po rozšíření proměnné, pokud proměnná není uvedena, jako zde (*) :

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

Takže, ve stejném duchu je to podobné tomu, co jste udělali, a funguje to:

$ 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 s vlnovkou to nedělá „t:

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

Toto je jasně zdokumentováno pro Bash:

Pořadí expanzí je: expanze složené závorky; expanze vlnovky, expanze parametrů a proměnných, …

K expanzi vlnovky dochází před expanzí proměnné, takže tildy uvnitř proměnných se nerozbalí. Snadným řešením je použít $HOME nebo úplnou cestu.

(* rozšíření globů z proměnných obvykle není to, co chcete)


Další věc:

Když procházíte smyčkami přes vzory, jako zde:

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

Všimněte si, že protože $exclude není uveden, je v tomto bodě rozdělen a také globlobován. Takže pokud aktuální adresář obsahuje něco, co odpovídá vzoru, je rozšířeno na toto:

$ 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 

Chcete-li tento problém vyřešit, použijte místo děleného řetězce proměnnou pole / p>

$ 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 další bonus mohou položky pole obsahovat i mezery bez problémů s rozdělením.


Něco podobného lze udělat s find -path, pokud vám nevadí, na jaké úrovni adresáře by cílové soubory měly být. Například najít jakoukoli cestu končící /e2e/*.js:

$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 

Musíme použít $HOME místo ~ ze stejného důvodu jako dříve, a $dirs je třeba uvést find příkazový řádek, aby se rozdělil, ale $pattern by měl být citován, aby nebyl omylem rozšířen shellem.

(Myslím, že byste mohli hrát s -maxdepth na GNU find, abyste omezili hloubku hledání, pokud vám na tom záleží, ale to je trochu jiný problém.)

Komentáře

  • Jste jedinou odpovědí s find? Vlastně také zkoumám tuto cestu, protože smyčka pro smyčku se komplikuje. Ale mám potíže s ‚ -path ‚.
  • Kredit vám jako informace o vlkodlaku ‚ ~ ‚ je více zaměřen na hlavní problém. Konečný scénář a vysvětlení zveřejním v jiné odpovědi. Ale plné uznání: D
  • @JohnSiu, jo, používání find bylo to, co mi poprvé přišlo na mysl. Může to být také použitelné, v závislosti na přesné potřebě. (nebo ještě lépe, pro některá použití.)
  • @kevinarpe, myslím, že pole jsou v podstatě určena právě pro toto, a ano, "${array[@]}" (s uvozovkami! ) je dokumentován (viz zde a zde ) k rozbalení na prvky jako odlišná slova bez dále je rozdělit.
  • @sixtyfive, [abc] je standardní součástí globálních vzorů , jako ?, nemyslím si ‚, že je ‚ nutné je zde všechny pokrýt .

Odpověď

Můžete jej uložit jako pole místo řetězce, který v mnoha případech použijete později, a nechte globování proběhnout, když jej definujete. Ve vašem případě například:

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

nebo v pozdějším příkladu „Bude potřeba eval některé řetězce

 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  

Komentáře

  • Všimněte si, jak $exclude obsahuje zástupné znaky, ‚ d je třeba před použitím operátoru split + glob na něm deaktivovat globbing a obnovit jej pro $i/$j a nepoužívat použití eval ale použijte "$i"/$j
  • Vy i ilkkachu dáváte dobrou odpověď. Jeho odpověď však problém identifikovala. Takže úvěr jemu.

Odpověď

Odpověď @ilkkachu vyřešila hlavní problém globalizace. Plná zásluha.

V1

Nicméně exclude obsahující položky se zástupným znakem i bez něj (*) a také nemusí existovat Celkově je po globování $i/$j zapotřebí další kontrola. Sdílím zde svá zjištění.

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

Vysvětlení výstupu

Následuje částečný výstup vysvětlující situaci.

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

Výše uvedené jsou vysvětlující.

/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 

Výše uvedené se zobrazí, protože položka vyloučení ($j) nemá žádný zástupný znak, $i/$j se stane prostým zřetězením řetězce. Soubor / adresář však neexistuje.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 

Výše uvedené položky se zobrazují jako vyloučená položka ($j) obsahují zástupný znak, ale nemá žádnou shodu souboru / adresáře, globbing $i/$j stačí vrátit původní řetězec.

V2

V2 použít jednoduchou uvozovku, eval a shopt -s nullglob získáte čistý výsledek. Není nutná žádná konečná kontrola souboru / adresáře.

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

Komentáře

  • Jedním problémem je, že v for j in $exclude, globy v $exclude se mohly v době této $exclude expanze (a volání) rozšířit eval o to žádá potíže). ‚ Chcete mít povoleno globování pro for i in $dir a for l in $k, ale ne pro for j in $exclude. ‚ Chcete set -f před druhou a set +f pro druhou. Obecněji řečeno, ‚ byste chtěli vyladit operátor split + glob před jeho použitím. V každém případě ‚ nechcete split + glob pro echo $l, takže $l by tam měl být citován.
  • @St é phaneChazelas, máte na mysli v1 nebo v2? U verze 2 jsou exclude a dirs uvedeny v jediné citaci (), so no globbing till eval`.
  • Globování se odehrává při nekótované proměnné expanzi v kontextech seznamu , což (ponechání nekótované proměnné) je to, co někdy zavolat operátor split + glob . ‚ v přiřazeních ke skalárním proměnným nedochází k žádnému globbingu. foo=* a foo='*' je stejný. Ale echo $foo a echo "$foo" nejsou (ve skořápkách jako bash, ‚ bylo opraveno ve skořápkách jako zsh, fish nebo rc, viz také výše uvedený odkaz). Zde uděláte chci použít tento operátor, ale na některých místech pouze rozdělená část a na jiných pouze globální část.
  • @St é phaneChazelas Díky za informace !!! Vzal mě někdy, ale nyní chápu obavy. To je velmi cenné !! Děkuji !!!

Odpověď

S zsh:

 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  

${^array}string má expandovat jako $array[1]string $array[2]string.... $=var je rozdělení slov na proměnnou (ve výchozím nastavení to dělají i jiné skořápky!), $~var provede na proměnné globování (něco jiného) granáty také ve výchozím nastavení (pokud je obvykle nechcete, musíte v jiných skořápkách citovat $f výše)).

(N) je kvalifikátor glob, který zapíná nullglob pro každý z globů vyplývající z $^array1/$^array2 expanze. To způsobí, že se koule globálně rozšíří na nic, když se neshodují. To také způsobí, že se z globálu podobného ~/code/private/foo/Thumbs.db stane jeden, což znamená, že pokud ten konkrétní neexistuje, není to zahrnuto.

Komentáře

  • To je opravdu pěkné. Testoval jsem a funguje. Zdá se však, že zsh je citlivější na nový řádek, když pomocí jednoduché uvozovky. Způsob, jakým je exclude uzavřen, ovlivňuje výstup.
  • @JohnSiu, ach ano, ‚ máte pravdu. Zdá se, že split + glob a $^array musí být provedeno ve dvou samostatných krocích, aby se zajistilo, že budou prázdné prvky zahozeny (viz úprava). To vypadá trochu jako chyba v zsh, ‚ upozorním na problém v jejich e-mailovém seznamu.
  • Přijdu s v2 pro bash, který je čistší, ale stále není tak kompaktní jako váš skript zsh, lol

odpověď

starý příspěvek, ale narazil jsem sem, takže t myslel jsem, že to může pomoci ostatním 🙂

V bash je funkce kinda-glob () … nazývá se to compgen -G Vypisuje hodnotu na řádek, takže k práci potřebuje readarray.

Vyzkoušejte toto:

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 

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *