Копирование файлов оболочки Unix, выравнивание структуры папок
Вопрос
В оболочке UNIX bash (в частности, Mac OS X Leopard), каким был бы самый простой способ скопировать каждый файл, имеющий определенное расширение, из иерархии папок (включая подкаталоги) в ту же папку назначения (без вложенных папок)?
Очевидно, что существует проблема наличия дубликатов в исходной иерархии.Я бы не возражал, если бы они были перезаписаны.
Пример:Мне нужно скопировать каждый текстовый файл в следующей иерархии
/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
Решение
В bash:
find /foo -iname '*.txt' -exec cp \{\} /dest/ \;
find
найдет все файлы по указанному пути /foo
соответствие подстановочному знаку *.txt
, без учета регистра (Вот что -iname
средства).Для каждого файла, find
выполнит cp {} /dest/
, с найденным файлом вместо {}
.
Другие советы
Единственная проблема с решением Magnus заключается в том, что оно запускает новый процесс "cp" для каждого файла, что не очень эффективно, особенно если имеется большое количество файлов.
В Linux (или других системах с GNU coreutils) вы можете сделать:
find . -name "*.xml" -print0 | xargs -0 echo cp -t a
(Значение -0 позволяет ему работать, когда в именах ваших файлов есть странные символы, например пробелы.)
К сожалению, я думаю, что Mac поставляются с инструментами в стиле BSD.Кто-нибудь знает "стандартный" эквивалент переключателя "-t"?
Приведенные выше ответы не допускают коллизий имен, поскольку запрашивающий не возражал против перезаписи файлов.
Я действительно возражаю против перезаписи файлов, поэтому придумал другой подход.Заменив каждый / в пути на - сохраните иерархию в именах и поместите все файлы в одну плоскую папку.
Мы используем find, чтобы получить список всех файлов, затем awk, чтобы создать команду mv с исходным именем файла и измененным именем файла, затем передаем их в bash для выполнения.
find ./from -type f | awk '{ str=$0; sub(/\.\//, "", str); gsub(/\//, "-", str); print "mv " $0 " ./to/" str }' | bash
где ./from и ./to - это каталоги для mv from и to.
Если вы действительно хотите запустить только одну команду, почему бы не выбрать одну и не запустить ее?Вот так:
$ 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.
Что касается справочной страницы для cp в окне FreeBSD, то нет необходимости в переключении -t.cp будет считать, что последний аргумент в командной строке является целевым каталогом, если передается более двух имен.