Como eu uso o comando exec dentro de um loop for após um comando find?

4

Eu tenho tentado fazer isso funcionar por um tempo agora. Eu quero procurar uma lista de arquivos e copiá-los todos para um determinado caminho.

Se eu executar o comando separadamente, ele funciona assim:

find <path> -name 'file1' -exec cp '{}' <path2> \;

Mas não consegui executá-lo dentro de um loop for.

#this works
for f in file1 file2 file3...; do find <path> -name "$f" \; done;
#none of these work (this code tries to find and copy the files named file and list from the path)
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp '{}' <path2> \; done;
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp {} <path2> \; done;

Eu tentei algumas outras coisas que provavelmente não funcionariam. A primeira linha na citação do código apenas fica presa e os outros não copiam nada, mesmo que não fiquem presos.

Eu não consegui executar nada com exec dentro de um loop for depois de uma pesquisa e, neste momento, não tenho certeza do que fazer.

Eu resolvi o problema imediato pesquisando os arquivos, registrando os resultados em outro arquivo e depois executando uma cópia dentro de um loop for separadamente, mas ainda gostaria de saber como fazer isso.

    
por John Hamilton 25.08.2017 / 08:15

3 respostas

4

Enquanto as outras respostas estão corretas, quero oferecer uma abordagem diferente que não requeira várias invocações de find para varrer a mesma estrutura de diretórios repetidamente. A ideia básica é usar find para

  1. gera uma lista de arquivos que correspondem aos critérios comuns,
  2. aplica um filtro personalizado a essa lista e
  3. execute alguma ação, e. g. cp , nas entradas da lista filtrada.

Implementação 1

(requer o Bash para ler registros delimitados por nulo)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s
if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s
FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s
declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s
find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s
if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s
FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s
declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s%pre%' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --
' "$f" fi done done | xargs -r0 -- cp -vt /some/other/path --
' "$f" fi
' "$f";; esac done | xargs -r0 -- cp -vt /some/other/path --
' "$f" fi done | xargs -r0 -- cp -vt /some/other/path --
' "$f" fi done done | xargs -r0 -- cp -vt /some/other/path --
' "$f" fi
' "$f";; esac done | xargs -r0 -- cp -vt /some/other/path --

Cada canal corresponde ao início do próximo passo dos três passos descritos acima.

A declaração case tem a vantagem de permitir correspondências globbing . Alternativamente, você pode usar as expressões condicionais do Bash:

%pre%

Implementação 2

Se a lista de nomes de arquivos para correspondência for um pouco mais longa e não puder ser substituída por um pequeno conjunto de globbing padrões, ou se a lista de nomes de arquivos para correspondência não é conhecida no momento da escrita, você pode recorrer a um array que contém a lista de nomes de arquivos de interesse:

%pre%

Implementação 3

Como variação, podemos usar uma matriz associativa que, esperamos, tenha uma aparência mais rápida vezes mais do que simples matrizes "list":

%pre%     
por David Foerster 25.08.2017 / 14:14
7

Duas questões:

  1. for f in some_file não faz iteração sobre o conteúdo de some_file , basta iterar ou obter a string literal some_file . Para superar isso, esqueça for looping para iterar sobre o conteúdo do arquivo, use uma construção while implementada corretamente.

  2. As variáveis não serão expandidas quando colocadas entre aspas simples, '$f' neste caso. Para que sua ideia original funcione, use aspas duplas.

Colocando estes juntos, assumindo que os nomes dos arquivos são newline separados dentro de file_list file:

while IFS= read -r f; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list

Ou se você souber que os arquivos estariam no diretório /path1 , não sob qualquer subdiretório dele, você pode simplesmente usar uma matriz para obter os nomes dos arquivos, e usar (GNU) cp diretamente, novamente assumindo nova linha separada nomes de arquivos dentro de file_list :

( 
 IFS=$'\n'
 files=( $(<file_list) )
 cp -t /path2 "${files[@]}"
)

No caso de um grande número de arquivos, seria melhor fazer uma iteração e cp individualmente, em vez de despejar em uma matriz:

while IFS= read -r f; do cp -- "$f" /path2; done <file_list

Se você tiver uma lista de arquivos, por exemplo, file1 file2 file3 ... diretamente na construção for , então, basta usar aspas duplas:

for f in file1 file2 file3; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done

Agora, você pode usar cp diretamente aqui também se todos os arquivos não estiverem em nenhum subdiretório:

cp -t /path2 file1 file2 file3

Ou você pode fornecer caminhos estáticos absolutos ou relativos caso queira usar cp apenas para lidar com quaisquer arquivos em qualquer subdiretório.

    
por heemayl 25.08.2017 / 08:29
5

Você esclareceu que sua lista de arquivos não é um arquivo de texto, mas uma série de nomes de arquivos que você deseja encontrar com find . Você mencionou que isso funciona para você:

for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;

presumivelmente, você quer dizer que você obteve a lista esperada de arquivos desse comando.

Você diz que isso não funciona:

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done

presumivelmente, você quer dizer que os arquivos listados anteriormente não são copiados para <path2> . Eu não tenho certeza do erro que você está recebendo, mas como está escrito, eu esperaria que o comando travasse assim:

$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>

aguardando o ; ausente. Supondo que você tenha corrigido esse problema, não consigo pensar em nenhum outro motivo óbvio pelo qual seu comando iria definitivamente falhar. Você deve ser capaz de gerenciar com

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done

No entanto, sugiro usar o sinalizador -t para especificar o diretório. Eu tenho que agradecer a David Foerster para este comentário que, a meu ver, mostra a melhor forma para uma invocação de cp ou mv usando find . Ele também se recusará a substituir arquivos duplicados.

for f in file1 file2 file3; do find /path/to/search -name "$f" -exec cp -vt /path/to/destination -- {} + ; done

Notas

  • -v seja detalhado - diga-nos o que está sendo feito
  • -t usa o diretório especificado como destino
  • -- não aceita mais opções
  • + se houver várias correspondências (no seu caso, isso é improvável, pois for será executado uma vez para cada item na lista de arquivos, mas pode haver vários arquivos correspondentes para cada nome) e, em seguida, construir uma lista de argumentos para cp em vez de executar cp várias vezes
  • ; separa comandos no shell. A forma de um loop for é

    for var in things; do command "$var"; done
    

    Se ele não vir o ; antes de done , o bash aguardará ; ou um sinal de interrupção.

por Zanna 25.08.2017 / 13:31