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
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 usareval
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 chamadaeval
em que está pedindo problemas). Você ‘ d deseja que o globbing seja ativado parafor i in $dir
efor l in $k
, mas não parafor j in $exclude
. Você ‘ d deseja umset -f
antes do último e umset +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 paraecho $l
, então$l
deve ser citado lá. - @St é phaneChazelas você está se referindo a v1 ou v2? Para v2,
exclude
edirs
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=*
efoo='*'
é o mesmo. Masecho $foo
eecho "$foo"
não são (em shells comobash
, ‘ 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
k
é uma string calculada, e eu preciso dela sta y dessa forma até o loop. Verifique minha versão longa.