A expansão com * .txt no shell não funciona se não existir um arquivo .txt

10

Eu estava brincando com a expansão e notei um comportamento peculiar. Eu tentei fazer:

echo ./*.txt

E eu não tinha nenhum arquivo .txt no meu diretório atual. A saída que obtive foi:

./*.txt

Só estou curioso: por que eu consegui isso? Eu esperava não obter nenhuma saída.

PS: Quando eu tinha um .txtarquivo, a expansão foi corretamente interpretada. Em outras palavras, digamos que eu tinha um arquivo smthn.txt, o eco realmente ecoou current_directory/smthn.txt.

Um Andarilho Ignorante
fonte

Respostas:

15

Adaptando a partir da página de manual do bash shell,

bash varre cada palavra para os caracteres *,? e [. Se um desses caracteres aparecer, a palavra será considerada um padrão e substituída por uma lista de nomes de arquivos classificados alfabeticamente que correspondem ao padrão. Se nenhum nome de arquivo correspondente for encontrado e a opção de shell nullglob não estiver ativada, a palavra permanecerá inalterada. Se a opção nullglob estiver configurada e nenhuma correspondência for encontrada, a palavra será removida.

Nesse caso, presumo que o nullglob não está ativado, portanto a palavra permanece inalterada - daí a saída que você vê.

ColinB
fonte
2
Você pode alterar o comportamento da seguinte maneira: shopt -s nullglobproduzirá cadeias vazias para padrões sem correspondência e shopt -u nullglob(configuração padrão) produzirá o próprio padrão.
PerlDuck 26/07/19
13

Eu esperava não obter nenhuma saída.

Se nullglobfosse o padrão, muitos comandos se comportariam inesperadamente, porque é (talvez infelizmente) comum que os comandos tratem o caso de zero argumentos de nome de arquivo de uma maneira qualitativamente diferente do que o caso de um ou mais argumentos de nome de arquivo.

Suponha que você tenha ativado nullglob( shopt -s nullglob) e esteja em um diretório onde nenhum arquivo corresponde *.txt. Então, *.txtele realmente se expandirá para nada - não um campo vazio, mas nenhum campo - como você esperava. Mas isso teria esses resultados:

  • ls *.txtlistaria todos os arquivos no diretório atual (exceto arquivos ocultos), porque é o que lsacontece quando você não passa nenhum argumento de nome de arquivo.
  • cat *.txtleria da entrada padrão , porque quando catnão tem argumentos de nome de arquivo, é como se você tivesse executado cat -. Se estiver executando interativamente, fica aguardando a entrada. Muitos comandos se comportam dessa maneira.
  • cp *.txt dest/falharia com o erro cp: missing destination file operand after 'dest/'. Isso não é um desastre, mas é confuso e bem diferente do sucesso silencioso que provavelmente é desejado.
  • file *.txt, e vários outros programas sem comportamento especial para o caso de zero argumentos de nome de arquivo, ainda falhariam com uma mensagem de erro ou uso quando nenhuma delas fosse passada.
  • Mesmo casos que, intuitivamente, parecem que deveriam funcionar, normalmente não funcionariam. printf 'Got file: "%s"\n' *.txtimprimiria em Got file: ""vez de nada.
  • Falhas acidentais em citar ocorrências de *, ?e [que não pretendem ser expandidas pelo shell produziriam com mais frequência resultados obviamente errados, mas de maneiras que podem ser difíceis de descobrir. Por exemplo, se nenhum nome de arquivo no diretório atual fosse iniciado gedit, então apt list gedit*(onde apt list 'gedit*'pretendido) se tornaria justo apt liste listaria todos os pacotes disponíveis.

Portanto, é bom que você não tenha esse comportamento sem solicitá-lo. Provavelmente a situação prática mais comum que é realmente simplificada nullglobé for f in *.txt. Veja também esta pergunta (à qual a resposta de Sergiy Kolodyazhnyy está vinculada).

A pergunta mais difícil de responder é por que - failglobonde é um erro de expansão ter um globo que não corresponde a nenhum arquivo - não é o padrão no bash. Acredito que a resposta de Sergiy Kolodyazhnyy capte a razão disso, mesmo sem abordá-la diretamente. Reter globs não expansíveis sem produzir um erro de expansão é (talvez infelizmente) o comportamento padronizado, e também é um comportamento tradicional e, portanto, esperado. Embora o bash não tente ser totalmente compatível com POSIX, a menos que seja chamado com o nome shou tenha passado a --posixopção, muitas de suas opções de design, mesmo quando não estão no modo POSIX, seguem o POSIX diretamente. Eles tiveram que escolher algum comportamento, e há desvantagens associadas a ir contra as expectativas dos usuários.


Acho que esse é o aspecto menos influente do ponto de vista histórico, então guardei por último ... mas vale a pena mencionar que há algo um pouco conceitualmente estranho no nullglobcomportamento.

nullglobparece elegante a princípio porque, sintaticamente , trata o caso de zero arquivos correspondentes de maneira diferente do caso de um, dois ou qualquer outro número. Os comandos que executamos, nos quais os globs se expandem para argumentos, não tendem a tratá-los da mesma forma, conforme detalhado acima. Mas sintaticamente isso pelo menos parece certo, o que eu acho que é a motivação para sua pergunta.

E, no entanto, há outra inconsistência mais sutil que nullglobnão trata - que ela realmente amplifica. O caso de zero caracteres " globbing " ("curingas") é tratado profundamente diferente do de um, dois ou qualquer outro número. Por exemplo, com shopt -s nullglob, se ab?d?fnão corresponder a nenhum arquivo, ele será removido; se ab?dnão corresponder a nenhum arquivo, ele será removido; mas se abnão corresponder a nenhum arquivo (ou seja, se não houver um arquivo cujo nome seja exatamente ab), ele ainda não será removido. Obviamente, seria um desastre se fosse removido, porque pode não ter a intenção de se referir a um arquivo existente no diretório atual; pode nem se referir a um arquivo. Mas isso ainda elimina qualquer esperança de consistência total.

Os três comportamentos que o bash fornece - o padrão de tratar globs que não correspondem a nenhum arquivo como se eles não fossem globs e transmiti-los sem expansão, o comportamento que você esperava de tratá-los (se você perdoa essa estranha frase) como significando todo o zero dos arquivos que correspondem ( nullglob) e o comportamento seguro de considerá-los erros ( failglob) - todos representam abordagens diferentes para a ambiguidade inerente ao shell, não sendo possível saber se alguma palavra em particular deve ser uma nome do arquivo. O shell executa suas expansões sem o conhecimento de como os comandos específicos que você chama com ele tratarão seus argumentos.

Esse é um dos muitos casos de separação de preocupações . Em sistemas cujo design segue a filosofia do Unix, cada parte tem a intenção de fazer uma coisa e fazê-la bem . O shell processa o texto em comandos e argumentos e chama esses comandos, a maioria dos quais são externos ao próprio shell. Este tende a ser um agradável muito e mais versátil do que os sistemas onde os comandos externos são eles próprios responsáveis por executar essas transformações (como acontece com os processadores de comando tradicionais em DOS e Windows). Mas tem suas desvantagens ocasionais.

Eliah Kagan
fonte
Aparentemente, o bash-4.3.39 (2) não possui failglob. Portanto, não pode ser o padrão porque nem sempre foi suportado.
Ruslan
6

A principal razão é porque este é o comportamento padrão especificado pelo POSIX - o padrão que cobre desembolsar linguagem de comando e entre outras coisas padrão correspondentes (conchas tais como bash, dashshell - padrão do Ubuntu /bin/sh, e kshseguem esse padrão). Na seção 2.13.3 Padrões usados ​​para expansão de nome de arquivo :

Se o padrão não corresponder a nenhum nome de arquivo ou caminho existente, a sequência do padrão será mantida inalterada.

Obviamente, isso tem um efeito colateral - que corresponde ao nome do arquivo que pode ser literalmente *.txt. A nullglobopção bashe zshpode ajudar: se essa opção estiver ativada via shopt -s nullglob(e não estiver ativada por padrão, o que se aplica a esta pergunta), o globstar será expandido para uma string vazia quando nenhum nome de arquivo correspondente for encontrado. ksh93possui seu próprio mecanismo avançado de correspondência de padrões, que obtém o mesmo efeito~(N)*.txt

Consulte também Por que o nullglob não é o padrão?

Sergiy Kolodyazhnyy
fonte