bash iterar lista de arquivos, exceto quando vazio

Achei que seria simples – mas está se revelando mais complexo do que eu esperava.

Quero iterar por 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 contanto 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 

Então tentei:

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

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

ls: *.zip: No such file or directory 

Como faço para escrever um loop que lida com nenhum arquivo correspondente?

Comentários

  • Adicione shopt -s nullglob antes de executar o loop for.
  • @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 FILES= ls * .zip ; for fname in "${FILES}"... mas funciona conforme o esperado com for fname in *.zip ; do....
  • Use for file in *.zip, não `ls ...`. A sugestão de @cuonglm ' s é que *.zip se expanda para nada quando o padrão não ' t corresponder a qualquer arquivo. ls sem argumentos lista o diretório atual.
  • Esta questão discute por que a análise da saída de ls geralmente deve ser evitado: Por que não analisa ls? ; veja também o link próximo ao topo da página para o artigo BashGuide ' s ParsingLs .
  • Possível duplicata de Por que meu script de shell bloqueia com espaços em branco ou outros caracteres especiais?

Resposta

Em bash, você pode definir a opção nullglob para que um padrão que não corresponde a nada “desaparece”, em vez de ser tratado como uma string literal:

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

No script de shell POSIX, basta verificar se fname existe (e ao mesmo tempo com [ -f ], verifique se é um arquivo regular (ou link simbólico para arquivo regular) e não outros tipos como diretório / fifo / dispositivo. .):

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 você deseja percorrer todos os arquivos (não ocultos) cujo nome termina em .zip independentemente de seu tipo.

Substitua *.zip por .*.zip .zip *.zip se você também quiser considerar os arquivos ocultos cujo nome termina em .zip.

Comentários

  • shopt -s nullglob não funcionou para eu no Ubuntu 17.04, mas [ -f "$fname" ] || continue funcionou bem.
  • @koppor Parece que você não ' realmente está usando bash.
  • +1 para uma solução POSIX.

Resposta

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ê mencionou a chamada 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 * 

Resposta

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 com export -f para que isso funcione. Agora find executa bash que executa sua função shell e permanece apenas no nível de dir atual.

Comentários

  • Que recorre através de subdiretórios, e eu quero invocar uma função bash (não script) para as correspondências.
  • @symcbean I ' editamos para limitar a um único diretório e manipular funções bash

Resposta

Em vez disso de:

FILES=`ls *.zip` 

Tente:

Desta forma, se falhar (o que faz no seu caso) ele irá grep 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 fazê-lo retornar “Não 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 

Desta forma, se o comando anterior foi bem-sucedido (saiu com um valor 0), ele imprimirá o arquivo atual, caso contrário, imprimirá” Não Arquivos encontrados “

Comentários

  • I thi nk, é uma má ideia adicionar mais um processo (grep) em vez de tentar corrigir o problema usando uma ferramenta melhor (find ) ou alterando a configuração relevante para a solução atual (com shopt -s nullglob)
  • De acordo com o comentário da OP ' na postagem original, o shopt -s nullglob não funciona. Eu tentei find enquanto verificava minha resposta e continuava falhando. Acho que por causa da coisa de exportação que Dani disse.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *