Como faço para executar uma ação em todos os arquivos com uma extensão específica em subpastas de maneira elegante?

17

Minha melhor aposta atual é:

for i in $(find . -name *.jpg); do echo $i; done

Problema: não manipula espaços nos nomes de arquivos.

Nota: Eu também adoraria uma maneira gráfica de fazer isso, como o comando "tree".

Vorac
fonte
Você não pode simplesmente colocar aspas $i? Eu faço isso e funciona bem. Há algo de errado com essa abordagem?
Umar Farooq Khawaja

Respostas:

26

A maneira canônica é fazer

find . -name '*.jpg' -exec echo {} \;

(substitua \;por +para passar mais de um arquivo echopor vez)

ou (específico do GNU, embora alguns BSDs agora também o tenham):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i
janeiro
fonte
2
Se você estiver usando xargs -0 , você deve combiná-lo com achado -0
itsbruce
1
@ MichaelKjörling, nenhuma citação {} não faz diferença. {} é expandido por localização, não pelo shell. Uma melhoria seria não usar o echoque expande seqüências de escape como \b(pelo menos os conformes do Unix echo).
Stéphane Chazelas
1
@sch Você tem certeza? A página de findmanual é exibida find . -type f -exec file '{}' \;na EXAMPLESseção. Talvez algumas conchas tratem o aparelho especialmente.
QuasarDonkey 9/10/12
1
As cotações não prejudicam, mas não fazem diferença. Todos '{}', \{\}, {}, "{}", '{'}são expandidos pela shell (o que quer Bourne-como shell) para um argumento para descobrir que é os dois personagens "{" e "}". Substitua "find" por "echo find" ou printf '<%s>\n' findse desejar verificar novamente .
Stéphane Chazelas
@QuasarDonkey Veja unix.stackexchange.com/questions/8647/…
Gilles 'SO- stop
2

Melhores respostas já foram dadas.

Mas observe que os espaços não são o único problema no código que você forneceu. guia, caracteres de nova linha e curinga também são um problema.

Com

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Somente caracteres de nova linha são um problema.

Stéphane Chazelas
fonte
1

O que você mencionou é um dos problemas básicos que as pessoas enfrentam quando tentam ler os nomes dos arquivos. Às vezes, pessoas com conhecimento limitado e com uma concepção equivocada da estrutura de arquivos e pastas tendem a esquecer que " No UNIX, tudo é um arquivo ". Portanto, eles não entendem que precisam lidar com espaços também, pois o nome do arquivo pode consistir em 2 ou mais palavras com espaços.

Solução: Portanto, uma das maneiras mais conhecidas de fazer isso é fazer uma leitura limpa .

Aqui, leremos todos os arquivos presentes e os manteremos em uma variável. Da próxima vez que realizarmos o processamento desejado, teremos que manter essa variável entre aspas, o que preservará os nomes dos arquivos com espaços. Essa é uma das maneiras básicas de fazê-lo, no entanto, as outras respostas fornecidas pelas outras pessoas aqui também funcionam.

ROTEIRO :

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Aqui eu estou lendo os arquivos, fornecendo o caminho para eles e o que quer que esteja lendo, eu os mantenho dentro de uma variável I, que citarei posteriormente, enquanto o processo para preservar os espaços e processá-los corretamente.

Espero que isso ajude você de alguma forma.

O Cavaleiro das Trevas
fonte
1
"tudo é um arquivo" refere-se a outra coisa. Sua solução é específica para GUN e não lida com caracteres de barra invertida ou nova linha nos nomes de arquivos. Os loops "durante a leitura" nas conchas (IMO) geralmente são uma indicação de práticas inadequadas de script de shell.
Stéphane Chazelas
@ sch: hein, e eu costumava pensar, está tudo bem. Obrigado por apontar isso. Eu provavelmente preciso olhar mais.
O Cavaleiro das Trevas
desculpe, eu quis dizer "GNU", e não "GUN" acima (é a primeira vez que percebem esses são anagramas BTW ...)
Stéphane Chazelas
1

Simplesmente com finde bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

Dessa forma, usamos $1no bash, como em um script básico, que abre boas perspectivas para executar tarefas avançadas (ou não).

Uma maneira mais eficiente (para evitar a necessidade de iniciar um novo bash para cada arquivo):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +
Gilles Quenot
fonte
0

Na versão recente do bash, você pode usar a globstaropção:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Para ações simples, você pode até pular o loop completamente:

echo **/*.jpg
choroba
fonte
Embora isso funcione no zsh (de onde o recurso se originou) e no ksh93 (with set -G), ele não deve ser usado no bash, pois a versão do bash é fundamentalmente quebrada (assim como o GNU grep -r), pois desce para links simbólicos para diretórios (equivalentes aos do find -Lzsh ***/*.jpg) Observe também que, diferentemente da localização, ele omitirá arquivos de ponto e não descerá em pontos de ponto (por padrão).
Stéphane Chazelas