76

I'm trying to do the following:

Rename-Item c:\misc\*.xml *.tmp 

I basically want to change the extension on every files within a directory to .tmp instead of .xml. I can't seem to find a straight forward way to do this in PowerShell.

1
  • 3
    And that's why linux is better Commented Sep 1, 2023 at 6:55

8 Answers 8

130

From example 4 in the help documentation of Rename-Item retrieved with the command:

get-help Rename-Item -examples 

Example:

Get-ChildItem *.txt| Rename-Item -NewName { $_.Name -replace '\.txt','.log' } 

Note the explanation in the help documentation for the escaping backslash in the replace command due to it using regular expressions to find the text to replace.

To ensure the regex -replace operator matches only an extension at the end of the string, include the regex end-of-string character $.

Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace '\.txt$','.log' } 

This takes care of the case mentioned by @OhadSchneider in the comments, where we might have a file named lorem.txt.txt and we want to end up with lorem.txt.log rather than lorem.log.log.

Now that the regex is sufficiently tightly targeted, and inspired by @etoxin's answer, we could make the command more usable as follows:

Get-ChildItem | Rename-Item -NewName { $_.Name -replace '\.txt$','.log' } 

That is, there is no need to filter before the pipe if our regex sufficiently filters after the pipe. And altering the command string (e.g. if you copy the above command and now want to use it to change the extension of '.xml' files) is no longer required in two places.

Sign up to request clarification or add additional context in comments.

4 Comments

if you specify the -WhatIf argument to Rename-Item it shows the output of the operation without actually renaming anything, which is very useful
That is actually a bad example in the docs IMO. This code is not equivalent to ren *.xml *.tmp (hint: a.xml.xml). You want System.IO.Path.ChangeExtension: stackoverflow.com/a/36241702/67824.
@OhadSchneider no longer the case since answer was expanded in 2020. Upvoted your answer anyways.
To recurse into subdirectories, do: Get-ChildItem -Recurse *.js| Rename-Item -NewName { $_.Name -replace '\.js','.ts' }
21

This works well too when you're in the desired directory.

Dir | Rename-Item –NewName { $_.name –replace "old","new" } 

2 Comments

In powershell dir is just an alias for Get-ChildItem. gci is also an alias (and somewhat more "powershell correct") . Run Get-Alias -Definition Get-ChildItem to see this. In any case, your example shows that there's really no need to filter before the pipe, if your -replace command sufficiently restricts the "old" value (which your example will not, if you only want target the extension).
Getting error rename-item: source and destination path must be different. That doesn't make any sense. But it still performed the rename????
21

The existing answers suggest the -replace operator, but what if the file is called a.xml.xml? Both .xml substrings will be replaced and the end result would be a.tmp.tmp. Fortunately, there's a .NET method for this:

Dir *.xml | rename-item -newname { [io.path]::ChangeExtension($_.name, ".tmp") } 

(Manish Kumar was close with GetFileNameWithoutExtension but this is more elegant and probably a bit more efficient, not that it overly matters in this case)

1 Comment

Also works for files with no extension at all. Nice!!
6

Here's another variant that will work.

dir *.xml | Rename-Item -NewName {$_.BaseName + ".tmp"} 

$_.BaseName will do the "base" name without the (last) extension.

1 Comment

Two things to note: dir is just an alias for Get-ChildItem; and the filter is using wildcards (which is probably desirable in this context as MyXmlReference.txt won't match) rather than regexes. learn.microsoft.com/en-us/powershell/module/…. In any case the use of $_.BaseName is a clever alternative.
6

a shortened version using the alias would be:

ls *.xml | ren -new {$_.BaseName + ".tmp"} 

1 Comment

learn.microsoft.com/en-us/powershell/scripting/learn/ps101/… "Aliases can save you a few keystrokes and they're fine when you're typing commands into the console. They shouldn't be used in scripts or any code that you're saving or sharing with others. As mentioned earlier in this book, using full cmdlet and parameter names is self-documenting and easier to understand."
2
dir -Recurse | where-object -FilterScript {$_.Extension -eq ".xml"} | Rename-Item -NewName {[System.IO.Path]::GetFileNameWithoutExtension($_.fullname) + ".tmp"} 

use -WhatIf to evaluate the result first

Comments

2

Even easier - remember that the replace search string is a regular expression,

dir *.xml | rename-item -newname {$_.name -replace "xml$","tmp"} 

The "$" represents end-of-string, so the characters "xml" must be the last three chars of the filename.

Comments

1

This seems to work and is a pythonic i.e simple is better than complex (https://www.python.org/dev/peps/pep-0020/) way of doing it (once you are in the directory):

$files = Get-ChildItem -file -Filter *.xml; ForEach ($file in $files) { $n = $file.Basename Copy-Item -Path $file -Destination "$n.tmp" Remove-Item "$n.xml" } 

1 Comment

Depends on your definition of simple and complex. The other solutions all involve changing the names of the files; this one actual will create copies of each file with the new name, then delete the original. In the real world, this could take hours to complete, rather than minutes in the other examples. It could also fail in cases where the files to be renamed are large, and little free space is available.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.