Por que nullglob não é o padrão?

61

Na maioria das conchas nullglobnão é o padrão. Isso significa, por exemplo, se você executar este comando

ls *

em um diretório vazio, ele expandirá o *glob para um literal *, em vez de uma lista vazia de argumentos. Existem maneiras de mudar esse comportamento, para que *em um diretório vazio retorne uma lista vazia de argumentos, o que pareceria mais intuitivo.

Portanto, existe um motivo para nullglobestar desativado por padrão? Se sim, qual é esse motivo?

Dakkaron
fonte
13
Alguém já fez uma má escolha que se transformou em "sempre fizemos dessa maneira". Um fenômeno muito onipresente (não apenas) no mundo do software.
PSkocik 21/05
4
O motivo original é que a opção nullglob não existia na época. Portanto, para manter a compatibilidade com versões anteriores, ele deve ser desativado por padrão.
PM 2Ring
3
Embora não mencione especificamente o glob nulo, é fornecido algum histórico sobre globbing por esta resposta: unix.stackexchange.com/a/136409/22724
StrongBad 21/15/15
4
Tenho a impressão de que alguns acham que deve ser óbvio que o nullglob deve ser ativado por padrão. Eu não acho óbvio. Que as expansões ocorram no shell antes da chamada do comando significa que expandir para nada é um comportamento menos intuitivo do que o globo que permanece inalterado.
Kojiro 21/05
2
@kojiro Menos intuitivo para quem? Qualquer pessoa familiarizada com os shells do * NIX sabe que *é um glob e se expande para todos os arquivos existentes ; como é "intuitivo" haver um caso especial em que os globs de diretório vazios são "expandidos" para um literal *?
Kyle Strand

Respostas:

78

A nullglobopção (que BTW é uma zshinvenção, adicionada apenas anos depois a bash( 2.0)) não seria ideal em vários casos. E lsé um bom exemplo:

ls *.txt

Ou seu equivalente mais correto:

ls -- *.txt

Com nullglobon seria executado lssem argumento que é tratado como ls -- .(liste o diretório atual) se nenhum arquivo corresponder, o que provavelmente é pior do que chamar lscom um literal *.txtcomo argumento.

Você teria problemas semelhantes com a maioria dos utilitários de texto:

grep foo *.txt

Procuraria foono stdin se não houver txtarquivo.

Um padrão mais sensato, e o de csh, tcsh, zsh ou fish 2.3+ (e dos shells iniciais do Unix) é cancelar o comando completamente se o glob não corresponder.

bash(desde a versão 3) tem uma failglobopção para isso (interessante para esta discussão, pois ao contrário da ashAT&T kshou zsh, bashnão suporta escopos locais para opções (embora isso mude no 4.4), essa opção, quando ativada globalmente, quebra algumas coisas como as funções de conclusão do bash).

Observe que csh e tcsh são um pouco diferentes ou zsh, em casos como:fishbash -O failglob

ls -- *.txt *.html

Onde você precisa que todos os globs não correspondam para que o comando seja cancelado. Por exemplo, se houver um arquivo txt e nenhum arquivo html, isso se tornará:

ls -- file.txt

Você pode obter esse comportamento com zshcom setopt cshnullglobembora uma forma mais sensata de fazê-lo em zshseria a utilização de um glob como:

ls -- *.(txt|html)

No zshe ksh93, você também pode aplicar o nullglob por glob, o que é uma abordagem muito mais saudável do que modificar uma configuração global:

files=(*.txt(N))  # zsh
files=(~(N)*.txt) # ksh93

criaria uma matriz vazia se não houver txtarquivo em vez de falhar no comando com um erro (ou torná-la uma matriz com um *.txtargumento literal com outras conchas).

As versões fishanteriores à 2.3 funcionariam como bash -O nullglobum aviso quando interativas quando uma glob não tem correspondência. Desde a versão 2.3, funciona como, zshexceto os globs usados ​​em for, setou count.

Agora, na nota da história, o comportamento foi realmente quebrado pelo shell Bourne. Nas versões anteriores do Unix, o globbing era feito através do /etc/globauxiliar e ele se comportava da seguinte maneira csh: falharia no comando se nenhum dos globs correspondesse a qualquer arquivo e os removesse sem nenhuma correspondência.

Portanto, a situação em que estamos hoje se deve a uma má decisão tomada no shell Bourne.

Observe que o shell Bourne (e o shell C) veio com outro novo recurso do Unix: o ambiente. Essa expansão variável significava (é predecessor só tinha os $1, $2... parâmetros posicionais). O shell Bourne também introduziu a substituição de comandos.

Outra má decisão de design do shell Bourne foi realizar globbing (e divisão) após a expansão de variáveis ​​e substituição de comandos (possivelmente para compatibilidade com versões anteriores com o shell Thompson, onde echo $1ainda invocaria /etc/globse houvesse $1curingas (era mais como expansão macro de pré-processador) lá, como no valor expandido, foi analisado novamente como código shell)).

Globs com falha que não correspondem significam, por exemplo, que:

pattern='a.*b'
grep $pattern file

falharia no comando (a menos que haja alguns a.whateverbarquivos no diretório atual). csh(que também executa globbing na expansão de variáveis) falha no comando nesse caso (e eu diria que é melhor do que deixar um bug inativo lá, mesmo que não seja tão bom quanto não fazer globbing como em zsh).

Stéphane Chazelas
fonte
Há outro problema de usabilidade: nullglobparece interromper o preenchimento da guia (pressionar a tecla tab não faz nada quando está ativado).
Kry Strand
11
É possível usar nullglob em um per glob base com o bash - embora a sintaxe não é tão elegante como zshfiles=$(shopt -s nullglob;echo *.txt)
Jon Nalley
2
@ JonNalley, que armazena a concatenação (com espaço) dos nomes dos arquivos (com possível transformação com xpg_echo) em variáveis escalares . Você precisa de algo como readarray -td '' files < <(shopt -s nullglob; printf '%s\0' *.txt)com bash4.4 ou acima ou (shopt -s nullglob; printf '%s\0' *.txt) | xargs -r0 cmdcom GNU xargspara que isso seja útil a todos com nomes de arquivos arbitrários. Ou, ainda com o bash4.4, use uma função auxiliar que use local -(copiada do ash 25 anos depois) para um escopo local de opções.
Stéphane Chazelas