Como verificar se uma glob tem uma expansão? [duplicado]

6

Existe uma maneira convencional boa e limpa de fazer isso?

Por exemplo, se houver arquivos ".gz" em um determinado diretório, quero descompactá-los. Mas se não houver, não quero ver nenhum erro.

Se eu usar gzip -d /mydir/*.gz , recebo o erro:

gzip: /mydir/*.gz: No such file or directory

Se eu primeiro shopt -s nullglob e, em seguida, gzip -d /mydir/*.gz , obtenho o seguinte:

gzip: compressed data not read from a terminal. Use -f to force decompression.
For help, type: gzip -h

Eu tenho um método que sei que funciona, que estou postando como resposta. Eu estou querendo saber se há uma maneira melhor / mais limpa.

Compatibilidade POSIX é um bônus, mas não é obrigatório.

    
por Wildcard 26.07.2016 / 08:05

3 respostas

8

com bash :

shopt -s nullglob
files=(/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"

com zsh :

files=(/mydir/*.gz(N))
(($#files == 0)) || gzip -d -- $files

Observe que em zsh , sem (N) , como em shells pré-Bourne, csh ou tcsh, se o glob não corresponder, o comando não é executado, você só faria o acima para evitar mensagem de erro resultante (de nenhuma correspondência encontrada em oposição a gzip com falha no glob expandido no caso de bash ou outros shells semelhantes a Bourne). Você pode obter o mesmo resultado com bash com shopt -s failglob .

Em zsh , uma falha na glob é um erro fatal que faz com que o shell (quando não interativo) saia. Você pode impedir que o script seja encerrado nesse caso usando um subshell ou usando o mecanismo de captura de erros zsh ( { try-block; } always { error-catching; } ), (ou definindo o nonomatch (para funcionar como sh ), nullglob ou noglob opção é claro, embora eu não recomende isso):

$ zsh -c 'echo zz*; echo not output'
zsh:1: no matches found: zz*
$ zsh -c '(echo zz*); echo output'
zsh:1: no matches found: zz*
output
$ zsh -c '{echo zz*;} always {TRY_BLOCK_ERROR=0;}; echo output'
zsh:1: no matches found: zz*
output
$ zsh -o nonomatch -c 'echo zz*; echo output'
zz*
output

Com ksh93

ksh93 acabou por adicionar um mecanismo semelhante ao qualificador zsh ' (N) glob para evitar a necessidade de definir uma opção nullglob globalmente:

files=(~(N)/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"

POSIXly

Portável em POSIX sh , onde globs não correspondentes são passados unexpanded sem nenhuma maneira de desabilitar esse comportamento (a única opção relacionada com POSIX glob é noglob para desativar globbing), o truque é fazer algo como:

set -- /mydir/[*].gz /mydir/*.gz
case $#$1$2 in
   '2/mydir/[*].gz/mydir/*.gz') : no match;;
   *) shift; gzip -d -- "$@"
esac

A idéia é que, se /mydir/*.gz não corresponder, ele se expandirá para si mesmo ( /mydir/*.gz ). No entanto, ele também poderia se expandir para que, se houvesse um arquivo realmente chamado /mydir/*.gz , para diferenciar os casos, também usaríamos o /mydir/[*].gz glob que também seria expandido para /mydir/*.gz se houvesse um arquivo chamado assim .

Como isso é muito estranho, você pode preferir usar find nesses casos:

find /mydir/. ! -name . -prune ! -name '.*' \
   -name '*.gz' -type f -exec gzip -d {} +

O ! -name . -prune é não procurar em subdiretórios (algumas find implementações têm -depth 1 ou -mindepth 1 -maxdepth 1 como equivalente). ! -name '.*' é excluir arquivos ocultos, como os globs.

Um benefício é que ainda funciona se a lista de arquivos for grande demais para caber no limite do tamanho dos argumentos para um comando executado ( find executará vários comandos gzip se precisar evitar isso, ksh93 e zsh também têm mecanismos para contornar isso).

Outro benefício é que você receberá mensagens de erro se find não puder ler o conteúdo de /mydir ou não puder determinar o tipo dos arquivos (os globs ignorariam silenciosamente o problema e agiriam como se os arquivos correspondentes não existe).

Um pequeno ponto negativo é que você perde o valor exato do status de saída gzip (se qualquer uma gzip invocação falhar com um status de saída diferente de zero, find ainda sairá com um valor diferente de zero status de saída (embora não necessariamente o mesmo), por isso é bom o suficiente para a maioria dos casos de uso).

Outro benefício é que você pode adicionar o -type f para evitar a descompactação de diretórios ou fifos / devices / sockets ... cujo nome termina em .gz . Exceto em zsh ( *.gz(.) apenas para arquivos regulares), globs não pode filtrar por tipos de arquivo, você precisa fazer coisas como:

set --
for f in /mydir/*.gz
  [ -f "$f" ] && [ ! -L "$f" ] && set -- "$@" "$f"
done
[ "$#" -eq 0 ] || gzip -d -- "$@"
    
por 26.07.2016 / 08:11
3

Uma maneira de fazer isso é:

shopt -s nullglob
for f in /mydir/*.gz; do
  gzip -d /mydir/*.gz
  break
done

O loop for com nullglob ativado só executará o loop se o glob tiver uma expansão, e a instrução break incondicional garantirá que o comando gzip seja executado somente uma vez.

É um pouco engraçado porque usa um for loop como if , mas funciona.

    
por 26.07.2016 / 08:05
1

A solução mais simples seria

for f in *.gz; do
  test -f "$f" && gzip -d -- "$f"
done

ou

for f in *.gz; do
  [ -f "$f" ] && gzip -d -- "$f"
done

ou até mesmo

for f in *.gz; do
  if [ -f "$f" ]; then
    gzip -d -- "$f"
  fi
done

Eu sou todo por verbosidade se ajuda a explicar o que o código faz. Os vários shells têm uma sintaxe compacta para fazer todo tipo de coisas, mas não supera o acima quando se trata de documentar o que você está fazendo (e portabilidade). Estou pensando principalmente em scripts de shell aqui e em situações em que seus scripts podem ser usados e modificados por outros.

EDITAR:

Se você quiser apenas ver se o glob tem uma expansão:

if [ "$(printf '%s' *.gz)" = "*.gz" ] && [ ! -f "*.gz" ]; then
    # From Stéphane Chazelas: "There are no gzipped files that I
    # can tell, or if there's one, it's called *.gz and it is not a
    # regular file (after symlink resolution) or at least I can't tell
    # if it's a regular file or not."

    echo "There are no gzipped files"
else
    echo "There are gzipped files"
fi

Veja também os comentários de Stéphane abaixo sobre nomes de arquivos exóticos.

    
por 26.07.2016 / 09:01