使用 StreamReader 循环读取和写入相同的 txt 文件

Read and write to same txt file in loop with StreamReader

我在 PowerShell 中有一个工作脚本:

$file = Get-Content -Path HKEY_USERS.txt -Raw

foreach($line in [System.IO.File]::ReadLines("EXCLUDE_HKEY_USERS.txt"))
{
    $escapedLine = [Regex]::Escape($line)
    $pattern = $("(?sm)^$escapedLine.*?(?=^\[HKEY)")
    
    $file -replace $pattern, ' ' | Set-Content HKEY_USERS-filtered.txt
    $file = Get-Content -Path HKEY_USERS-filtered.txt -Raw
}

对于 EXCLUDE_HKEY_USERS.txt 中的每一行,它正在对文件 HKEY_USERS.txt 执行一些更改。因此,在每次循环迭代中,它都会写入此文件并重新读取同一文件以提取更改。但是,Get-Contentnotorious for memory leaks,所以我想将其重构为 StreamReaderStreamWriter,但我很难让它工作。

只要我这样做:

$filePath = 'HKEY_USERS-filtered.txt';
$sr = New-Object IO.StreamReader($filePath);
$sw = New-Object IO.StreamWriter($filePath);

我得到:

New-Object : Exception calling ".ctor" with "1" argument(s): "The process cannot access the file 
'HKEY_USERS-filtered.txt' because it is being used by another process."

看来我不能同时对同一个文件使用 StreamReader 和 StreamWriter。或者我可以吗?

tl;dr

  • Get-Content -Raw 读取一个文件 作为一个整体 并且速度很快并且消耗的内存很少。

  • [System.IO.File]::ReadLines() 比 line-by-line 阅读 Get-Content(没有 -Raw)更快更 memory-efficient,但是你需要确保输入文件作为 完整 路径传递,因为 .NET 的工作目录通常不同于 PowerShell 的。

    • Convert-Path 将给定的相对路径解析为完整的 file-system-native 一个。

    • 使用 [System.IO.File]::ReadLines() 的 PowerShell-native 替代方法是带有 -File 参数的 switch 语句,它在避免 working-directory 差异陷阱,并提供额外的功能。

  • 不需要在每次迭代后将修改后的文件内容保存到磁盘——只需更新$file变量,并且,退出后循环,将 $file 的值保存到输出文件。

$fileContent = Get-Content -Path HKEY_USERS.txt -Raw

# Be sure to specify a *full* path.
$excludeFile = Convert-Path -LiteralPath 'EXCLUDE_HKEY_USERS.txt'

foreach($line in [System.IO.File]::ReadLines($excludeFile)) {
    $escapedLine = [Regex]::Escape($line)
    $pattern = "(?sm)^$escapedLine.*?(?=^\[HKEY)"
    # Modify the content and save the result back to variable $fileContent
    $fileContent = $fileContent -replace $pattern, ' '
}

# After all modifications have been performed, save to the output file
$fileContent | Set-Content HKEY_USERS-filtered.txt

基于 Santiago Squarzon 的有用评论:

  • Get-Content 不会 导致内存 泄漏 ,但它会消耗大量内存 garbage-collected 直到一个不可预测的后期时间点。
    • 原因是 - 除非使用 -Raw 开关 - 它会用 PowerShell ETS (Extended Type System) 属性修饰读取的每一行,其中包含有关原始文件的元数据,例如其路径(.PSPath) 和行号 (.ReadCount).
    • 这会消耗额外的内存并减慢命令速度 - GitHub issue #7537 请求一种方法来 选择退出 这种浪费的装饰,因为它通常不需要.
    • 然而,使用-Raw 读取是高效的,因为整个文件内容被读入一个单个,multi-line字符串,表示装饰只进行一次.

So it looks like I cannot use StreamReader and StreamWriter on same file simultaneously. Or can I?

不,你不能。您不能同时读取文件并覆盖它。

要更新/替换现有文件,您有两种选择(请注意,对于完全可靠的解决方案,应保留原始文件的所有属性(最后写入时间和大小除外),这需要额外的工作) :

  • 将旧内容全部读入内存,进行修改内存,然后写入修改内容回到原始文件,如顶部部分所示。

    • 但是,存在数据丢失的轻微风险,即如果写回文件的过程被中断。
  • 更安全,将修改后的内容写入临时文件,成功完成后,替换原来的用临时文件归档。