Список исключений в PowerShell Copy-Item не работает
У меня есть следующий фрагмент сценария PowerShell:
$source = 'd:t1*'
$dest = 'd:t2'
$exclude = @('*.pdb','*.config')
Copy-Item $source $dest -Recurse -Force -Exclude $exclude
который работает для копирования всех файлов и папок из t1 в t2, но он исключает только список исключений в папке "root"/"first-level", а не в подпапках.
Как сделать так, чтобы исключить список исключений во всех папках?
10 ответов:
Я думаю, что лучший способ-использовать Get-ChildItem и pipe в команде Copy-Item.
Я обнаружил, что это работает:
$source = 'd:\t1' $dest = 'd:\t2' $exclude = @('*.pdb','*.config') Get-ChildItem $source -Recurse -Exclude $exclude | Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}в основном, то, что происходит здесь, заключается в том, что вы проходите через допустимые файлы один за другим, а затем копируете их в новый путь. Оператор "Join-Path" в конце таков, что каталоги также сохраняются при копировании файлов. Эта часть принимает каталог назначения и присоединяет его к каталогу после источника путь.
Я получил идею от здесь, а затем немного изменил его, чтобы он работал для этого примера.
Я надеюсь, что это работает!
параметр exclude не будет работать с dirs. Вариант сценария Бо делает трюк:
$source = 'c:\tmp\foo' $dest = 'c:\temp\foo' $exclude = '\.bak' Get-ChildItem $source -Recurse | where {$_.FullName -notmatch $exclude} | Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}
как код формата комментариев плохо я опубликую в качестве ответа, но это просто дополнение к ответу @landyman. Предлагаемый скрипт имеет недостаток-он будет создавать двойные вложенные папки. Например, для 'd:\t1\sub1' он создаст пустой каталог 'd:\t2\sub1\sub1'. Это связано с тем, что Copy-Item для каталогов ожидает имя родительского каталога в свойстве назначения, а не само имя каталога. Вот обходной путь, который я нашел:
Get-ChildItem -Path $from -Recurse -Exclude $exclude | Copy-Item -Force -Destination { if ($_.GetType() -eq [System.IO.FileInfo]) { Join-Path $to $_.FullName.Substring($from.length) } else { Join-Path $to $_.Parent.FullName.Substring($from.length) } }
у меня тоже была эта проблема, и я потратил 20 минут на применение решений здесь, но продолжал иметь проблемы.
поэтому я решил использовать robocopy-хорошо, это не powershell, но должно быть доступно везде, где работает powershell.и это сработало прямо из коробки:
robocopy $source $dest /S /XF <file patterns to exclude> /XD <directory patterns to exclude>например
robocopy $source $dest /S /XF *.csproj /XD obj Properties Controllers Modelsкроме того, он имеет множество функций, таких как возобновляемые копия. Документы здесь.
обратите внимание, что синтаксическая спецификация вызывает массив строк; ala String[]
SYNTAX Copy-Item [[-Destination] <String>] [-Confirm] [-Container] [-Credential <PSCredential>] [-Exclude <String[]>] [-Filter <String>] [-Force] [-FromSession <PSSession>] [-Include <String[]>] -LiteralPath <String[]> [-PassThru] [-Recurse] [-ToSession <PSSession>] [-UseTransaction] [-WhatIf] [<CommonParameters>]если вы не явны в своем поколении массива, вы в конечном итоге получаете объект[] - и это игнорируется во многих случаях, оставляя внешний вид "багги-поведения" Из-за безопасности типа. Поскольку PowerShell может обрабатывать блоки сценариев, оценка другой переменной, отличной от конкретной для типа (чтобы можно было определить допустимую строку), оставила бы отверстие для потенциальной атаки режима впрыска на любой система, политика выполнения которой была слабой.
так что это ненадежно:
PS > $omissions = @("*.iso","*.pdf","*.zip","*.msi") PS > $omissions.GetType() Note the result.... IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Arrayи это работает.... например:
PS > $omissions = [string[]]@("*.iso","*.pdf","*.zip","*.msi") **or** PS > [string[]]$omissions = ("*.iso,*.pdf,*.zip,*.msi").split(',') PS > $omissions.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String[] System.Arrayобратите внимание, что даже "один" элемент все равно потребует того же приведения, чтобы создать массив из 1 элемента.
если вы пытаетесь сделать это дома, обязательно используйте переменную Replace-Variable "пропуски", чтобы очистить существование $пропусков перед повторной обработкой в примерах, показанных выше.
А что касается трубопровода это работает надежно, что я проверил....
--------------------------------------------------------------------------------------- cd $sourcelocation ls | ?{$_ -ne $null} | ?{$_.BaseName -notmatch "^\.$"} | %{$_.Name} | cp -Destination $targetDir -Exclude $omissions -recurse -ErrorAction silentlycontinue ---------------------------------------------------------------------------------------выше перечисляет каталог исходных файлов в базовом (выбранном "текущем") каталоге, отфильтровывает потенциальные проблемные элементы, преобразует файл в базовое имя и заставляет cp (псевдоним элемента копирования) повторно обращаться к файлу "по имени" в "текущем каталоге"-таким образом, повторно запрашивая объект файла и копирует его. Это создаст пустые каталоги, в том числе те, которые могут даже содержать исключенные файлы (за вычетом исключений курс.) Обратите внимание также, что "ls" (get-childitem) не является рекурсивным-то есть оставленным для cp. Наконец , если у вас возникли проблемы и вам нужно отладить, удалите переключатель-ErrorAction silentlycontinue и аргумент, который скрывает множество неприятностей, которые могут прервать сценарий в противном случае.
для тех, чьи комментарии были связаны с включениями"\", имейте в виду, что вы работаете над подуровнем .NET через интерпретатор (т. е. PowerShell), а в c#, например, включение одного "\" (или несколько синглов в строке), приводит к тому, что компилятор требует, чтобы вы исправили условие, используя либо"\\", чтобы избежать обратной косой черты, либо перед строкой с @ как в @"\"; с другой оставшейся опцией, являющейся вложением строки в одинарные кавычки, как '\'. Все это из-за ASCII интерполяции комбинаций символов, таких как "\n" и т. д.
последнее-гораздо более важная тема, поэтому я оставлю вас с этим соображением.
Я искал способ скопировать файлы, измененные после определенной даты / метки времени, чтобы архивировать их. Таким образом, я мог бы сохранить именно те файлы, над которыми я работал (предполагая, что я знаю, когда я начал). (Да, я знаю, что это то, для чего предназначен SCM, но бывают случаи, когда я просто хочу сделать снимок своей работы, не проверяя ее.)
используя наконечник landyman и вещи, которые я нашел в другом месте, я обнаружил, что это сработало:
$source = 'c:\tmp\foo' $dest = 'c:\temp\foo' $exclude = @('*.pdb', '*.config') Get-ChildItem $source -Recurse -Exclude $exclude | where-object {$_.lastwritetime -gt "8/24/2011 10:26 pm"} | Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}
Get-ChildItem с Join-Path работал в основном для меня, но я понял, что он копирует корневые каталоги внутри других корневых каталогов, что было плохо.
- c:\SomeFolder
- c:\SomeFolder\CopyInHere
- c:\SomeFolder\CopyInHere\Thing.txt
- c:\SomeFolder\CopyInHere\SubFolder
c:\SomeFolder\CopyInHere\SubFolder\Thin2.txt
Исходный Каталог: c:\SomeFolder\CopyInHere
- каталог назначения: d:\PutItInHere
цель: Скопируйте каждый childitem внутри c:\SomeFolder\CopyInHere к корню d:\PutItInHere, но не включая c:\SomeFolder\CopyInHere сам по себе.
- Например, возьмите всех детей CopyInHere и сделайте их детьми PutItInHereприведенные выше примеры делают это большую часть пути, но что происходит, это создает папку, называемую Подпапкой, и создает папку в папке называется подпапка.
это потому, что Join-Path вычисляет путь назначения d:\PutItInHere\SubFolder для дочернего элемента подпапки, поэтому подпапка get создается в папке с именем подпапка.
Я обошел это с помощью Get-ChildItems, чтобы вернуть коллекцию элементов, а затем с помощью цикла, чтобы пройти через него.
Param( [Parameter(Mandatory=$True,Position=1)][string]$sourceDirectory, [Parameter(Mandatory=$True,Position=2)][string]$destinationDirectory ) $sourceDI = [System.IO.DirectoryInfo]$sourceDirectory $destinationDI = [System.IO.DirectoryInfo]$destinationDirectory $itemsToCopy = Get-ChildItem $sourceDirectory -Recurse -Exclude @('*.cs', 'Views\Mimicry\*') foreach ($item in $itemsToCopy){ $subPath = $item.FullName.Substring($sourceDI.FullName.Length) $destination = Join-Path $destinationDirectory $subPath if ($item -is [System.IO.DirectoryInfo]){ $itemDI = [System.IO.DirectoryInfo]$item if ($itemDI.Parent.FullName.TrimEnd("\") -eq $sourceDI.FullName.TrimEnd("\")){ $destination = $destinationDI.FullName } } $itemOutput = New-Object PSObject $itemOutput | Add-Member -Type NoteProperty -Name Source -Value $item.FullName $itemOutput | Add-Member -Type NoteProperty -Name Destination -Value $destination $itemOutput | Format-List Copy-Item -Path $item.FullName -Destination $destination -Force }что это делает вкратце, это использует полное имя текущего элемента для расчета назначения. Однако затем он проверяет если это объект DirectoryInfo. Если это он проверяет, является ли его родительская папка исходным каталогом, это означает, что текущая папка, которая повторяется, является прямым дочерним элементом исходного каталога, поэтому мы не должны добавлять его имя в каталог назначения, потому что мы хотим, чтобы эта папка была создана в каталоге назначения, а не в папке его в каталоге назначения.
после этого все остальные папки будут работать нормально.
$sourcePath="I:\MSSQL\Backup\Full" $excludedFiles=@("MASTER", "DBA", "MODEL", "MSDB") $sourceFiles=(ls $sourcePath -recurse -file) | where-object { $_.directory.name -notin $excludedFiles }это то, что я сделал, мне нужно было скопировать кучу резервных файлов в отдельное место в сети для получения клиента. мы не хотели, чтобы у них были вышеуказанные резервные копии системных БД.
у меня была аналогичная проблема, расширяющая это немного. Я хочу, чтобы решение работало для таких источников, как
$source = "D:\scripts\*.sql"тоже. Я нашел это решение:
function Copy-ToCreateFolder { param( [string]$src, [string]$dest, $exclude, [switch]$Recurse ) # The problem with Copy-Item -Rec -Exclude is that -exclude effects only top-level files # Copy-Item $src $dest -Exclude $exclude -EA silentlycontinue -Recurse:$recurse # http://stackoverflow.com/questions/731752/exclude-list-in-powershell-copy-item-does-not-appear-to-be-working if (Test-Path($src)) { # Nonstandard: I create destination directories on the fly [void](New-Item $dest -itemtype directory -EA silentlycontinue ) Get-ChildItem -Path $src -Force -exclude $exclude | % { if ($_.psIsContainer) { if ($Recurse) # Non-standard: I don't want to copy empty directories { $sub = $_ $p = Split-path $sub $currentfolder = Split-Path $sub -leaf #Get-ChildItem $_ -rec -name -exclude $exclude -Force | % { "{0} {1}" -f $p, "$currentfolder$_" } [void](New-item $dest$currentfolder -type directory -ea silentlycontinue) Get-ChildItem $_ -Recurse:$Recurse -name -exclude $exclude -Force | % { Copy-item $sub$_ $dest$currentfolder$_ } } } else { #"{0} {1}" -f (split-path $_.fullname), (split-path $_.fullname -leaf) Copy-Item $_ $dest } } } }
Comments