Percorre todos os arquivos com uma extensão específica

109
for i in $(ls);do
    if [ $i = '*.java' ];then
        echo "I do something with the file $i"
    fi
done

Quero fazer um loop em cada arquivo na pasta atual e verificar se ele corresponde a uma extensão específica. O código acima não funciona, sabe por quê?

AR89
fonte
4
Sobre o quê for i in $(ls *.java); do echo "do something with file $i"; done?
speakr de
não há como consertar essa instrução if?
AR89 de
1
Você está comparando $icom a string literal "* .java"; a expansão do padrão não é executada aqui.
chepner
Para corrigir a instrução if como você a possui, use if [[ $i == *.java ]]; then.. (observe os double [[]] se não citados * .java).
aquele outro cara de
2
Não analisels - aceite a resposta de @chepner
glenn jackman

Respostas:

201

Não são necessários truques sofisticados:

for i in *.java; do
    [ -f "$i" ] || break
    ...
done

O guarda garante que, se não houver arquivos correspondentes, o loop será encerrado sem tentar processar um nome de arquivo inexistente *.java. Em bash(ou shells que suportam algo semelhante), você pode usar a nullglobopção de simplesmente ignorar uma correspondência com falha e não entrar no corpo do loop.

shopt -s nullglob
for i in *.java; do
    ...
done
chepner
fonte
5
A maneira mais simples é adicionar outro padrão: for i in *.java *.cpp; do. Se você tiver padrões estendidos habilitados bashcom shopt -s extglob, você pode escrever for i in *.@(java|cpp); do.
chepner de
8
Será, se realmente corresponder a qualquer arquivo. Você precisa usar shopt -s nullglobpara que um padrão não correspondente se expanda para a sequência vazia em vez de ser tratado literalmente.
chepner 01 de
1
@zygimantus sim, deveria, desde que o diretório atual seja de onde o script está sendo executado. Porém, se você não estiver no diretório que deseja, cdfor
1
@puk Depende das opções do seu shell. Por padrão, um padrão não correspondente é tratado como uma string literal. Você pode definir nullglobpara que seja tratado como uma sequência vazia ou failglobpara que seja tratado como um erro. (Se você definir ambos, failglobtem precedência.)
chepner
3
@chepner Pode ser útil para não especialistas indicar isso na resposta, pois alguém pode copiar e colar e descobrir que não está funcionando. Um exemplo perfeito seria alguém que está convertendo todos os arquivos ".jpg" em " .png" antes de realizar uma função crucial
puk
13

a resposta correta é @chepner's

EXT=java
for i in *.${EXT}; do
    ...
done

no entanto, aqui está um pequeno truque para verificar se um nome de arquivo tem uma determinada extensão:

EXT=java
for i in *; do
    if [ "${i}" != "${i%.${EXT}}" ];then
        echo "I do something with the file $i"
    fi
done
umläute
fonte
como seria com uma variável em extvez de .java?
AR89 de
13

Adicione subpastas recursivamente,

for i in `find . -name "*.java" -type f`; do
    echo "$i"
done
luismartingil
fonte
em vez de find . -name "*.java" -type f -exec echo \{\} \; evitar a análise incorreta da saída defind
umläute
1
E se você não fizer isso, pelo menos você deve citar "$i"dentro do loop.
tripleee
2
Isso não funcionará se algum dos arquivos tiver um espaço em branco no nome.
codeforester
1
@codeforester Eu tive exatamente esse problema e o corrigi usando o método desta resposta: askubuntu.com/a/343753/551184
fsinisi90
7

Loop através de todos os arquivos que terminam com: .img, .bin, .txtsufixo, e imprimir o nome do arquivo:

for i in *.img *.bin *.txt;
do
  echo "$i"
done

Ou de forma recursiva (encontre também em todos os subdiretórios):

for i in `find . -type f -name "*.img" -o -name "*.bin" -o -name "*.txt"`;
do
  echo "$i"
done
Benny
fonte
3

Concordo com as outras respostas sobre a maneira correta de percorrer os arquivos. No entanto, o OP perguntou:

O código acima não funciona, sabe por quê?

Sim!

Um excelente artigo Qual é a diferença entre teste, [e [[?] Explica em detalhes que, entre outras diferenças, você não pode usar expression matchingou pattern matchingdentro do testcomando (que é uma abreviação de [)

Novo teste de recurso [[teste antigo [Exemplo

Correspondência de padrões = (ou ==) (não disponível) [[$ name = a *]] || echo "nome não começa com um 'a': $ nome"

Expressão regular = ~ (não disponível) [[$ (data) = ~ ^ Sex \ ... \ 13]] && echo "É sexta-feira, dia 13!"
Coincidindo

Portanto, esta é a razão da falha do seu script. Se o OP estiver interessado em uma resposta com a [[sintaxe (que tem a desvantagem de não ser suportada em tantas plataformas quanto o [comando), ficaria feliz em editar minha resposta para incluí-la.

EDITAR: Quaisquer procedimentos sobre como formatar os dados na resposta como uma tabela seriam úteis!

user000001
fonte
2

como @chepner diz em seu comentário, você está comparando $ i a uma string fixa.

Para expandir e retificar a situação, você deve usar [[]] com o operador regex = ~

por exemplo:

for i in $(ls);do
    if [[ $i =~ .*\.java$ ]];then
        echo "I want to do something with the file $i"
    fi
done

a regex à direita de = ~ é testada em relação ao valor do operador do lado esquerdo e não deve ser colocada entre aspas (aspas não causam erro, mas são comparadas com uma string fixa e, portanto, provavelmente falharão "

mas a resposta de @chepner acima usando glob é um mecanismo muito mais eficiente.

peteches
fonte
como seria com uma variável em extvez de .java?
AR89 de
1
ack, sem necessidade de uma expressão regular: if [[ $i == *.java ]]ou if [[ $i == *.$ext ]]. Mas não analise ls
glenn jackman
1

Achei essa solução bastante útil. Ele usa a -oropção em find:

find . -name \*.tex -or -name "*.png" -or -name "*.pdf"

Ele vai encontrar os arquivos com a extensão tex, pnge pdf.

f0nzie
fonte