Como tornar o bash glob uma variável de string?

Informações do sistema

SO: OS X

bash: GNU bash, versão 3.2.57 (1) -release (x86_64-apple-darwin16)

Histórico

Eu quero que a máquina do tempo exclua um conjunto de diretórios e arquivos de todos os meus projetos git / nodejs. Meus diretórios de projeto estão em ~/code/private/ e ~/code/public/ , então estou tentando use o bash looping para fazer o tmutil.

Problema

Versão curta

Se eu tiver um calculado variável de string k, como faço para torná-lo global ou imediatamente antes de um 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 

Na versão longa abaixo, você verá k=$i/$j. Portanto, não posso codificar a string em o loop for.

Versão longa

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

Saída

Eles não são globalizados. Não é o que eu quero.

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

Comentários

  • As aspas simples param a interpolação do shell no Bash, então você pode tentar aspas duplas em sua variável.
  • @ThomasN não, isso não funciona. k é uma string calculada, e eu preciso dela sta y dessa forma até o loop. Verifique minha versão longa.
  • @ThomasN Atualizei a versão curta para torná-la mais clara.

Resposta

Você pode forçar outra rodada de avaliação com eval, mas isso” não é realmente necessário. (E eval começa tendo sérios problemas no momento em que seus nomes de arquivo contêm caracteres especiais como $.) O problema não é com globbing, mas com a expansão do til.

Globbing acontece após a expansão da variável, se a variável não estiver entre aspas, como aqui (*) :

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

Então, na mesma linha, isso é semelhante ao que você fez, e funciona:

$ 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 

Mas com o til não “t:

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

Isso está claramente documentado para Bash:

A ordem das expansões é: expansão de chave; expansão de til, expansão de parâmetro e variável, …

A expansão do til acontece antes da expansão da variável, então os til dentro das variáveis não são expandidos. A solução mais fácil é usar $HOME ou o caminho completo.

(* expandir globs a partir de variáveis geralmente não é o que você deseja)


Outra coisa:

Quando você faz um loop sobre os padrões, como aqui:

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

observe que como $exclude não está entre aspas, é dividido e também globalizado neste ponto. Portanto, se o diretório atual contiver algo que corresponda ao padrão, ele será expandido para:

$ 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 

Para contornar isso, use uma variável de matriz em vez de uma string dividida:

$ 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 

Como um bônus adicional, as entradas da matriz também podem conter espaços em branco sem problemas de divisão.


