Eu estava tentando escrever um pequeno script que escreveria todos os programas executáveis encontrados em $PATH
:
for dir in $(tr ':' ' ' <<<"${PATH}"); do
for pgm in $dir/*; do
if command -v "${pgm}" >/dev/null 2>&1; then
echo "${pgm}"
fi
done
done | sort >file
No bash, funciona como esperado, mas o zsh para de processar o script assim que uma geração de nome de arquivo falha no loop interno:
for pgm in $dir/*; do
^^^^^^
...
done
Como resultado, como my $PATH
contém um diretório que não contém nenhum arquivo ( /usr/local/sbin
), em zsh, o script falha ao gravar os executáveis encontrados nos diretórios posteriormente.
Aqui está outro código mostrando o mesmo problema:
for f in /not_a_dir/*; do
echo 'in the loop'
done
echo 'after the loop'
No bash, este comando gera:
in the loop
after the loop
E sai com o código 0
.
Enquanto em zsh, o mesmo comando gera:
no matches found: /not_a_dir/*
E sai com o código 1
.
A diferença de comportamento entre os shells parece vir da nomatch
opção, que é descrita em man zshoptions
:
NOMATCH (+3) <C> <Z>
Se um padrão para geração de nome de arquivo não tiver correspondências, imprima um erro, em vez de deixá-lo inalterado na lista de argumentos. Isso também se aplica à expansão de arquivo de um arquivo inicial
~
ou=
.
E também explicado em man zshexpn
(seção GERAÇÃO DE NOME DE ARQUIVO):
A palavra é substituída por uma lista de nomes de arquivos classificados que correspondem ao padrão. Se nenhum padrão correspondente for encontrado, o shell fornecerá uma mensagem de erro, a menos que a opção NULL_GLOB seja definida, caso em que a palavra será excluída; ou a menos que a opção NOMATCH não esteja definida, caso em que a palavra permanece inalterada.
Porque se eu unset nomatch
, zsh se comporta como bash:
unsetopt nomatch
for f in /not_a_dir/*; do
echo 'in the loop'
done
echo 'after the loop'
Agora eu entendo a diferença de comportamentos entre bash e zsh e por que o script gera um erro em zsh, mas quero entender por que uma geração de nome de arquivo com falha faz com que o zsh pare imediatamente de processar um script. Então, tentei reproduzir o mesmo problema substituindo a geração de nome de arquivo com falha por um comando com falha (executando not_a_cmd
):
for f in ~/*; do
not_a_cmd
done
echo 'after the loop'
Mas a saída deste script é quase idêntica em ambos os shells (exceto pelas mensagens de erro devido a not_a_cmd
). Em particular, ambos os shells imprimem:
after the loop
E ambos os shells saem com o código 0
.
Por que uma geração de nome de arquivo com falha (como for f in /not_a_dir/*
) faz com que o zsh pare de processar um script, mas não um comando com falha (como not_a_cmd
)?
estou usando zsh 5.6.2-dev-0 (x86_64-pc-linux-gnu)
.
É um recurso, não um bug. ?
Como diz no manual do Zsh, seção "Erros" :
Quanto ao raciocínio por trás disso, encontrei esta troca de e-mail nos arquivos da lista de discussão Zsh :