powershell 在一行中不声明变量的情况下释放StreamWriter

xjreopfe  于 2022-11-10  发布在  Shell
关注(0)|答案(2)|浏览(135)

下面的PowerShell命令无法复制整个文件;末尾总是缺少几个字符。

[System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8).Write([System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1')).ReadToEnd())

我怀疑这是因为编写器不刷新最后一位,因为这确实复制了整个文件:

$X = [System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1'))
$Y = [System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)
$Y.Write($X.ReadAll())
$X.Dispose()
$Y.Dispose()

是否有可能在不创建引用读取器和写入器的变量的情况下处理(和刷新)读取器和写入器?
编辑:我使用StreamReader/Writer尝试了这一行程序,希望读取器的读缓冲区可以直接传输到写入器的写缓冲区,而不是等待读取器将整个文件读取到内存中,然后再写入。有什么技术可以做到这一点呢?
我个人发现没有声明一次性对象的代码通常更干净/更简洁,但我的重点是理解对象是否/如何处置自己,而不是样式。
没有“需要”回避变量或在一行上编写代码,但这种行为并不是我所期望的。在VBA中,人们可以像这样复制文件,并相信它将正确地自我处置,而不必声明变量并显式刷新(我认为)。

Sub Cpy()
With New Scripting.FileSystemObject
    .CreateTextFile("c:\Temp\Out.txt").Write .OpenTextFile("C:\Temp\In.txt", ForReading).ReadAll
End With
End Sub

通过在Class_Terminate()过程中编写适当的“清理”代码,可以在自定义VBA类中实现类似的行为。我假设,一旦行执行并且不再有与其关联的变量,StreamWriter将类似地通过垃圾收集在终止时刷新数据。
我还注意到,该文件仍处于锁定状态,在关闭PowerShell会话之前无法将其删除。有没有一种方法可以在不声明要使用的变量的情况下刷新内容并释放文件?

5cg8jx4n

5cg8jx4n1#

只是为了向您展示,使用System.IO.FileWriteAllText()ReadAllText()的静态方法可以更容易地实现这一点。
以下查询https://loripsum.net/ API以获取随机段落,并使用iso-8859-1编码写入文件。然后读取该文件并使用相同的编码写入副本,最后比较两个文件散列。如你所见,阅读和写作都是一句俏皮话。
可以删除using语句,但需要使用完全限定的类型名称。

将位置设置为用于测试的临时文件夹。

using namespace System.IO
using namespace System.Text

$fileRead  = [Path]::Combine($pwd.Path, 'test.txt')
$fileWrite = [Path]::Combine($pwd.Path, 'test-copy.txt')
$content   = Invoke-RestMethod 'https://loripsum.net/api/5/short/headers/plaintext'
$encoding  = [Encoding]::GetEncoding('iso-8859-1')

[File]::WriteAllText($fileRead, $content, $encoding)
[File]::WriteAllText($fileWrite, [File]::ReadAllText($fileRead, $encoding), $encoding)

(Get-FileHash $fileRead).Hash -eq (Get-FileHash $fileWrite).Hash # => Should be True

$fileRead, $fileWrite | Remove-Item
u0sqgete

u0sqgete2#

  • 对于给定的特定用例Santiago Squarzon's helpful answer确实是最好的解决方案:使用静态System.IO.File类的静态方法,不需要示例来表示需要调用.Close()方法或显式处置的文件。
  • 懒惰地读取并因此支持逐行重叠读写,您可以使用静态[System.IO.File]::ReadLines()[System.IO.File]::WriteAllLines()方法,但请注意,这种方法(A)总是在输出文件中使用平台原生[Environment]::NewLine的换行符,而不管输入文件使用哪种换行符格式,并且(B)总是在这种格式中添加一个尾随换行符,即使输入文件没有尾随换行符。
  • 要克服这些限制,需要使用较低级别的原始字节API System.IO.FileStream,这也需要显式处理(见下一节)。
  • 假设您的方法首先将整个输入文件读取到内存中,然后写入,假设您运行的是PowerShell(Core)7+,默认情况下会写入无BOM的UTF-8文件,并且其-Encoding参数接受任何支持的编码,例如您的示例中的ISO-8859-1,则甚至可以使用PowerShell cmdlet:

# PowerShell (Core) 7+ only

Get-Content -Raw -Encoding iso-8859-1 C:\TEMP\a.csv |
  Set-Content -NoNewLine C:\TEMP\b.csv

关于你的一般性问题
从PowerShell(核心)7.2.1开始:

  • PowerShell没有等价于C#的using语句的构造,它允许自动*处置其类型实现System.IDisposable接口的对象(对于文件I/O API,该接口隐式关闭文件)。
  • GitHub issue #9886讨论添加这样的声明,但讨论表明它很可能不会实施。
  • 注意:虽然PowerShell确实有一系列以关键字using开头的语句,但它们的用途不同-请参阅概念性的about_Using帮助主题。
  • 未来*PowerShell版本将支持当advanced function或脚本终止时自动调用的clean { ... }(或cleanup { ... })块**,这允许执行任何必要的脚本级清理(处置对象)-请参阅RFC #294

是否从终结器调用.Dispose()方法取决于实现IDisposable接口的每个类型。只有在这样的情况下,垃圾回收器才会自动最终处理对象。
对于System.IO.StreamWriter和较低级别的System.IO.FileStream类,情况似乎并非如此,因此在PowerShell中,您必须显式调用.Close().Dispose(),这最好从try / catch / finally statementfinally块完成。
通过将对象构造和变量赋值结合起来,你可以在一定程度上减少仪式,但一个健壮的习语仍然需要大量的仪式:

$x = $y = $null
try {
  ($y = [System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)).
    Write(
      ($x = [System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1'))).
        ReadToEnd()
    )
} finally {
  if ($x) { $x.Dispose() }
  if ($y) { $y.Dispose() }
}

帮助器函数Use-Object(下面的源代码)可以稍微缓解这一点:

Use-Object 
  ([System.IO.StreamReader]::new('C:\Temp\a.csv',[System.Text.Encoding]::GetEncoding('iso-8859-1'))), 
  ([System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)) `
  { $_[1].Write($_[0].ReadToEnd()) }

请注意,作为第一个参数传递的禁用对象是如何通过$_作为脚本块参数中的ARRAY引用的(通常,您可以使用$PSItem代替$_)。
一种更具可读性的替代方案:

Use-Object 
  ([System.IO.StreamReader]::new('C:\Temp\a.csv',[System.Text.Encoding]::GetEncoding('iso-8859-1'))), 
  ([System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)) `
  { 
    $reader, $writer = $_
    $writer.Write($reader.ReadToEnd()) 
  }

Use-Object源代码

function Use-Object {
  param( 
    [Parameter(Mandatory)] $ObjectsToDispose,  # a single object or array
    [Parameter(Mandatory)] [scriptblock] $ScriptBlock
  )

  try {
    ForEach-Object $ScriptBlock -InputObject $ObjectsToDispose
  }
  finally {
    foreach ($o in $ObjectsToDispose) {
      if ($o -is [System.IDisposable]) {
        $o.Dispose()
      }
    }
  }

}

相关问题