【问题标题】:rm -f equivalent for PowerShell that ignore nonexistent filesrm -f 等效于忽略不存在文件的 PowerShell
【发布时间】:2020-11-19 06:35:04
【问题描述】:

背景

我有一个将一些结果写入文件的 PowerShell 脚本。

  • 我想在脚本开头使用Remove-Item 自动删除结果文件。
  • 您可以手动删除结果文件,因此即使结果文件不存在,我也不想显示错误消息。
  • 我想在脚本由于其他原因无法删除结果文件时显示错误消息,例如文件已锁定。

您可以在类 Unix 系统中使用 rm -f 满足上述所有要求。

问题

首先我尝试了Remove-Item -Force,但它无法忽略不存在的文件(参见rm -f 忽略不存在的文件)。

PS C:\tmp> Remove-Item C:\tmp\foo.txt -Force
Remove-Item : Cannot find path 'C:\tmp\foo.txt' because it does not exist.
At line:1 char:1
+ Remove-Item C:\tmp\foo.txt -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\tmp\foo.txt:String) [Remove-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

接下来,我尝试了Remove-Item -ErrorAction Ignore 和Remove-Item -ErrorAction SilentlyContinue,但它们在删除文件失败时不会显示错误消息(参见rm -f 在这种情况下会显示类似rm: cannot remove 'foo.txt': Operation not permitted 的错误消息)。

PS C:\tmp> $file = [System.IO.File]::Open('C:\tmp\foo.txt',[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None)
PS C:\tmp> Remove-Item C:\tmp\foo.txt -ErrorAction Ignore
# I expected it shows an error because it couldn't remove the file because of the lock, but it showed nothing
PS C:\tmp> $file = [System.IO.File]::Open('C:\tmp\foo.txt',[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None)
PS C:\tmp> Remove-Item C:\tmp\foo.txt -ErrorAction SilentlyContinue
# I expected it shows an error because it couldn't remove the file because of the lock, but it showed nothing

问题

PowerShell 中是否存在满足上述所有要求的 rm -f 等效项?

【问题讨论】:

    标签: powershell rm


    【解决方案1】:

    对我来说,最简单的解决方案是:

    if (test-path $file) {
      remove-item $file
    }
    

    这也发生在我身上。 $error[0] 总是最新的错误。

    remove-item $file -erroraction silentlycontinue
    if ($error[0] -notmatch 'does not exist') {
      write-error $error[0]  # to standard error
    }
    

    我认为您也可以在特定例外情况下使用 try/catch。这是一个例子。我通过选项卡完成发现了异常。但是脚本会因其他未捕获的异常而停止。此错误通常不会停止。

    try { remove-item foo -erroraction stop }
    catch [System.Management.Automation.ItemNotFoundException] { $null }
    'hi'
    

    【讨论】:

    • 感谢您的评论。第一个似乎有TOCTOU问题,第二个似乎不适用于非英语环境。但它简单易懂,对在单一(英文)环境中使用 PowerShell 的人很有帮助。第三种是最适合我的上下文的方式(我在日语语言环境中使用 Windows 10),并且足够简单,每个人都可以理解。我想将此答案标记为最有帮助。
    【解决方案2】:

    您不能单独使用该 cmdlet 执行此操作。您必须为错误提供额外的逻辑。

    'D:\temp\abc.txt', 'D:\Temp\hw.txt', 'D:\Temp\nonexistent.txt.', 'D:\Temp\book1.csv' | 
    ForEach{
        try   {Remove-Item   -Path    $PSitem -WhatIf -ErrorAction Stop}
        catch {Write-Warning -Message $PSItem.Exception.Message}
    }
    # Results
    <#
    What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
    What if: Performing the operation "Remove File" on target "D:\Temp\hw.txt".
    WARNING: Cannot find path 'D:\Temp\nonexistent.txt.' because it does not exist.
    What if: Performing the operation "Remove File" on target "D:\Temp\book1.csv".
    #>
    

    您应该在所有代码(交互式和脚本)中利用错误处理。 您可以将任何屏幕输出发送到 $null、Out-Null 或 [void] 以防止它进入屏幕,但仍然知道它发生了。

    对于您要求的用例,您将需要多个逻辑(try/catch、if/then)语句。

    所以,类似这个修改过的包装函数:

    function Remove-ItemNotFileLocked
    {
        [cmdletbinding(SupportsShouldProcess)]
        [Alias('rinf')]
    
        param 
        (
            [parameter(Mandatory = $true)][string]$FullFilePath
        )
    
        $TargetFile = New-Object System.IO.FileInfo $FullFilePath
    
        if ((Test-Path -Path $FullFilePath) -eq $false) {return $false}
    
        try 
        {
            $TargetFileStream = $TargetFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
    
            if ($TargetFileStream) 
            {
                $TargetFileStream.Close()
                Remove-Item -Path $FullFilePath
            }
            $false
        } 
        catch 
        {
            $true
        }
    }
    
    'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
    ForEach {Remove-ItemNotFileLocked -FullFilePath $PSItem -WhatIf}
    
    # Results
    <#
    What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
    True
    False
    #>
    

    注意点:记事本等文本编辑器不会锁定文件。

    • 第一条消息显示一个记事本文件已打开,但可以删除
    • 第二个显示 Word 文档打开和锁定
    • 第三个显示一个文本文件,不在系统上

    如果我不想要那种屏幕噪音,那么... 注释掉那些 $false 和 $True 语句,它们用于调试和验证工作。

    'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
    ForEach {$null = Remove-ItemNotFileLocked -FullFilePath $PSItem -WhatIf}
    # Results
    <#
    What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
    #>
    

    当然,删除/注释掉 -WhatIf 以允许事情发生,这种噪音也会消失。

    如果您不想使用某个函数,那么此代码块应该可以解决您的用例。

    # Remove non-Locked file and show screen output
    'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
    ForEach{
        try   
        {
            $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                                [System.IO.FileMode]::Open, 
                                                [System.IO.FileAccess]::ReadWrite, 
                                                [System.IO.FileShare]::None
                          )
            $TargetFile.Close()  
            Remove-Item -Path $PSItem -WhatIf  
        }
        catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
        catch {$PSItem.Exception.Message}
    }
    
    # Results
    <#
    What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
    Exception calling "Open" with "3" argument(s): "The process cannot access the file 'D:\Documents\Return To Sender.docx' because it is being used by another process."
    Exception calling "Open" with "3" argument(s): "Could not find file 'D:\Temp\nonexistent.txt'."
    #>
    
    # Remove non-Locked file and silence screen output
    'D:\temp\abc.txt', 'D:\Documents\Return To Sender.docx','D:\Temp\nonexistent.txt.' | 
    ForEach{
        try   
        {
            $TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
                                                [System.IO.FileMode]::Open, 
                                                [System.IO.FileAccess]::ReadWrite, 
                                                [System.IO.FileShare]::None
                          )
            $TargetFile.Close()  
            Remove-Item -Path $PSItem -WhatIf 
        }
        catch [System.Management.Automation.ItemNotFoundException]{$null = $PSItem.Exception.Message}
        catch {$null = $PSItem.Exception.Message}
    }
    # Results
    <#
    What if: Performing the operation "Remove File" on target "D:\temp\abc.txt".
    #>
    

    【讨论】:

    • 感谢您的评论。似乎有文件锁定的 TOCTOU 问题,但在我的上下文中不会实现该问题。不幸的是,我的问题中的“另一个原因”不仅限于文件锁定,因此需要更多的努力才能使其正常工作。
    猜你喜欢
    • 2013-02-18
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    • 2011-11-18
    • 2015-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多