I am trying to write functions to rename Paths and Files.
I want to add or remove to the directory and file names (excluding the file extensions) at the end or start of the name certain identifiers.
So I wrote these functions:
##################################
# Rename Files and Directories
##################################
function Ensure-String-End ($string, $ending) {
# Ensure that $string ends with $ending.
if ($string -match $(-join('', ".*", $ending, '$'))) {
return $string
} else {
return $(-join('', $string, $ending))
}
}
function Ensure-String-Start ($string, $start) {
# Ensure that $string starts with the $start string.
if ($string -match $(-join('', '^', $start, '.*$'))) {
return $string
} else {
return $(-join('', $start, $string))
}
}
function Remove-String-End ($string, $ending) {
# Remove $ending string from end of $string
return $string -replace "$ending$", ''
}
function Remove-String-Start ($string, $start) {
# Remove $start string from start of $string
return $string -replace "^$start", ''
}
function Ensure-Filename-End ($filename, $ending) {
# Similar to Ensure-String-End just for file names - The file extension should stay.
# But at the end of the filename without extension the $ending should be ensured.
$path = Get-Item $filename
return -join('', $path.DirectoryName, '\', $(Ensure-String-End $path.Basename $ending), $path.Extension)
}
function Ensure-Filename-Start ($path, $start) {
# Filename should start with $start string.
$path = Get-Item $path
return -join('', $path.DirectoryName, '\', $(Ensure-String-Start $path.Basename $start), $path.Extension)
}
function Remove-Filename-End ($filename, $ending) {
# Remove from filename end the $ending (file extension should stay)
$path = Get-Item $filename
return -join('', $path.DirectoryName, '\', $(Remove-String-End $path.Basename $ending), $path.Extension)
}
function Remove-Filename-Start ($path, $start) {
# Remove from file name's start the $start string. Rest of the path should be invariant.
$path = Get-Item $path
return -join('', $path.DirectoryName, '\', $(Remove-String-Start $path.Basename $start), $path.Extension)
}
function Ensure-Directories-Ending ($path, $ending) {
# Make directories end with $ending and rename (`mv`) the directories.
Get-ChildItem -Path $path -Directory | %{$_.FullName} |
ForEach-Object {
$new_name = Ensure-String-End $_ $ending
if ($new_name -ne $_) {
echo "Renaming $_ to $new_name"
Rename-Item -Path $_ -NewName $new_name
}
}
}
function Ensure-Files-Ending ($path, $ending) {
# `mv` the file names, ensuring they end with $ending - while file extension is kept.
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
$new_name = Ensure-Filename-End $_ $ending
if ($new_name -ne $_) {
echo "Renaming $_ to $new_name"
Rename-Item -Path $_ -NewName $new_name
}
}
}
function Ensure-Directories-and-Files-Ending ($path, $ending) {
# Recursively add to all directory and file names the $ending if they don't end already by it.
Ensure-Directories-Ending $path $ending
Ensure-Files-Ending $path $ending
}
function Remove-Directories-Ending ($path, $ending) {
# Rename directories so that if they end with $ending this $ending is removed.
Get-ChildItem -Path $path -Directory |
ForEach-Object {
$dir_path = %{$_.FullName}
$new_name = Remove-String-End $dir_path $ending
if ($new_name -ne $dir_path) {
echo "Renaming $dir_path to $new_name"
Rename-Item -Path $dir_path -NewName $new_name
}
}
}
function Remove-Files-Ending ($path, $ending) {
# Remove $ending from File names - rename them in this system.
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
$new_name = Remove-Filename-End $_ $ending
if ($new_name -ne $_) {
echo "Renaming $_ to $new_name"
Rename-Item -Path $_ -NewName $new_name
}
}
}
function Remove-Directories-and-Files-Ending ($path, $ending) {
# Recursively remove from ending of all Folders and Files' names the $ending string.
$(Remove-Directories-Ending $path $ending)
$(Remove-Files-Ending $path $ending)
}
Generate a test folder:
New-Item .\Desktop\test\a -type Directory
New-Item .\Desktop\test\b -type Directory
New-Item .\Desktop\test\c -type Directory
New-Item .\Desktop\test\a\a.txt -type File
New-Item .\Desktop\test\a\b.txt -type File
New-Item .\Desktop\test\a\c.txt -type File
New-Item .\Desktop\test\b\a.txt -type File
New-Item .\Desktop\test\b\b.txt -type File
New-Item .\Desktop\test\b\c.txt -type File
New-Item .\Desktop\test\c\a.txt -type File
New-Item .\Desktop\test\c\b.txt -type File
New-Item .\Desktop\test\c\c.txt -type File
Add to each end of folders and files a certain ending:
Ensure-Directories-and-Files-Ending $HOME\Desktop\test "_test"
And remove them again:
Remove-Directories-and-Files-Ending $HOME\Desktop\test "_test"
there appear:
Rename-Item : Cannot rename the specified target, because it represents a path or device name.
Errors/Warnings.
How can I avoid them?
Thank you everybody for your inputs. Considering them, I came to the conclusion that writing my own little functions for path handling would be helpful for my matter.
function PathLast ($path) {
return $path.split('\')[-1]
}
function PathExt ($path) {
$last = PathLast $path
if ($last.Contains('.')) {
return ".$($last.split('.')[-1])"
} else {
return ''
}
}
function PathBase ($path) {
$last = PathLast $path
return $last -replace "$(PathExt $path)$", ''
}
function PathDir ($path) {
$last = PathLast $path
return $($path -replace "$last$", '') -replace '\\$', ''
}
# Renaming Paths should test, whether the absolute names are identical
# if identical, no renaming is performed.
function Rename-Path ($path, $new_path) {
if ($path -ne $new_path) {
Rename-Item -Path $path -NewName $new_path
}
}
# Using those, the renaming functions were written.
# Each of them work both for files and directories/folders as well.
function Ensure-End ($path, $ending) {
$dir = PathDir $path
$base = PathBase $path
$ext = PathExt $path
echo "$dir\\$base$ext"
if (-not $base.EndsWith($ending)) {
Rename-Path $path "$dir\\$base$ending$ext"
}
}
function Ensure-Start ($path, $start) {
$dir = PathDir $path
$base = PathBase $path
$ext = PathExt $path
if (-not $base.StartsWith($start)) {
Rename-Path $path "$dir\\$start$base$ext"
}
}
function Remove-End ($path, $ending) {
$dir = PathDir $path
$base = PathBase $path
$ext = PathExt $path
if ($base.EndsWith($ending)) {
Rename-Path $path "$dir\\$($base -replace "$ending$", '')$ext"
}
}
function Remove-Start ($path, $start) {
$dir = PathDir $path
$base = PathBase $path
$ext = PathExt $path
if ($base.StartsWith($start)) {
Rename-Path $path "$dir\\$($base -replace "^$start", '')$ext"
}
}
# The following functions are like the previous ones,
# just recursively applying the renamings on all children
# in the path-tree.
function Ensure-End-All ($path, $ending) {
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
Ensure-End $_ $ending
}
}
function Ensure-Start-All ($path, $start) {
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
Ensure-Start $_ $start
}
}
function Remove-End-All ($path, $ending) {
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
Remove-End $_ $ending
}
}
function Remove-Start-All ($path, $start) {
Get-ChildItem -Path $path -Recurse | %{$_.FullName} |
ForEach-Object {
Remove-Start $_ $start
}
}
# Ensure-End-All .\Desktop\test "_test"
# Remove-End-All .\Desktop\test "_test"
# Ensure-Start-All .\Desktop\test "test_"
# Remove-Start-All .\Desktop\test "test_"
The Rename-Item
cmdlet's -NewName
parameter truly only accepts a new name for the input file-system item, not a path:
By design and invariably, Rename-Item
renames a file or directory (or other PowerShell provider item) in its current location.
If, by contrast, you want to rename and also move the item to a different location, you must use the Move-Item
cmdlet.
You can easily provoke the error as follows:
PS> Get-Item $PROFILE | Rename-Item -NewName "$HOME\NewName" -WhatIf
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Note that, as a courtesy, Rename-Item
does accept a path in two cases:
If the -NewName
argument is prefixed by verbatim relative path .\
(or ./
).
For instance, the following two calls are equivalent:
Rename-Item -LiteralPath c:\path\to\foo.txt -NewName bar.txt
Rename-Item -LiteralPath c:\path\to\foo.txt -NewName .\bar.txt
Do note that .
in this context does not refer to the current directory, but to that of the input file.
Curiously, the input item's current, full path is also accepted, but, given that you cannot rename an item to itself, this has the following effect: