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
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 (comshopt -s nullglob
) - De acordo com o comentário da OP ' na postagem original, o
shopt -s nullglob
não funciona. Eu tenteifind
enquanto verificava minha resposta e continuava falhando. Acho que por causa da coisa de exportação que Dani disse.
shopt -s nullglob
antes de executar o loop for.FILES=
ls * .zip; for fname in "${FILES}"...
mas funciona conforme o esperado comfor fname in *.zip ; do....
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.ls
geralmente deve ser evitado: Por que não analisals
? ; veja também o link próximo ao topo da página para o artigo BashGuide ' s ParsingLs .