Repetidamente iterar através de todos os subdiretórios, Se existir um arquivo com uma extensão específica, execute um comando nessa pasta uma vez

0

Eu preciso recursivamente iterar por todos os subdiretórios de uma pasta. Nos subdiretórios, se houver um arquivo com uma extensão '.xyz', preciso executar um comando específico nessa pasta uma vez.

Aqui está o que eu tenho até agora

recursive() {
  for d in *; do
    if [ -d "$d" ]; then
      (cd -- "$d" && recursive)
    fi
  dir='pwd'   
  pattern="*.xyz"
file_count=$(find $dir -name $pattern | wc -l)
if [[ $file_count -gt 0 ]]; then
    echo "Match found. Going to execute a command"
    #execute command
fi
  done
}

(cd /target; recursive)

Mas o problema é que a mensagem "Encontrado encontrado ..." é exibida mais de uma vez por pasta quando há uma correspondência. Existe uma maneira mais simples de fazer isso ao corrigir esse problema?

    
por ishanipu 23.01.2018 / 03:16

2 respostas

2

find tem um sinalizador interno para imprimir strings, o que é bastante útil aqui:

find -iname "*.xyz" -printf "%h\n" imprime os nomes de todos os diretórios que contêm um arquivo que corresponde ao seu padrão (a sintaxe mágica %h é apenas find que se expande para o diretório de arquivos e \n é, é claro, um linebreak).

Portanto, isso faz o que você quer:

COMMAND='echo'
find 'pwd' -iname "*.pdf" -printf "%h\n" | sort -u | while read i; do                                              
    cd "$i" && pwd && $COMMAND
done

Existem algumas coisas que estão acontecendo aqui. Para executar os comandos apenas uma vez, apenas passamos por sort com o -u flag, o que descarta todas as entradas duplicadas. Então passamos por cima de tudo com while . Observe também que usei find 'pwd' , que é um truque interessante para tornar caminhos absolutos de find , em vez de relativos, o que nos permite usar cd sem precisar se preocupar com caminhos relativos.

Editar: Tenha cuidado com os nomes dos diretórios ao executar este script, pois nomes de diretórios contendo uma nova linha ( \n ) ou mesmo \ podem quebrar o script (talvez outros caracteres incomuns também, mas eu não testei mais do que isso). Corrigir isso é difícil e eu não sei como fazer isso, então eu só posso sugerir não usar esses diretórios.

    
por 23.01.2018 / 03:59
4

Você está reinventando find .

Tente algo assim (usando o GNU findutils e o GNU sort ):

find /target -iname '*.xyz' -printf '%h
find /target -iname '*.xyz' -printf '%h
#!/bin/sh

for d in "$@" ; do
  cd "$d"
  echo "Match found in $d. Going to execute command"
  # execute command
done
0' | sort -z -u | xargs -0 -r /path/to/myscript.sh
0' | sort -z -u | xargs -0 -r -I {} sh -c "cd {} ; yourcommandhere"

O -printf imprime os nomes de diretório ( %h ) onde os arquivos '* .xyz' são encontrados, com bytes NUL ( sort0 ) como delimitador. xargs é usado para eliminar duplicatas e, em seguida, cd é usado para yourcommandhere em cada diretório e executa printf .

Você também pode escrever um script para ser executado com xargs. por exemplo,

find /target -iname '*.xyz' -exec bash -c \
    'typeset -A seen
     for f in "$@"; do
       d="$(dirname "$f")";
       if [[ ! -v $seen[$d] ]]; then
         echo "Match found in $d. Going to execute command"
         # Execute command
         seen["$d"]=1
       fi
     done' {} +

exemplo simples de myscript.sh:

find /target -iname '*.xyz' -printf '%h
find /target -iname '*.xyz' -printf '%h
#!/bin/sh

for d in "$@" ; do
  cd "$d"
  echo "Match found in $d. Going to execute command"
  # execute command
done
0' | sort -z -u | xargs -0 -r /path/to/myscript.sh
0' | sort -z -u | xargs -0 -r -I {} sh -c "cd {} ; yourcommandhere"

Esta segunda versão será significativamente mais rápida se houver muitos diretórios correspondentes - ela só precisa bifurcar um shell uma vez (que, então, itera sobre cada argumento) em vez de bifurcar um shell uma vez por diretório.

BTW, nem sort nem xargs nem $seen[] são realmente necessários aqui ... mas eles tornam muito mais fácil ler e entender o que está acontecendo. Tão importante quanto isso, eliminando as duplicatas antecipadamente (com o printf e o sort), ele é executado muito mais rápido do que usar apenas o bash e elimina o risco (razoavelmente mínimo) de executar o comando mais de uma vez em qualquer diretório.

Aqui está outra maneira de fazer a mesma coisa, sem ordenar ou xargs:

find /target -iname '*.xyz' -exec bash -c \
    'typeset -A seen
     for f in "$@"; do
       d="$(dirname "$f")";
       if [[ ! -v $seen[$d] ]]; then
         echo "Match found in $d. Going to execute command"
         # Execute command
         seen["$d"]=1
       fi
     done' {} +

Isso usa uma matriz associativa no bash ( *.xml ) para acompanhar quais diretórios já foram vistos e processados. Observe que, se houver muitos milhares de arquivos -exec correspondentes (o suficiente para exceder o comprimento máximo da linha de comando, para que o script bash seja bifurcado mais de uma vez), seu comando pode ser executado mais de uma vez em qualquer diretório.

O script executado pela opção %code% do find pode ser um script independente, como na versão xargs acima.

BTW, qualquer uma das variantes aqui poderia facilmente executar um script awk ou perl ou qualquer script em vez de um script sh ou bash.

    
por 23.01.2018 / 03:58