Как использовать команду exec внутри цикла for после команды find?

Я пытался заставить это работать какое-то время. Я хочу найти список файлов, а затем скопировать их на определенный путь.

Если я запускаю команду отдельно, то она работает так:

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

Но Я не смог запустить его в цикле 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;

Я пробовал несколько других вещей, которые вряд ли сработают. Первая строка в цитате кода просто застревает, а другие ничего не копируют, даже если они не застревают.

Я не смог запустить что-либо с exec внутри цикла for после поиска, и на данный момент я не уверен, что делать.

Я решил немедленную проблему, выполнив поиск файлов и запустив результаты в другой файл, а затем запустив копию внутри цикла for отдельно, но мне все же хотелось бы знать, как это сделать.

]
4
задан 25 August 2017 в 13:05

9 ответов

В то время как другие ответы правильные, я хочу предложить другой подход, который не требует многократных вызовов find для повторного сканирования той же структуры каталога. Основная идея состоит в том, чтобы использовать find для

сгенерировать список файлов, соответствующих общим критериям, применить к этому списку специальный фильтр и выполнить некоторые действия, например. г. cp, о записях отфильтрованного списка.

Реализация 1

(требует, чтобы Bash считывал записи с нулевым байтом)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s\0' "$f";;
  esac
done |
xargs -r0 -- cp -vt /some/other/path --

Каждая труба соответствует началу следующего шага три шага, описанные выше.

У оператора case есть преимущество, позволяющее совпадениям с глобусами. В качестве альтернативы вы можете использовать условные выражения Bash:

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s\0' "$f"
fi

Реализация 2

Если список имен файлов для соответствия немного длиннее и не может быть заменен небольшим набором [d10 ] globbing или если список имен файлов, которые будут соответствовать, неизвестен на момент написания, вы можете обратиться к массиву, который содержит список интересующих файлов:

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\0' "$f"
    fi
  done
done |
xargs -r0 -- cp -vt /some/other/path --

Реализация 3

В качестве вариации мы можем использовать массив , который, как мы надеемся, имеет более быстрое время поиска, чем обычные массивы «list»:

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\0' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --
4
ответ дан 22 May 2018 в 19:05

В то время как другие ответы правильные, я хочу предложить другой подход, который не требует многократных вызовов find для повторного сканирования той же структуры каталога. Основная идея состоит в том, чтобы использовать find для

сгенерировать список файлов, соответствующих общим критериям, применить к этому списку специальный фильтр и выполнить некоторые действия, например. г. cp, о записях отфильтрованного списка.

Реализация 1

(требует, чтобы Bash считывал записи с нулевым байтом)

find /some/path -type f -print0 | while read -rd '' f; do case "${f##*/}" in file1|file2|file3) printf '%s\0' "$f";; esac done | xargs -r0 -- cp -vt /some/other/path --

Каждая труба соответствует началу следующего шага три шага, описанные выше.

У оператора case есть преимущество, позволяющее совпадениям с глобусами. В качестве альтернативы вы можете использовать условные выражения Bash:

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then printf '%s\0' "$f" fi

Реализация 2

Если список имен файлов для соответствия немного длиннее и не может быть заменен небольшим набором globbing или если список имен файлов, которые будут соответствовать, неизвестен на момент написания, вы можете обратиться к массиву, который содержит список интересующих файлов:

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\0' "$f" fi done done | xargs -r0 -- cp -vt /some/other/path --

Реализация 3

В качестве вариации мы можем использовать массив , который, как мы надеемся, имеет более быстрое время поиска, чем обычные массивы «list»:

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\0' "$f" fi done | xargs -r0 -- cp -vt /some/other/path --
4
ответ дан 18 July 2018 в 07:56

В то время как другие ответы правильные, я хочу предложить другой подход, который не требует многократных вызовов find для повторного сканирования той же структуры каталога. Основная идея состоит в том, чтобы использовать find для

сгенерировать список файлов, соответствующих общим критериям, применить к этому списку специальный фильтр и выполнить некоторые действия, например. г. cp, о записях отфильтрованного списка.

Реализация 1

(требует, чтобы Bash считывал записи с нулевым байтом)

