Weird behaviour in PowerShell bulk file renaming
Asked Answered
C

3

5

I'm new to PowerShell and recently I try to rename 120 files in a folder and I encounter a weird behavior.

What I have is files with named from 0001.txt, 0002.txt, ... 0120.txt a total of 120 files. I want to add an 'a' in front of each of the file name. I came up with this command:

ls | ren -newname {$_.name -replace '(\d+)','a$1'}

But after execution, I get numerous error like this:

"The specified path, file name, or both are too long. The fully qualified file name must be less than 260"

And when I look into my folder, all I get are file names from

aaaaaaaaaaaaaaaaaaaaaaaaa(repeat until it hit system limit on path length)....a0001.txt
...
...
...
aaaaaaaaaaaaaaaaaaaaaaaaa(repeat until it hit system limit on path length)....a0120.txt

Upon further inspection using the -cf switch, it turns out that PowerShell attempts the renaming process recursively. After the first pass of renaming all the 120 files, it went on again applying the command to a0001.txt effectively adding another 'a' in front of the filename. And this went on until it hit the path length limit and reported the error.

Can anyone tell me if there is anything wrong in my renaming command?

Coffee answered 18/8, 2013 at 16:32 Comment(0)
C
5

Pipe to Foreach-Object (shorthand %{ }) to mass-rename files. If you just want to prepend the letter a to each filename, you don't need a regex, all you need to do is this:

Get-ChildItem | %{Rename-Item $_ ('a' + $_.name)}

Using your preferred aliases:

ls | %{ren $_ ('a' + $_.name)}

To do it with a regex, it's simpler to do use ($_.name -replace '^','a'). If the reason for using a regex is that you have other files in the directory and you only want to rename files named with a string of digits and the .txt extension, note that the regex you're using would prepend an a to any string of consecutive digits. For example, agent007.txt would be renamed to agenta007.txt. You should have the regex match only the format you want: '^(\d+)\.txt'.

Also note that by using a regex replace in the -NewName argument, you're renaming each file that doesn't match the regex to the same name that it already has. Not a big deal for 120 files, but I'd filter the file listing in advance:

Get-ChildItem | ?{$_ -match '^(\d+)\.txt'} | %{Rename-Item $_ ('a' + $_.name)}

UPDATE: There appears to be a bug in PowerShell 3.0 that can cause bulk file renaming to fail under certain conditions. If the files are renamed by piping a directory listing to Rename-Item, any file that's renamed to something that's alphabetically higher than its current name is reprocessed by its new name as it's encountered later in the directory listing. This can cause the same files to be repeatedly renamed until the length of the full path exceeds the 260 character maximum and an error is thrown.

Note, for example, that if the letter a is appended rather then prepended, there is no problem. This works for any number of files:

Get-ChildItem | %{Rename-Item $_ ($_.name + 'a')}

If the files are named b0001, b0002, etc., and the letter a is prepended, the repeated reprocessing does not occur. However, if the letter c is prepended, it does occur.

The following command adds a single a to the beginning of any number of files if all their names begin with with b:

Get-ChildItem | %{Rename-Item $_ ('a' + $_.name)}

Given the same files, with names beginning with b, the following command prepends the letter c repeatedly until the length limit is reached:

Get-ChildItem | %{Rename-Item $_ ('c' + $_.name)}

This only happens for a listing containing a certain number of files or higher. The OP said that he doesn't have a problem if he reduces the number of files to less than 20. I found that the threshold is 37 (with 36 files or less, the reprocessing doesn't happen). I haven't determined yet whether the number is the same for all applicable cases or depends on more specific factors.

I'll elaborate on this later, and submit a bug report to Microsoft once I've determined the parameters of this problem more specifically.

WORKAROUND: Filter the listing from Get-ChildItem with Where-Object such that the new filenames are excluded. The last version of the command above before the update section works in PowerShell 3.0. Presumably, the renamed files are reintroduced into the pipeline by their new names and processed one more time, but because the new names don't match the filter ?{$_ -match '^(\d+)\.txt'}, they are not renamed on their second pass through the pipeline and not reprocessed again.

Chaffin answered 18/8, 2013 at 16:52 Comment(6)
Thanks for your answer, your explanation is very informative for a newbie like me. Your command did work (so as mine) on PowerShell version 2. But it gave the same behavior on version 3 of PowerShell. Can I know which version of PowerShell are you using? And in version 3, this strange behavior doesn't exhibit if I reduce the files in the folder to 20 files. I might have just found a bug in PowerShell or I have a faulty ram chipCoffee
Wow, you're right! This is a bug in PS 3. I tested in both versions, but only with a small number of files. With a larger number, what appears to be happening is that if a file is renamed to something that's alphabetically higher, it's processed again by its new name - which happens repeatedly with the same files in a case like this. Too long for a comment, but I'll update my answer, and submit a bug report to Microsoft. For now, I can tell you that the last version in my answer, with the where filter, does work, because the new names don't match the pattern so they're not reprocessed.Chaffin
Thanks for your feedback and lengthy update. Feel good to be the first to discover a bug. :) Just wonder why this bug hasn't been brought out already.Coffee
Yesterday I was just arbitrary reduce the files number to 20 and discover no error. It is by no means the threshold in my case. However, I just did a quick test and found that if I have 36 files in the folder, the renamed files will be aa0001.txt up to aa0035.txt (meaning it stop at second iteration), but the 36th file, was renamed correctly to a0036.txt. If I only have 35 files in the folder, everything work flawlessly. 37 files will have the same exact error in my opening post.Coffee
Thanks for the information on the PowerShell bug and its workaround, was driving me crazy!Unaccountable
I find the same issue in Powershell 5.1 ! But I find it only happens when you have more than 110 files to rename and you use the -Filter parameter in Get-ChildItem command. If you use -Path instead of -Filter, issue will be solved... Example: Get-ChildItem -FILTER *.xlsx | foreach { Rename-Item -path $_.FullName -NewName ($_.Name -Replace '_2019','_ULVR_2019' ) } triggers the bug, but Get-ChildItem *.xlsx | foreach { Rename-Item -path $_.FullName -NewName ($_.Name -Replace '_2019','_ULVR_2019' ) } does not.Uird
B
1

I would suggest a simple workaround, which worked fine for me without any additional regexes and filtering, with any files number

$files = Get-ChildItem; $files | ForEach-Object {Rename-Item $_ ('a' + $_.Name)}
Byrd answered 21/2, 2020 at 13:5 Comment(0)
O
1

Or use parentheses around ls or get-childitem so that it completes first. This issue is resolved in powershell 6.

(ls) | ren -newname {$_.name -replace '(\d+)','a$1'}

Here's another way to prefix an 'a' to a filename:

(ls) | ren -newname { $_.name -replace '^','a' } -whatif

Or with the ps 6 -replace (then the parentheses aren't needed):

ls | ren -newname { $_.name -replace '.+',{"a$_"} } -whatif
Ozenfant answered 21/2, 2020 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.