Algo semelhante pode ser feito com find -path, se você não se importa em qual nível de diretório os arquivos de destino devem estar. Por exemplo, para encontrar qualquer caminho que termine em /e2e/*.js:

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

Temos que usar $HOME em vez de ~ pelo mesmo motivo de antes, e $dirs precisa ser desquitado no find linha de comando para que seja dividida, mas $pattern deve estar entre aspas para que não seja acidentalmente expandido pelo shell.

(Acho que você poderia brincar com -maxdepth no GNU find para limitar a profundidade da pesquisa, se você se importar, mas isso “é um problema um pouco diferente.)

Comentários

  • Você é a única resposta com find? Na verdade, estou explorando essa rota também, pois o loop for está ficando complicado. Mas estou tendo dificuldade com o ‘ -path ‘.
  • Crédito a você como sua informação sobre til ‘ ~ ‘ é mais direto ao problema principal. Vou postar o roteiro final e a explicação em outra resposta. Mas todos os créditos para você: D
  • @JohnSiu, sim, usar find foi o que primeiro veio à mente. Também pode ser utilizável, dependendo da necessidade exata. (ou melhor também, para alguns usos.)
  • @kevinarpe, acho que os arrays são basicamente destinados exatamente a isso, e sim, "${array[@]}" (com as aspas! ) está documentado (consulte aqui e aqui ) para expandir os elementos como palavras distintas sem dividindo-os ainda mais.
  • @sixtyfive, bem, [abc] é uma parte padrão dos padrões glob , como ?, eu não ‘ acho que ‘ é necessário cobrir todos eles aqui .

Resposta

Você pode salvá-lo como uma matriz em vez de uma string para usar posteriormente em muitos casos e deixe o globbing acontecer quando você o definir. No seu caso, por exemplo:

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

ou no exemplo posterior, você “Vou precisar eval algumas das strings

 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  

Comentários

  • Observe como $exclude contém curingas, você ‘ d precisa desativar o globbing antes de usar o operador split + glob nele e restaurá-lo para $i/$j e não usar eval mas use "$i"/$j
  • Você e ilkkachu dão uma boa resposta. No entanto, sua resposta identificou o problema. Portanto, crédito para ele.

Resposta

@ilkkachu answer resolveu o principal problema de globbing. Crédito total para ele.

V1

No entanto, devido a exclude conter entradas com e sem curinga (*), e também podem não existir Ao todo, uma verificação extra é necessária após o globbing de $i/$j. Estou compartilhando minhas descobertas aqui.

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

Explicação da saída

A seguir está a saída parcial para explicar a situação.

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

Os itens acima são autoexplicativos.

/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 

O acima aparece porque a entrada de exclusão ($j) não tem curinga, $i/$j torna-se uma concatenação de string simples. No entanto, o arquivo / dir não existe.

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

O acima aparece como entrada de exclusão ($j) contém curinga, mas não tem correspondência de arquivo / diretório, o globbing de $i/$j apenas retorna a string original.

V2

V2 usa aspas simples, eval e shopt -s nullglob para obter um resultado limpo. Nenhuma verificação final de arquivo / dir é necessária.

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

Comentários

  • Um problema é que em for j in $exclude, os globs em $exclude poderiam ser expandidos no momento dessa $exclude expansão (e chamada eval em que está pedindo problemas). Você ‘ d deseja que o globbing seja ativado para for i in $dir e for l in $k, mas não para for j in $exclude. Você ‘ d deseja um set -f antes do último e um set +f para o outro. De maneira mais geral, você ‘ d deseja ajustar seu operador split + glob antes de usá-lo. Em qualquer caso, você não ‘ quer dividir + glob para echo $l, então $l deve ser citado lá.
  • @St é phaneChazelas você está se referindo a v1 ou v2? Para v2, exclude e dirs estão entre aspas simples (), so no globbing till eval`.
  • Globbing ocorre na expansão da variável não citada em contextos de lista , que (deixando uma variável não citada) é o que nós às vezes chame o operador split + glob . Não há ‘ qualquer globbing nas atribuições de variáveis escalares. foo=* e foo='*' é o mesmo. Mas echo $foo e echo "$foo" não são (em shells como bash, ‘ foi corrigido em conchas como zsh, fish ou rc, consulte também o link acima). Aqui você faz deseja usar esse operador, mas em alguns lugares apenas a parte dividida e em outros apenas a parte global.
  • @St é phaneChazelas Obrigado por a informação !!! Demorou um pouco, mas agora entendo a preocupação. Isso é muito valioso !! Obrigado !!!

Resposta

Com zsh:

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

${^array}string deve expandir como $array[1]string $array[2]string.... $=var é realizar a divisão de palavras na variável (algo que outros shells fazem por padrão!), $~var faz globbing na variável (algo diferente shells também por padrão (quando você geralmente não quer, você teria que citar $f acima em outros shells).

(N) é um qualificador glob que ativa nullglob para cada um desses globs resultantes dessa $^array1/$^array2. Isso faz com que os globs se expandam para nada quando eles não correspondem. Isso também transforma um não-glob como ~/code/private/foo/Thumbs.db em um, o que significa que, se aquele particular não existe, não está incluído.

Comentários

  • Isso é muito bom. Eu testei e funciona. No entanto, parece que o zsh é mais sensível a novas linhas quando usando aspas simples. A maneira como exclude é delimitado está afetando a saída.
  • @JohnSiu, ah, sim, você ‘ está certo. Parece que o split + glob e o devem ser feitos em duas etapas separadas para garantir que os elementos vazios sejam descartados (consulte a edição). bug em zsh, eu ‘ levantarei o problema em sua lista de e-mails.
  • Eu proponho uma v2 para bash, que é mais limpo, mas ainda não tão compacto quanto seu script zsh, lol

Resposta

Antigo postar, mas eu tropecei aqui então t achei que isso pudesse ajudar outros 🙂

Há uma função kinda-glob () no bash … é chamada de compgen -G. Ela produz um valor por linha, portanto, precisa de um readarray para funcionar.

Tente isto:

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 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *