bash iterar lista de arquivos, exceto quando vazio

33

Eu pensei que isso seria simples - mas está se mostrando mais complexo do que eu esperava.

Eu quero percorrer todos os arquivos de um tipo específico em um diretório, então escrevo isto:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

Isso funciona desde que haja pelo menos um arquivo correspondente no diretório No entanto, se não houver arquivos correspondentes, recebo o seguinte:

current file is *.zip

Eu então tentei:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

Enquanto o corpo do loop não é executado quando não há arquivos, recebo um erro de ls:

ls: *.zip: No such file or directory

Como escrevo um loop que não lida corretamente com arquivos correspondentes?

symcbean
fonte
7
Adicione shopt -s nullglobantes de executar o loop for.
cuonglm
@cuolnglm: assustadoramente isso resulta em ls retornando o nome do script em execução em vez de uma lista vazia nesta caixa RHEL5 (bash 3.2.25) se eu fizer FILES=ls * .zip, ; for fname in "${FILES}"...mas funciona como esperado comfor fname in *.zip ; do....
symcbean
3
Use for file in *.zip, não `ls ...`. A sugestão do @ cuonglm é *.zipexpandir para nada quando o padrão não corresponder a nenhum arquivo. lssem argumentos lista o diretório atual.
Stéphane Chazelas
1
Esta pergunta discute por que a análise de saída lsgeralmente deve ser evitada: Por que não analisar ls? ; veja também o link no topo da página para o artigo ParsingLs do BashGuide .
precisa saber é o seguinte

Respostas:

34

Em bash, você pode definir a nullglobopção para que um padrão que corresponda a nada "desapareça", em vez de ser tratado como uma sequência literal:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

No shell script POSIX, você apenas verifica se fnameexiste (e ao mesmo tempo com [ -f ], verifique se é um arquivo regular (ou um link simbólico para um arquivo normal) e não outros tipos, como diretório / fifo / device ...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

Substitua [ -f "$fname" ]por [ -e "$fname" ] || [ -L "$fname ]se desejar fazer um loop sobre todos os arquivos (não ocultos) cujo nome termina .zipindependentemente do tipo.

Substitua *.zippor .*.zip .zip *.zipse você também deseja considerar arquivos ocultos cujo nome termina em .zip.

chepner
fonte
1
shopt -s nullglobnão funcionou para mim no Ubuntu 17.04, mas [ -f "$fname" ] || continuefuncionou bem.
koppor
@koppor Parece que você não está realmente usando bash.
chepner
2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

Em um comentário aqui, você menciona a invocação de uma função ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *
mikeserv
fonte
2

Use find

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

Você DEVE exportar sua função shell export -fpara que isso funcione. Agora findexecuta o bashque executa sua função shell e permanece apenas no nível de diretório atual.

Dani_l
fonte
Que se repete através de subdiretórios, e eu quero chamar uma função bash (não script) para as correspondências.
Symcbean # 30/15
@symcbean Eu editei a limite para funções dir única e festança alça
Dani_l
-3

Ao invés de:

FILES=`ls *.zip`

Experimentar:

FILES=`ls * | grep *.zip`

Dessa forma, se ls falhar (o que ocorre no seu caso), ele receberá a saída com falha e retornará como uma variável em branco.

current file is      <---Blank Here

Você pode adicionar alguma lógica a isso para fazer com que ele retorne "Nenhum arquivo encontrado"

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

Dessa forma, se o comando anterior tiver êxito (encerrado com um valor 0), ele imprimirá o arquivo atual; caso contrário, imprimirá "Nenhum arquivo encontrado"

Kip K
fonte
1
Eu acho que é uma má idéia adicionar mais um processo ( grep) em vez de tentar corrigir o problema usando uma ferramenta melhor ( find) ou alterar a configuração relevante para a solução atual (com shopt -s nullglob)
Thomas Baruchel
1
De acordo com o comentário do OP em sua postagem original, shopt -s nullglobisso não funciona. Tentei findverificar minha resposta e ela continuou falhando. Acho que por causa da coisa de exportação que Dani disse.
Kip K