Как мне найти и скопировать произвольный список файлов в терминале?

Я сейчас пытаюсь найти файлы в терминале. Я нашел много ответов о том, как найти файлы по определенному шаблону, например, файлы, заканчивающиеся определенным расширением или с определенного периода времени и т. Д.

Однако я пытался выяснить, как найти и скопировать несколько произвольных файлов, например, список определенных файлов.

Скажем, например, у меня есть следующие папки и файлы

/Documents/Folder_1/ с содержанием:

file1.txt file2.txt file3.txt file4.txt

/Documents/Folder_2/ с содержанием:

file5.txt file6.txt file7.txt

/Documents/Folder_3/ с содержанием:

file8.txt, file9.txt

и что я хочу сделать, это сделать новую папку под названием Folder_4 и скопировать file2.txt, file5.txt а также file9.txt в эту новую папку.

Есть ли способ сделать это через терминал?

Я попытался сделать текстовый список имен файлов и назвать его чем-то вроде list.txt, например

file2.txt
file5.txt
file9.txt

и загружаем этот текстовый файл в команду Find, например, так:

find /home/usr/Documents -name $cat list.txt

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

Мне интересно, есть ли способ найти несколько произвольных файлов с помощью команды Find, или я думаю об этом неправильно?

РЕДАКТИРОВАТЬ: Просто хотел сказать спасибо за ответы и резюмировать, как я закончил подход к этой проблеме. Я особенно ценю то, что кто-то объяснил мне, что команда find не предназначена для приема более одного ввода без флага -o, это заставило меня чувствовать себя немного менее глупо из-за того, что я не смог понять это!

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

В основном сценарий, который я закончил, более или менее похож на это:

#!/bin/bash

#take user input
echo "Please enter list of filenames to search"
read list
echo "Please enter the folder path to search"
read path
echo "Please enter the destination folder path"
read destination

#use inputted list as input for array, and then use a for loop to find each item on list one by one
array=($list)
for i in "${array[@]}"
do
    find "$path" -iname "i" -exec cp '{}' "$destination" \;
done

3 ответа

К сожалению find командования -name Предикат принимает только один шаблон. Если вы хотите искать несколько файлов по имени, вам нужно связать их -o (логическое OR) оператор - что-то вроде:

find Documents/ \( -name "file2.txt" -o -name "file5.txt" -o -name "file9.txt" \) -print

Это затрудняет построение поиска программно из списка; самое близкое, что я могу прийти к вашей попытке команды:

  1. читать список в массив оболочки

    mapfile -t files < list.txt
    
  2. использовать оболочку Bash printf построить список предикатов

    printf -- '-name "%s" -o ' "${files[@]}"
    
  3. использование eval оценить полученную командную строку

Здесь есть морщина, поскольку мы используем printfвозможность повторного использования формата для построения списка таким образом, у нас остается "висячий" -o; мы можем обойти это, завершив список с -false тест (с -o -false является логическим не-опцией), так что наша последняя строка предиката становится

"\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)"

Собираем все вместе - дано

$ tree dir
dir
├── Folder_1
│   ├── file1.txt
│   ├── file2.txt
│   ├── file3.txt
│   └── file4.txt
├── Folder_2
│   ├── file5.txt
│   ├── file6.txt
│   └── file7.txt
└── Folder_3
    ├── file8.txt
    └── file9.txt

3 directories, 9 files

а также

$ cat list.txt
file2.txt
file5.txt
file9.txt

затем

$ mapfile -t files < list.txt

$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -print
dir/Folder_1/file2.txt
dir/Folder_2/file5.txt
dir/Folder_3/file9.txt

Чтобы скопировать файлы, а не просто перечислить их, вы можете сделать

$ mkdir newdir
$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -exec cp -t newdir/ -- {} +

в результате чего

$ tree newdir
newdir
├── file2.txt
├── file5.txt
└── file9.txt

0 directories, 3 files

Обратите внимание eval Команда сильна и потенциально открыта для злоупотреблений: используйте с осторожностью.


На практике, учитывая, что вы, похоже, хотите найти только небольшое количество файлов, подход KISS заключается в том, чтобы принять снижение производительности нескольких find звонки и просто использовать цикл:

while read -r f; do
  find dir/ -name "$f" -exec cp -v -- {} newdir/ \;
done < list.txt

или даже используя xargs

xargs -a list.txt -n1 -I@ find dir/ -name @ -exec cp -v -- {} newdir/ \;

Все еще пытаюсь выяснить ваш вопрос самым ясным способом.

Вы пытаетесь найти какие-то конкретные файлы в каталоге? Если да и существует шаблон, который вы можете использовать регулярные выражения, чтобы найти их, используйте find <directory> -regex "your_regex" и если нет шаблона, вам нужно указать имена файлов, которые вы ищете. Вы можете использовать скрипт Python ниже, чтобы скопировать ваши файлы.

import shutil
import os

list = [file1.txt, file2.txt] # replace with your file list.
destination = "your_destination" # replace with your destination.
directory = os.getcwd() # replace os.getcwd() with your directory.

for dirpath, dirname, filenames in os.walk(directory, topdown='true'):
    for file in filenames :
        if file in list :
            shutil.copy(os.path.join(dirpath, file), destination) 

Если вышеупомянутое не ваше дело, тогда я предлагаю использовать это:

find <somewhere> -name "something" | xargs cp -t <your_destination>

Сценарий bash ниже, если он выполняется из каталога верхнего уровня (скажем, из вашего ~/Documents папка), создаст новую директорию и скопирует / переместит нужные файлы. По умолчанию он отображает только найденные файлы, так что пользователь может проверить, является ли это желаемым результатом. Примечание: заменить echo с cp скопировать или mv двигаться.

Список файлов назначен regex переменная с \| разделитель (который для grep значит логично или утверждение)

copy_files.sh:

#!/bin/bash
new_dir="./dir4"
[ -d "$new_dir" ] ||  mkdir "$new_dir"
regex="file1\|file3\|file6"
find . -print0 | while IFS= read -d $'\0' line;
do
    if grep -q "$regex" <<< $line
    then
       filename=$(basename "$line")
       printf "%s\n" "Found $filename"
       echo "$line" "$new_dir"/"$filename"
       # Replace echo with cp for copying,
       # or mv for moving
    fi   
done
bash-4.3$ ./copy_files.sh 
Found file6
./dir3/file6 ./dir4/file6
Found file3
./dir1/file3 ./dir4/file3
Found file1
./dir1/file1 ./dir4/file1

Как вы можете видеть выше, скрипт сообщает, какой файл был найден и куда он скопирует или переместит, например, в последней строке он скопирует / переместит ./dir1/file1 в ./dir4/file1 если это было на самом деле с помощью cp или же mv и не только echo

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