File and Folder Manipulation in Powershell

2019-07-24 18:24发布

问题:

I have a folder of thousands of filenames.

All filenames are relative to a user's userID. Some users have multiple files in this folder, so it appends a number onto the end of the name. I.E. susy.txt, susy1.txt, susy2.txt, carl.txt, carl1.txt, etc...

What I am trying to do is create a specific folder for each user that has multiple files, and move all associated files into that folder. So I notice there are multiple susy documents. So I want to create a folder named Susy and place susy.txt, susy1.txt, and susy2.txt into it... And so on for all files.

Is it even possible to do this as a batch file, if so can someone point me in the correct direction on doing this? I have a small amount of knowledge in writing batch scripts, and would like to take this as an opportunity to learn more.

Thanks

回答1:

Here's a simpler/more compact way of doing it:

Get-ChildItem | ?{$_.Name -match '(\D+)\d*\.txt'} | %{
  md $matches[1] -ea SilentlyContinue 
  Move-Item $_ $matches[1]
}

A purist might say that it's "better practice" to Test-Path before attempting to create the directory, but I generally prefer more compact code; if no other decisions hinge on whether the directory already exists, it's quicker to just attempt to create it each time and ignore errors.


A couple of explanatory notes, in response to the OP's comment:

  • ?{ } is shorthand for Where-Object, which filters on a condition, i.e. only objects that meet the specified condition are passed down the pipeline. So in this case, it takes the listing of the current directory and selects only the objects whose Name property matches the regex.
  • After the -match operator is used, the matches are stored in the automatic array variable $matches. $matches[0] contains the entire matched part of the string, and indices starting with 1 contain the match groups, numbered from the ordinal position of the left parenthesis, i.e. $matches[1] is the match group beginning with the first left parenthesis encountered, $matches[2] is the match group beginning with the second left parenthesis, etc.

    In this case there is only one match group, and $matches[1] contains the part of the string matched by (\D+), i.e. the sequence of consecutive non-digit characters prior to the first digit or dot.



回答2:

Here is my quick-and-dirty just trumped up solution. It uses a regular expression to split the users name out of the filename, i.e. the expression will split carl out of carl1.txt carl3.txt carl22.txt. It then creates folders from those names, uniquely. So it only tries to create carl once, not 57 times. After that, it moves all the carl and johnny and susy etc., into the new folders.

If you're new, don't be afraid if this seems a little intimidating. Especially regular expressions. There's almost certainly a way to do this without them, if they're confusing to you, but regular expressions are one of those tools that once you learn it you're aware of just how great it is at solving things.

[regex]$pattern_name = '^\w.+?(?=[\d\.])'

# create folders for all the unique users
get-Childitem -File -path 'c:\scripts' | foreach-Object {$pattern_name.match($_).value } | sort -unique | foreach-Object { mkdir $_ }

# move files into those folders
get-Childitem -File -path 'c:\scripts' | foreach-Object {
    $user = $pattern_name.match($_).value 
    Move-Item -destination ".\$user" -Path $_.FullName
    }


回答3:

One way of doing it. What this does is:

  1. Groups the files in the directory by the user's name.

  2. If the group has more than one file it creates a directory for the user and then moves the user's files into that directory.

(Note that I added the -WhatIf option to all commands that would modify data - you will have to remove them to actually modify the data.)

# Make backup for safety
cp -recurse .\userFiles .\userFilesBackup

cd .\userFiles

gci | `

select-object @{n='User';e={$_.BaseName -replace "^([a-z]+)[\d]*$", '$1'}}, Name | `

group-object User | `

? { $_.Count -gt 1 } | `

% { `

$userDirectory = $_.Name
mkdir -WhatIf $userDirectory

foreach ($f in $_.Group)
{ mv -WhatIf $f.Name $userDirectory }

}