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
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.
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
}
One way of doing it. What this does is:
Groups the files in the directory by the user's name.
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 }
}