Как скопировать определенные файлы (без иерархии папок), но не перезаписывать существующие файлы?

Мне нужно скопировать все *.doc файлы (но не папки, имена которых соответствуют *.doc) из сетевой папки \\server\source (включая файлы во всех вложенных папках) в локальную папку C:\destination без сохранения вложенных папок иерархии (т.е. все файлы должны идти непосредственно в C:\destination, и никакие вложенные папки не должны создаваться в C:\destination). Если есть несколько файлов с одинаковыми именами из разных подпапок \\server\source, только первый должен быть скопирован и никогда не перезаписан - все конфликтующие файлы, найденные позже, должны быть пропущены (может быть много случаев, подобных этому, и пропущенные файлы не должны быть перенесены по сети, иначе это займет слишком много времени). Вот моя попытка реализовать его в PowerShell:

cp \\server\source\* -Recurse -Include *.doc -Container:$false -Destination C:\destination

Есть две проблемы с этой командой:

  • Он копирует папки, имена которых также соответствуют *.doc.
  • В случае конфликтующих имен любой файл, найденный позже, передается по сети и перезаписывает предыдущий файл.

Можете ли вы предложить, как исправить эти проблемы?
Реализации с использованием copy, xcopy, robocopy, cscript или *.bat, *.cmd также приветствуются.
Локальная ОС - это Windows 8, а файловая система - NTFS.

Ответ 1

Я сначала создаю список файлов и проверю, как вы проходите через список.

Что-то вроде этого:

$srcdir = "\\server\source\";
$destdir = "C:\destination\";
$files = (Get-ChildItem $SrcDir -recurse -filter *.doc | where-object {-not ($_.PSIsContainer)});
$files|foreach($_){
    if (!([system.io.file]::Exists($destdir+$_.name))){
                cp $_.Fullname ($destdir+$_.name)
    };
}

Итак, используйте Get-ChildItem для отображения файлов в исходной папке, соответствующих фильтру, через where-object, чтобы вырезать каталоги.

Затем пройдите через каждый файл в цикле foreach и проверьте, существует ли имя файла (не полное имя) в целевом объекте с помощью метода Exists класса system.io.file.NET.

Если это не так, скопируйте, используя только оригинальное имя файла (отбрасывая исходный путь).

Используйте параметр -whatif на копии при тестировании, поэтому он отображает только то, что он сделал бы, в случае, если результат не тот, который вы хотели:-)

Ответ 2

Предыдущие ответы кажутся мне слишком сложными, если я что-то не понимаю. Это должно работать:

Get-ChildItem "\\server\source\" *.doc -Recurse | ?{-not ($_.PSIsContainer -or (Test-Path "C:\Destination\$_"))} | Copy-Item -Destination "C:\Destination"

Ни одна из встроенных команд - copy, xcopy или robocopy - будет делать то, что вы хотите самостоятельно, но есть утилита с именем xxcopy, которая будет удобно доступна в http://www.xxcopy.com. Он имеет ряд встроенных опций, специально предназначенных для выравнивания деревьев каталогов в один каталог. Ниже описано, что вы описали:

xxcopy "\\server\source\*.doc" "C:\Destination" /SGFO

Тем не менее, xxcopy имеет различные другие опции для обработки повторяющихся имен файлов, чем просто копирование первой, с которой они столкнулись, например, добавление имени исходного каталога в имя файла или добавление последовательного числа для всех, кроме первого, или всего, кроме самого нового или самый старый. Подробнее см. на этой странице: http://www.xxcopy.com/xxcopy16.htm

Ответ 3

# Get all *.doc files under \\server\source
Get-ChildItem -Path \\server\source *.doc -Recurse |
    # Filter out directores
    Where-Object { -not $_.PsIsContainer } | 
    # Add property for destination
    Add-Member ScriptProperty -Name Destination -Value { Join-Path 'C:\destination' $this.Name } -PassThru |
    # Filter out files that exist on the destination
    Where-Object { -not (Test-Path -Path $_.Destination -PathType Leaf } | 
    # Copy. 
    Copy-Item

Ответ 4

Зачем использовать foreach, когда у вас уже есть конвейер? Вычисляемые свойства для победы!

Get-ChildItem -Recurse -Path:\\Server\Path -filter:'*.doc' | 
    Where { -not $_.PSIsContainer } |
    Group Name |
    Select @{Name='Path'; Expression={$_.Group[0].FullName}},@{Name='Destination'; Expression={'C:\Destination\{0}' -f $_.Name}} |
    Copy-Item

Ответ 5

$docFiles = Get-ChildItem -Path "\\server\source" -Recurse | Where-Object {$_.Attributes.ToString() -notlike "*Directory*" -and ($_.Name -like "*.doc" -or $_.Name -like "*.doc?")} | Sort-Object -Unique;
$docFiles | ForEach-Object { Copy-Item -Path $_.fullname -Destination "C:\destination" };

Первая строка считывает каждый файл *.doc и *.doc? (поэтому он также рассматривает формат Office 2010.docx), за исключением Каталогов и дубликатов файлов.
Вторая строка копирует каждый элемент из пункта назначения в источник (папка C:\destination должна уже существовать).
В общем, я предлагаю вам разделить команду на несколько строк, потому что проще создать код (в этом случае первая задача: получить файлы, вторая задача: скопировать файлы).