Bash: рекурсивное копирование именованных файлов с сохранением структуры папок.

103

Я надеялся, что:

cp -R src/prog.js images/icon.jpg /tmp/package

даст симметричную структуру в целевом каталоге:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

но вместо этого оба файла копируются в / tmp / package. Плоская копия. (Это на OSX).

Есть ли простая функция bash, которую я могу использовать для копирования всех файлов, включая файлы, указанные с помощью подстановочного знака (например, src / *. Js), в их законное место в целевом каталоге. Немного похоже на «запускать для каждого файла mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")», но, возможно, одна команда.

Это соответствующая тема, которая предполагает, что это невозможно. Однако решение автора не так полезно для меня, потому что я хотел бы просто предоставить список файлов, подстановочных знаков или нет, и скопировать их все в целевой каталог. IIRC MS-DOS xcopy делает это, но, похоже, нет эквивалента для cp.

Mahemoff
источник

Ответы:

154

Вы пробовали использовать параметр --parents? Я не знаю, поддерживает ли это OS X, но это работает в Linux.

cp --parents src/prog.js images/icon.jpg /tmp/package

Если это не сработает в OS X, попробуйте

rsync -R src/prog.js images/icon.jpg /tmp/package

как предложил AIF.

Устун
источник
4
Спасибо. оказывается, что "cp --parents" невозможно на Mac, но хорошо знать флаг для других unixen. rsync -R - простейшее переносимое решение этой проблемы.
mahemoff 01
1
Я принял это из-за его элегантности / запоминаемости, но только что обнаружил, что он не копирует целые каталоги (по крайней мере, на OSX), в то время как tar ниже делает.
mahemoff
cp --parentsнедопустимая опция в OSX (BSD cp), но gcp(GNU cp) работает нормально. Если его еще нет в вашей системе, используйте brew install coreutils. У U будет много утилит с префиксом g.
kyb
@mahemoff cp -R --parentsи rsync -rRкопирует файлы и каталоги относительно.
Vortico 05
22

В одну сторону:

tar cf - <files> | (cd /dest; tar xf -)
Рэнди Проктор
источник
о, мне это нравится намного больше, чем мой ответ.
EMPraptor
4
Вы также можете использовать эту -Cопцию, чтобы выполнить chdir за вас - tar cf - _files_ | tar -C /dest xf -или что-то в этом роде.
Д.Шоули,
спасибо, это кратко, хотя я предпочитаю rsync для простоты.
mahemoff 01
1
Большой! Кто-нибудь знает, как превратить это в команду оболочки? Он должен принимать N входов, первые N-1 - это файлы для копирования, а последний - папка назначения.
arod 04
@arod $ {! #} - последний параметр, и используйте его для получения предыдущих аргументов stackoverflow.com/questions/1215538/… . Если вы пишете команду, пожалуйста, дайте ссылку на суть здесь.
mahemoff 05
18

В качестве альтернативы, если вы придерживаетесь старой школы, используйте cpio:

cd /source;
find . -print | cpio -pvdmB /target

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

Параметр «-p» предназначен для «сквозного» режима (в отличие от «-i» для ввода или «-о» для вывода). '-V' является подробным (перечислить файлы по мере их обработки). '-M' сохраняет время модификации. «-B» означает использование «больших блоков» (где большие блоки имеют размер 5120 байт вместо 512 байт); возможно, в наши дни это не действует.

Джонатан Леффлер
источник
1
Лучше использовать -print0с комбинацией --nullопций, чтобы она не прерывалась специальными символами и такими:find . -print0 | cpio -pvdmB --null /target
haridsv
Называйте это старой школой, называйте это портативным, называйте как угодно, но я определенно доверяю cpioэтой задаче. Я согласен , что -print0и -nullварианты должны быть использованы, в противном случае, в какой - то момент кто - то даст вам некоторые папки с «специальные символы» (пробелы, скорее всего) и что - то произойдет. Не то чтобы я говорю из личного опыта, но вы можете попытаться создать резервную копию кучи файлов, и в результате будет создана резервная копия только половины из них из-за пробелов в именах файлов. (Хорошо, я говорю из личного опыта.)
bballdave025
@ bballdave025: Проблемы возникают только в том cpioслучае, если имена файлов содержат символы новой строки, - тогда вы обычно получаете сообщение об ошибке о двух (или более) именах файлов, которые не были найдены для каждой новой строки в имени файла. (Иногда вы можете получить меньше сообщений, но это требует большой осторожности при построении тестового примера.) Когда я использовал cpio, --nullвыбора не было; Параметры с двойным тире не были частью обозначений опций SVR4, и концепция -print0не присутствовала findни в одном из них. Но это было давно (например, середина 90-х. До того, как Linux стал доминировать).
Джонатан Леффлер,
Спасибо за подробности, @Jonathan_Leffler. Я люблю учиться здесь. Теперь я пытаюсь вспомнить, какую ошибку рекурсивного копирования я сделал, из-за которой у меня возникли проблемы с пробелами - есть много способов, которыми я мог сделать эту ошибку,
bballdave025
@ bballdave025— В миксе используется одна возможность xargs- он разделяется на пробелы - пробелы, табуляторы, переводы строк. OTOH, я не уверен, как и зачем вы это сделали. В cpioруководстве GNU по режиму вывода четко указано, что в каждой строке указано одно имя файла. Руководство пользователя SVR4 (напечатано в 1990 г.) более расплывчато: cpio -o(режим копирования) считывает стандартный ввод для получения списка путевых имен и копирует эти файлы в стандартный вывод вместе с именем пути и информацией о состоянии. Следовательно, есть шанс, что он разбил имена на пробелы.
Джонатан Леффлер,
16

Параметр -R rsync сделает то, что вы ожидаете. Это очень многофункциональный копировальный аппарат. Например:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Примеры результатов:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Райан Брайт
источник
1

Пытаться...

for f in src/*.js; do cp $f /tmp/package/$f; done

так что за то, что вы делали изначально ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

или

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
источник
Вам не нужно echoни $vздесь. Также этот метод не сработает, если соответствующий каталог не существует в месте назначения.
Weijun Zhou