find /some/path -type f -print0 | while read -rd '' f; do case "${f##*/}" in file1|file2|file3) printf '%s\0' "$f";; esac done | xargs -r0 -- cp -vt /some/other/path --

Каждая труба соответствует началу следующего шага три шага, описанные выше.

У оператора case есть преимущество, позволяющее совпадениям с глобусами. В качестве альтернативы вы можете использовать условные выражения Bash:

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then printf '%s\0' "$f" fi

Реализация 2

Если список имен файлов для соответствия немного длиннее и не может быть заменен небольшим набором globbing или если список имен файлов, которые будут соответствовать, неизвестен на момент написания, вы можете обратиться к массиву, который содержит список интересующих файлов:

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\0' "$f" fi done done | xargs -r0 -- cp -vt /some/other/path --

Реализация 3

В качестве вариации мы можем использовать массив , который, как мы надеемся, имеет более быстрое время поиска, чем обычные массивы «list»:

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\0' "$f" fi done | xargs -r0 -- cp -vt /some/other/path --
4
ответ дан 24 July 2018 в 18:56

Два вопроса:

for f in some_file не перебирает содержимое some_file, просто перебирает или принимает литералную строку some_file. Чтобы преодолеть это, забудьте про цикл for для итерации содержимого файла, используйте правильно реализованную конструкцию while. В этом случае переменные не будут расширены при вставке в одинарные кавычки '$f'. Чтобы ваша оригинальная идея работала, используйте двойные кавычки.

Объединяя их, предполагая, что имена файлов являются символами новой строки, отделенными внутри файла file_list:

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

Или, если вы знаете, что файлы будут находиться в каталоге /path1, а не в любой подкаталог из него, вы можете просто использовать массив для получения имен файлов и использовать (GNU) cp напрямую, опять же предполагая, что имена файлов, разделенных символом новой строки, внутри file_list:

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

В случае огромного количество файлов, вам лучше было бы перебирать и cp индивидуально, а не вбрасывать в массив:

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

Если у вас есть список файлов, например, например, file1 file2 file3 ... непосредственно в конструкции for, то просто используя двойные кавычки:

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

Теперь вы можете использовать cp прямо здесь, если все файлы не будут находиться в каком-либо подкаталоге:

cp -t /path2 file1 file2 file3

Или вы можете указать статические абсолютные или относительные пути, если вы хотите использовать cp только для работы с любыми файлами под любым подкаталогом.

7
ответ дан 22 May 2018 в 19:05
  • 1
    Мне нужно найти файлы в подпапках. Я пробовал ваши предложения, и они не работают (они застревают и не двигаются). У меня есть список файлов в конструкторе for, как вы сказали в последнем примере цикла for. Например: for f in file1 file2 file3; do find /path1 -name "$f" -exec cp '{}' /path2 \; done P.S. каталог невелик, поэтому поиск одного файла выполняется с помощью instantlt, и я пытаюсь сделать это только с одним файлом (для f в файле1 и т. д.), и это тоже не работает. – John Hamilton 25 August 2017 в 11:28
  • 2
    @JohnHamilton Пожалуйста, определите , не работайте , а также покажите точную команду, которую вы выполнили. – heemayl 25 August 2017 в 11:49
  • 3
    Это не работает, как в предыдущих случаях. Он ничего не делает, просто висит там с новой линией навсегда. – John Hamilton 25 August 2017 в 14:04
  • 4
    @JohnHamilton. Вы определенно не использовали то, что я использовал. Проверьте команды в моем ответе снова ... – heemayl 25 August 2017 в 15:05

Вы пояснили, что ваш список файлов не является текстовым файлом, а серией имен файлов, которые вы хотите найти с помощью find. Вы сказали, что это работает для вас:

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

Предположительно, вы имеете в виду, что вы получаете ожидаемый список файлов из этой команды.

Тогда вы говорите, что это не работает: [!d2 ]

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

Предположительно, вы имеете в виду, что файлы, перечисленные выше, не копируются в <path2>. Я не уверен, какую ошибку вы получаете, но, как написано, я бы ожидал, что команда будет выглядеть так:

$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

