sintaxe sh para lidar com zero arquivos correspondentes a um curinga e muito mais?

sintaxe sh para lidar com zero arquivos correspondentes a um curinga e muito mais?

Quero escrever um /bin/shscript de shell que lide com qualquer arquivo que corresponda a um curinga. É fácil lidar com 1 ou mais arquivos correspondentes. No entanto, estou achando estranho lidar com o caso de 0 arquivos correspondentes.

A construção óbvia é:

#!/bin/sh
for f in *.ext; do
  handle "$f"
done

onde *.extpoderia ser uma ou mais expressões que o shell compara aos caminhos de arquivo e handleé um comando do shell que é executado corretamente se for fornecido um caminho para um arquivo existente, mas falha se for fornecido um caminho que não mapeia para um arquivo. (No caso que provoca esta pergunta, são *.flace ffmpeg, mas acho que isso não importa.)

Se houver arquivos correspondentes foo.ext, bar.extentão este script executa

handle "foo.ext"
handle "bar.ext"

como esperado. No entanto, se houvernãoquaisquer arquivos correspondentes, este script exibe uma mensagem de erro como,

handle: *.ext: No such file or directory

Acho que entendo por que isso acontece: ofestapágina de manual(que presumo ser válido /bin/shtambém) diz que a "lista de palavras a seguir iné expandida, gerando uma lista de itens.… Se a expansão dos itens a seguir resultar em uma lista vazia, nenhum comando será executado e o status de retorno será 0." Aparentemente, quando *.ext"está expandido", a lista de resultados inclui *.ext, em vez de uma lista vazia. Mas isso não explica como impedir que isso aconteça.

(Atualização: há umsh (1)página de manual do Projeto Heirloomque descreve o Bourne Shell sh, não o posterior Bourne Again Shell bash. SobGeração de nome de arquivo, diz claramente: "Se nenhum nome de arquivo que corresponda ao padrão for encontrado, a palavra permanecerá inalterada." Explica por que shdeixa um *.extpadrão na lista.)

Qual é uma maneira compacta e idiomática de escrever esse loop de modo que ele seja executado zero vezes sem erros se não houver arquivos correspondentes, mas seja executado uma vez para cada arquivo correspondente? Melhor ainda, o que funcionará com vários padrões como:

for f in *.ext1 special.ext1 *.ext2; do ...

Prefiro usar sintaxe compatível com /bin/sh, para portabilidade. Acontece que estou usando o Mac OS X 10.11.6, mas espero uma sintaxe que funcione em qualquer sistema operacional semelhante ao Unix.

Eu descobri algumas maneiras desajeitadas e não idiomáticas de escrever esse loop. Se eu não obtiver boas respostas imediatamente, contribuirei com elas como respostas, para registro.

Responder1

A maneira portátil mais simples de fazer isso é pular o loop se a expansão não produzir algo que realmente exista:

for f in *.ext1 *.ext2; do
  [ -e "$f" ] || continue
  handle "$f"
done

Explicação: suponha que existam vários arquivos .ext2, mas nenhum arquivo .ext1. Nesse caso, os curingas serão expandidos para *.ext1 filea.ext2 fileb.ext2 filec.ext2. Então [ -e "*.ext1" ]falha e é executado continue, o que pula o resto da iteração do loop. Então [ -e "filea.ext2" ], etc., é bem-sucedido e essas iterações do loop são executadas normalmente.

Aliás, você pode modificar isso para, por exemplo, [ -f "$f" ] || continuepular qualquer coisa que não seja um arquivo simples (se quiser pular diretórios, etc.).

Claro, se você estiver executando no bash, você pode simplesmente executar shopt -s nullglobprimeiro para não deixar padrões incomparáveis ​​na lista. E depois shopt -u nullglob, para evitar efeitos colaterais inesperados (por exemplo, grep pattern *.nonexistenttentarei ler o stdin se nullglobestiver definido).

Responder2

Mude para o Bourne Again Shell ( /bin/bash) do Bourne Shell ( /bin/sh) e uma solução simples se tornará possível.

Obash(1)página de manualmenciona onullglobopção:

Se definido, o bash permite padrões que não correspondem a nenhum arquivo (vejaExpansão do nome do caminhoacima) para expandir para uma string nula, em vez de eles próprios.

OExpansão do nome do caminhoseção diz:

Após a divisão das palavras,…bash verifica cada palavra em busca dos caracteres*,?, e[. Se um desses caracteres aparecer, a palavra será considerada um padrão e substituída por uma lista ordenada alfabeticamente de nomes de arquivos que correspondam ao padrão. Se nenhum nome de arquivo correspondente for encontrado e a opção shellnullglobnão está habilitado, a palavra permanece inalterada. Se onullglobopção for definida e nenhuma correspondência for encontrada, a palavra será removida.…

Então, definindo onullglobopção fornece o comportamento desejado para padrões. Se nenhum arquivo corresponder ao padrão, o loop não será executado.

#!/bin/bash
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done

Mas onullglobopção pode interferir no comportamento desejado para outros comandos. Então, você provavelmente achará sensato salvar a configuração existente denullglobe restaure-o posteriormente. Felizmente, o shopt -pcomando interno emite uma saída em um formato que pode ser reutilizado como entrada. Mas ele emite saída para todas as opções, então use greppara escolhernullobjcontexto. Veja o log abaixo (onde $indica o prompt de comando do bash):

$ shopt -p | grep nullglob
shopt -u nullglob
$ shopt -s nullglob
$ shopt -p | grep nullglob
shopt -s nullglob

Portanto, a sintaxe final do bash para lidar com zero ou mais arquivos que correspondam a um padrão curinga é semelhante a esta:

#!/bin/bash
SAVED_NULLGLOB=$(shopt -p | grep nullglob)
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done
eval "$SAVED_NULLGLOB"

Além disso, a pergunta mencionou vários padrões, como:

for f in *.ext1 special.ext1 *.ext2; do ...

OnullglobA opção não afeta uma palavra na lista que não seja um "padrão", portanto special.ext1ainda será passada para o loop. A única solução para isso é@Gordon Davissonexpressão continue, [ -e "$f" ] || continue.

Agradecimento: ambos@Ignacio Vázquez-Abramse@Gordon Davissonaludido bash(1)e seunullglobopção. Obrigado. Como eles não contribuíram imediatamente com uma resposta com um exemplo completo, eu mesmo estou fazendo isso.

informação relacionada