Скопируйте структуру папок с файловой системой Unix
В оболочке UNIX bash (в частности, Mac OS X Leopard) был бы самый простой способ скопировать каждый файл с определенным расширением из иерархии папок (включая подкаталоги) в одну и ту же папку назначения (без подпапок)?
Очевидно, что существует проблема дублирования исходной иерархии. Я бы не прочь, если они будут перезаписаны.
Пример: мне нужно скопировать каждый .txt файл в следующую иерархию
/foo/a.txt
/foo/x.jpg
/foo/bar/a.txt
/foo/bar/c.jpg
/foo/bar/b.txt
В папку с именем "dest" и получите:
/dest/a.txt
/dest/b.txt
Ответы
Ответ 1
В bash:
find /foo -iname '*.txt' -exec cp \{\} /dest/ \;
find
найдет все файлы под контуром /foo
, соответствующие подстановочному знаку *.txt
, без учета регистра (что означает -iname
). Для каждого файла find
выполнит cp {} /dest/
, а найденный файл вместо {}
.
Ответ 2
Единственная проблема с решением Магнуса заключается в том, что он генерирует новый процесс "cp" для каждого файла, что не очень эффективно, особенно если имеется большое количество файлов.
В Linux (или других системах с GNU coreutils) вы можете:
find . -name "*.xml" -print0 | xargs -0 echo cp -t a
(Параметр -0 позволяет ему работать, когда ваши имена файлов содержат в них странные символы - например, пробелы).
К сожалению, я думаю, что у Mac есть инструменты стиля BSD. Кто-нибудь знает "стандартный" эквивалент переключателя "-t"?
Ответ 3
Ответы, приведенные выше, не допускают коллизий имен, поскольку искатель не возражал против перезаписи файлов.
Я делаю так, чтобы файлы были переписанными, поэтому придумал другой подход. Замена каждого/в пути: сохранить иерархию в именах и поместить все файлы в одну плоскую папку.
Мы используем find для получения списка всех файлов, затем awk для создания команды mv с исходным именем файла и измененным именем файла, а затем передаем их в bash для выполнения.
find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash
где./from и. /to являются каталогами mv от и до.
Ответ 4
Если вы действительно хотите запустить только одну команду, почему бы не задуматься и запустить ее? Например:
$ find /foo -name '*.txt' | xargs echo | sed -e 's/^/cp /' -e 's|$| /dest|' | bash -sx
Но это не будет иметь большого значения по производительности, если вы не сделаете этого много или не будете иметь массу файлов. Однако будьте осторожны с названиями. Я заметил, что GNU cp по крайней мере предупреждает о столкновениях:
cp: will not overwrite just-created `/dest/tubguide.tex' with `./texmf/tex/plain/tugboat/tubguide.tex'
Я думаю, что самое чистое:
$ find /foo -name '*.txt' | xargs -i cp {} /dest
Меньше синтаксиса для запоминания, чем опция -exec.
Ответ 5
Что касается man-страницы для cp в поле FreeBSD, нет необходимости в -t-переключателе. cp будет принимать последний аргумент в командной строке как целевой каталог, если передано более двух имен.