A expansão zsh funciona de forma diferente em scripts não interativos?

3

Atualmente, estou trabalhando em um script zsh bem simples. O que eu costumo fazer é algo como:

mv */*.{a,b} .

Quando executo isso em um script zsh, ele parece expandir de maneira diferente e falhar enquanto funciona no modo interativo.

% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% mv */*.{a,b} .
% ls file.a
file.a

Então, isso funciona, mas como um script:

% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% cat script.sh
#!/usr/bin/zsh
mv */*.{a,b} .
% ./script.sh
./script.sh:2: no matches found: */*.b

Então, o que é diferente? O que estou fazendo errado?

    
por jhr 28.04.2015 / 16:25

2 respostas

7

Ambos estão errados com as configurações da opção zsh default. Você pode ver facilmente o que está acontecendo usando echo como o comando em vez de mv .

Interativamente, parece que você tem a opção null_glob definida. De acordo com a documentação zsh , essa opção não é configurada por padrão. O que acontece com essa opção unset depende se outra opção, nomatch , está definida ou não definida. Com nomatch unset ( nonomatch ) você obteria isso:

% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% echo */*.{a,b} .
dir/file.a */*.b .

A expansão acontece em 2 etapas. Primeiro, */*.{a,b} é expandido para 2 palavras: */*.a e */*.b . Então cada palavra é expandida como um padrão glob. O primeiro se expande para dir/file.a e o segundo se expande para si mesmo porque não corresponde a nada. Tudo isso significa que, se você usar mv e não echo , mv deverá tentar mover 2 arquivos: dir/file.a (fino) e */*.b (nenhum arquivo). Isso é o que acontece por padrão na maioria dos shells, como sh e ksg e bash .

As configurações da opção zsh defaults são de que null_glob não está definido e nomatch está definido. Os scripts são executados com as configurações de opção padrão (a menos que você as altere em ~/.zshenv ou /etc/zshenv , o que você não deveria). Isso significa que, em scripts, você consegue isso:

% mkdir dir
% touch dir/file.a
% ls file.a
ls: cannot access file.a: No such file or directory
% cat script.sh
#!/usr/bin/zsh
echo */*.{a,b} .
% ./script.sh
./script.sh:2: no matches found: */*.b

Como */*.b não corresponde a nada, você recebe um erro devido a nomatch .

Se você inserir setopt nonomatch no script antes do comando echo / mv , retornará ao comportamento incorreto como descrito acima: ele tenta mover um arquivo que não existe.

Se você inserir setopt null_glob no script antes do comando echo / mv , obterá o comportamento obtido em seu shell interativo, que é o que funciona.

    
por 28.04.2015 / 16:36
2

Quando você executa o zsh interativamente, ele lê o seu ~/.zshrc (e também o sistema /etc/zshrc , mas se o seu administrador não é desobediente e não é o culpado). É provável que você defina algumas opções lá que modificam como o zsh expande os comandos , em particular extended_glob (que deve ser o padrão, se o zsh não escolheu ser retrocompatível com o início dos anos 90, quando esta opção não existe). Essas opções não serão definidas quando você executar um script.

No seu caso, o que está acontecendo é que o comando mv */*.{a,b} . é primeiro analisado em uma lista de palavras mv , */*.a , */*.b , . (a menos que você desative a expansão de chaves). Em seguida, cada palavra que contém um caractere curinga é tratada como um padrão glob. Por padrão, em zsh, se um padrão glob não corresponder a nenhum arquivo, o comando não será executado e zsh sinalizará um erro. Evidentemente, no seu .zshrc , você ativou a opção null_glob , o que faz com que o padrão não compatível seja expandido para uma lista vazia de palavras (ou seja, é removido). Quando você executa seu script, o segundo padrão */*.b não corresponde a nenhum arquivo, o que aciona o erro que você vê. Quando você executa o comando interativamente, esse padrão é removido.

Você pode ativar a opção null_glob explicitamente em seu script com setopt null_glob . Você também pode ativá-lo para um padrão específico com o N qualificador glob :

mv */*.{a,b}(N) .

Em vez de usar a expansão de chaves aqui, você deve usar o ou operador , que está disponível em zsh (ao contrário de plain sh), porque é exatamente isso que você quer dizer - não dois padrões, mas arquivos que correspondem a qualquer padrão.

mv */*.(a|b) .

Dessa forma, há um único padrão, e você só receberá um erro se nenhum arquivo corresponder a esse padrão único.

Se nenhum arquivo corresponder, este comando resultará em um erro. Você não pode simplesmente silenciá-lo com (N) porque isso faria com que o comando inválido mv . fosse executado. Em vez disso, primeiro teste se houver correspondências e execute mv apenas se houver.

files=(*.(a|b)(N))
if ((#files)); then mv -- $files[@] .; fi
    
por 29.04.2015 / 01:27