Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird behaviour in PowerShell bulk file renaming

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?

like image 590
L Z Avatar asked Aug 18 '13 16:08

L Z


3 Answers

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.

like image 56
Adi Inbar Avatar answered Oct 21 '22 12:10

Adi Inbar


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)}
like image 28
Pavel T Avatar answered Oct 21 '22 11:10

Pavel T


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
like image 44
js2010 Avatar answered Oct 21 '22 12:10

js2010