Однако я предлагаю использовать флаг -t для указания каталога. Я должен поблагодарить Дэвида Фёрстера за этот комментарий, который, как мне кажется, показывает лучшую форму для вызова cp или mv, используя find.

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

Примечания

-v быть подробным - рассказать нам, что делается -t использовать указанный каталог в качестве адресата -- не принимают никаких дополнительных параметров +, если имеется несколько совпадений (в вашем случае это маловероятно, так как for будет запускаться один раз для каждого элемента в списке файлов, но для каждого имени может быть несколько совпадающих файлов) затем создайте список аргументов для cp вместо запуска cp несколько раз ;, разделяя команды в оболочке. Форма петли for равна
for var in things; do command "$var"; done
Если она не видит ; до done, bash будет ожидать ; или сигнал прерывания.
5
ответ дан 22 May 2018 в 19:05
  • 1
    Очень подробный и тщательный ответ, спасибо. Я пробовал эту другую команду с дополнительной точкой с запятой, она запускала команду, но не копировала ничего в каталог. Теперь я думаю, что он пытался скопировать имя файла вместо указанного каталога. – John Hamilton 25 August 2017 в 14:41
  • 2
    После нескольких испытаний он начал работать, и я не уверен, почему он не работал раньше: for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done – John Hamilton 25 August 2017 в 14:49

Два вопроса:

for f in some_file не перебирает содержимое some_file, просто перебирает или принимает литералную строку some_file. Чтобы преодолеть это, забудьте про цикл for для итерации содержимого файла, используйте правильно реализованную конструкцию while. В этом случае переменные не будут расширены при вставке в одинарные кавычки '$f'. Чтобы ваша оригинальная идея работала, используйте двойные кавычки.

Объединяя их, предполагая, что имена файлов являются символами новой строки, отделенными внутри файла file_list:

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

Или, если вы знаете, что файлы будут находиться в каталоге /path1, а не в любой подкаталог из него, вы можете просто использовать массив для получения имен файлов и использовать (GNU) cp напрямую, опять же предполагая, что имена файлов, разделенных символом новой строки, внутри file_list:

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

В случае огромного количество файлов, вам лучше было бы перебирать и cp индивидуально, а не вбрасывать в массив:

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

Если у вас есть список файлов, например, например, file1 file2 file3 ... непосредственно в конструкции for, то просто используя двойные кавычки:

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

Теперь вы можете использовать cp прямо здесь, если все файлы не будут находиться в каком-либо подкаталоге:

cp -t /path2 file1 file2 file3

Или вы можете указать статические абсолютные или относительные пути, если вы хотите использовать cp только для работы с любыми файлами под любым подкаталогом.

7
ответ дан 18 July 2018 в 07:56

Вы пояснили, что ваш список файлов не является текстовым файлом, а серией имен файлов, которые вы хотите найти с помощью find. Вы сказали, что это работает для вас:

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

Предположительно, вы имеете в виду, что вы получаете ожидаемый список файлов из этой команды.

Тогда вы говорите, что это не работает:

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

Предположительно, вы имеете в виду, что файлы, перечисленные выше, не копируются в <path2>. Я не уверен, какую ошибку вы получаете, но, как написано, я бы ожидал, что команда будет выглядеть так:

$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

Однако я предлагаю использовать флаг -t для указания каталога. Я должен поблагодарить Дэвида Фёрстера за этот комментарий, который, как мне кажется, показывает лучшую форму для вызова cp или mv, используя find.

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

Примечания

-v быть подробным - рассказать нам, что делается -t использовать указанный каталог в качестве адресата -- не принимают никаких дополнительных параметров +, если имеется несколько совпадений (в вашем случае это маловероятно, так как for будет запускаться один раз для каждого элемента в списке файлов, но для каждого имени может быть несколько совпадающих файлов) затем создайте список аргументов для cp вместо запуска cp несколько раз ;, разделяя команды в оболочке. Форма петли for равна for var in things; do command "$var"; done Если она не видит ; до done, bash будет ожидать ; или сигнал прерывания.
5
ответ дан 18 July 2018 в 07:56

Два вопроса:

