路径重命名的函数调用的奇怪行为

Strange behavior of Function call for Path renaming

我正在尝试编写函数来重命名路径和文件。

我想在名称的末尾或开头添加或删除目录和文件名(不包括文件扩展名)某些标识符。

所以我写了这些函数:

##################################
# 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)
}

生成测试文件夹:

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

在文件夹和文件的每一端添加一个特定的结尾:

Ensure-Directories-and-Files-Ending $HOME\Desktop\test "_test"

并再次删除它们:

Remove-Directories-and-Files-Ending $HOME\Desktop\test "_test"

出现了:

Rename-Item : Cannot rename the specified target, because it represents a path or device name.

Errors/Warnings.

我怎样才能避免它们?

我的最终解决方案

谢谢大家的意见。考虑到它们,我得出的结论是,编写自己的路径处理小函数对我的事情会有所帮助。


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_"

Rename-Item cmdlet 的 -NewName 参数实际上只接受新的 name输入 file-system 项目,而不是 path:

  • 根据设计,Rename-Item 会在其当前位置重命名文件或目录(或其他 PowerShell provider 项目) .

  • 相反,如果您想重命名 并将项目移动到不同的位置,则必须使用 Move-Item 命令。


您很容易引发如下错误:

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.

请注意,出于礼貌,Rename-Item 在两种情况下接受路径:

  • 如果-NewName参数以逐字相对路径为前缀.\(或./)。

    • 例如,下面两个调用是等价的:

      Rename-Item -LiteralPath c:\path\to\foo.txt -NewName bar.txt
      Rename-Item -LiteralPath c:\path\to\foo.txt -NewName .\bar.txt
      
    • 请注意,在此上下文中 . 不是 指的是 当前 目录,但是到输入文件的那个。

  • 奇怪的是,输入项的 当前完整路径 也被接受,但是,鉴于您不能将项重命名为自身,这具有以下效果:

    • 对于 文件 的输入项,调用是 安静 no-op.
    • 对于目录,您会收到一个错误,指出“Rename-Item:源路径和目标路径必须不同。 “
    • 这种令人惊讶的不一致是 GitHub issue #14903 的主题。