Windows: File copy/move with filename regular expressions?

i basically want to run:

C:\>xcopy [0-9]{13}\.(gif|jpg|png) s:\TargetFolder /s

i know xcopy doesn't support regular-expression filename searches.

i can't find out how to find out if PowerShell has a Cmdlet to copy files; and if it does, how to find out if it supports regular expression filename matching.

Can anyone think of a way to perform a recursive file copy/move with regex filename matching?


Solution 1:

I like using all Powershell commands when I can. After a bit of testing, this is the best I can do.

$source = "C:\test" 
$destination = "C:\test2" 
$filter = [regex] "^[0-9]{6}\.(jpg|gif)"

$bin = Get-ChildItem -Path $source | Where-Object {$_.Name -match $filter} 
foreach ($item in $bin) {Copy-Item -Path $item.FullName -Destination $destination}

The first three lines are just to make this easier to read, you can define the variables inside the actual commands if you want. The key to this code sample is the the "Where-Object" command which is a filter that accepts regular expression matching. It should be noted that regular expression support is a little weird. I found a PDF reference card here that has the supported characters on the left side.

[EDIT]

As "@Johannes Rössel" mentioned, you can also reduce the last two lines down to a single line.

((Get-ChildItem -Path $source) -match $filter) | Copy-Item -Destination $destination

The main difference is that Johannes's way does object filtering and my way does text filtering. When working with Powershell, it's almost always better to use objects.

[EDIT2]

As @smoknheap mentioned, the above scripts will flatten out the folder structure and put all your files in one folder. I'm not sure if there is a switch that retains folder structure. I tried the -Recurse switch and it doesn't help. The only way I got this to work is to go back to string manipulation and add in folders to my filter.

$bin = Get-ChildItem -Path $source -Recurse | Where-Object {($_.Name -match $filter) -or ($_.PSIsContainer)}
foreach ($item in $bin) {
    Copy-Item -Path $item.FullName -Destination $item.FullName.ToString().Replace($source,$destination).Replace($item.Name,"")
    }

I'm sure that there is a more elegant way to do this, but from my tests it works. It gather s everything and then filters for both name matches and folder objects. I had to use the ToString() method to gain access to the string manipulation.

[EDIT3]

Now if you want to report the pathing to make sure you have everything correct. You can use the "Write-Host" Command. Here's the code that will give you some hints as to what's going on.

cls
$source = "C:\test" 
$destination = "C:\test2" 
$filter = [regex] "^[0-9]{6}\.(jpg|gif)"

$bin = Get-ChildItem -Path $source -Recurse | Where-Object {($_.Name -match $filter) -or ($_.PSIsContainer)}
foreach ($item in $bin) {
    Write-Host "
----
Obj: $item
Path: "$item.fullname"
Destination: "$item.FullName.ToString().Replace($source,$destination).Replace($item.Name,"")
    Copy-Item -Path $item.FullName -Destination $item.FullName.ToString().Replace($source,$destination).Replace($item.Name,"")
    }

This should return the relevant strings. If you get nothing somewhere, you'll know what item is having problems with.

Hope this helps

Solution 2:

PowerShell is an excellent tool for that task. You can use Copy-Item cmdlet for the copying process. You can pipeline it with other cmdlets for complex copy commands, here is someone that did exactly that :)

Regular expressions use the .NET RegEx class from the System.Text.RegularExpressions namespace, there are quick how-to on these class

PowerShell also have the -match and -replace operators that can be used when pipelining with copy-item

There are also tools to help you create the RegEx itself, e.g. RegEx buddy