for f in some_file не перебирает содержимое some_file, просто перебирает или принимает литералную строку some_file. Чтобы преодолеть это, забудьте про цикл for для итерации содержимого файла, используйте правильно реализованную конструкцию while. В этом случае переменные не будут расширены при вставке в одинарные кавычки '$f'. Чтобы ваша оригинальная идея работала, используйте двойные кавычки.

Объединяя их, предполагая, что имена файлов являются символами новой строки, отделенными внутри файла file_list:

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

Или, если вы знаете, что файлы будут находиться в каталоге /path1, а не в любой подкаталог из него, вы можете просто использовать массив для получения имен файлов и использовать (GNU) cp напрямую, опять же предполагая, что имена файлов, разделенных символом новой строки, внутри file_list:

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

В случае огромного количество файлов, вам лучше было бы перебирать и cp индивидуально, а не вбрасывать в массив:

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

Если у вас есть список файлов, например, например, file1 file2 file3 ... непосредственно в конструкции for, то просто используя двойные кавычки:

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

Теперь вы можете использовать cp прямо здесь, если все файлы не будут находиться в каком-либо подкаталоге:

cp -t /path2 file1 file2 file3

Или вы можете указать статические абсолютные или относительные пути, если вы хотите использовать cp только для работы с любыми файлами под любым подкаталогом.

7
ответ дан 24 July 2018 в 18:56
  • 1
    Мне нужно найти файлы в подпапках. Я пробовал ваши предложения, и они не работают (они застревают и не двигаются). У меня есть список файлов в конструкторе for, как вы сказали в последнем примере цикла for. Например: for f in file1 file2 file3; do find /path1 -name "$f" -exec cp '{}' /path2 \; done P.S. каталог невелик, поэтому поиск одного файла выполняется с помощью instantlt, и я пытаюсь сделать это только с одним файлом (для f в файле1 и т. д.), и это тоже не работает. – John Hamilton 25 August 2017 в 11:28
  • 2
    @JohnHamilton Пожалуйста, определите , не работайте , а также покажите точную команду, которую вы выполнили. – heemayl 25 August 2017 в 11:49
  • 3
    Это не работает, как в предыдущих случаях. Он ничего не делает, просто висит там с новой линией навсегда. – John Hamilton 25 August 2017 в 14:04
  • 4
    @JohnHamilton. Вы определенно не использовали то, что я использовал. Проверьте команды в моем ответе снова ... – heemayl 25 August 2017 в 15:05

Вы пояснили, что ваш список файлов не является текстовым файлом, а серией имен файлов, которые вы хотите найти с помощью find. Вы сказали, что это работает для вас:

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

Предположительно, вы имеете в виду, что вы получаете ожидаемый список файлов из этой команды.

Тогда вы говорите, что это не работает:

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

Предположительно, вы имеете в виду, что файлы, перечисленные выше, не копируются в <path2>. Я не уверен, какую ошибку вы получаете, но, как написано, я бы ожидал, что команда будет выглядеть так:

$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

Однако я предлагаю использовать флаг -t для указания каталога. Я должен поблагодарить Дэвида Фёрстера за этот комментарий, который, как мне кажется, показывает лучшую форму для вызова cp или mv, используя find.

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

Примечания

-v быть подробным - рассказать нам, что делается -t использовать указанный каталог в качестве адресата -- не принимают никаких дополнительных параметров +, если имеется несколько совпадений (в вашем случае это маловероятно, так как for будет запускаться один раз для каждого элемента в списке файлов, но для каждого имени может быть несколько совпадающих файлов) затем создайте список аргументов для cp вместо запуска cp несколько раз ;, разделяя команды в оболочке. Форма петли for равна for var in things; do command "$var"; done Если она не видит ; до done, bash будет ожидать ; или сигнал прерывания.
5
ответ дан 24 July 2018 в 18:56
  • 1
    Очень подробный и тщательный ответ, спасибо. Я пробовал эту другую команду с дополнительной точкой с запятой, она запускала команду, но не копировала ничего в каталог. Теперь я думаю, что он пытался скопировать имя файла вместо указанного каталога. – John Hamilton 25 August 2017 в 14:41
  • 2
    После нескольких испытаний он начал работать, и я не уверен, почему он не работал раньше: for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done – John Hamilton 25 August 2017 в 14:49

Другие вопросы по тегам:

Похожие вопросы: