Como fazer bash glob uma variável de string?

9

Informações do sistema

OS: OS X

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

Antecedentes

Eu quero que o time machine exclua um conjunto de diretórios e arquivos de todo o meu projeto git / nodejs. Meus diretórios de projeto estão em ~/code/private/ e ~/code/public/ , então estou tentando usar o loop bash para fazer o tmutil .

Problema

Versão resumida

Se eu tiver uma variável de string calculada k , como faço para globar ou antes de um loop for:

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 no 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

Resultado

Eles não são globbed. 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
    
por John Siu 06.10.2016 / 20:29

4 respostas

10

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

Globbing acontece após expansão da variável, se a variável não estiver marcada, 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 isso não acontece:

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

Isso é claramente documentado para o Bash:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, ...

A expansão de til ocorre antes da expansão da variável, de modo que as propriedades dentro das variáveis não são expandidas. A solução fácil é para usar $HOME ou o caminho completo.

(* globs de expansão de variáveis geralmente não é o que você quer)

Outra coisa:

Quando você percorre os padrões, como aqui:

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

observe que, como $exclude não é citada, ela é dividida e também é globalizada nesse momento. Portanto, se o diretório atual contiver algo que corresponda ao padrão, ele será expandido para isso:

$ 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 resolver 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 de matriz também podem conter espaços em branco sem problemas com divisão.

Algo semelhante pode ser feito com find -path , se você não se importar com o nível de diretório dos arquivos de destino. 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

Precisamos usar $HOME em vez de ~ pelo mesmo motivo de antes e $dirs precisa ser excluído da linha de comando find para que seja dividido, mas $pattern deve ser citado assim não é expandido acidentalmente pelo shell.

(Eu acho que você poderia brincar com -maxdepth no GNU para limitar a profundidade da busca, se você se importa, mas isso é um pouco diferente).

    
por 06.10.2016 / 21:08
3

Você pode salvá-lo como uma matriz em vez de uma string para usar posteriormente em muitos casos e permitir que a globulação ocorra ao defini-la. No seu caso, por exemplo:

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

ou no exemplo anterior, você precisará eval de 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
    
por 06.10.2016 / 21:05
3

@ilkkachu resposta resolveu a questão principal globbing. Crédito total para ele.

V1

No entanto, devido a exclude contendo entradas com e sem caractere curinga (*), e também podem não existir ao todo, é necessária uma verificação extra após a globalização 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 de saída

A seguir, 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 auto-explicativos.

/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

As opções acima são exibidas porque a entrada de exclusão ( $j ) não possui nenhum caractere curinga, $i/$j se torna uma concatenação de cadeia 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

As mostradas acima como exclusão de entrada ( $j ) contêm curinga, mas não tem correspondência de arquivo / diretório, a globulação de $i/$j apenas retorna a sequência original.

V2

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

#!/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
    
por 07.10.2016 / 00:20
1

com zsh :

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

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

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

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

(N) é um qualificador glob que ativa nullglob para cada um desses globs resultantes dessa expansão $^array1/$^array2 . Isso faz com que os globs se expandam para nada quando não combinam. Isso também acontece de transformar um não-glob como ~/code/private/foo/Thumbs.db em um, o que significa que, se esse particular não existir, ele não será incluído.

    
por 07.10.2016 / 07:34