使用 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-Content
是 notorious for memory leaks,所以我想将其重构为 StreamReader
和 StreamWriter
,但我很难让它工作。
只要我这样做:
$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?
不,你不能。您不能同时读取文件并覆盖它。
要更新/替换现有文件,您有两种选择(请注意,对于完全可靠的解决方案,应保留原始文件的所有属性(最后写入时间和大小除外),这需要额外的工作) :
将旧内容全部读入内存,进行修改内存,然后写入修改内容回到原始文件,如顶部部分所示。
- 但是,存在数据丢失的轻微风险,即如果写回文件的过程被中断。
更安全,将修改后的内容写入临时文件,成功完成后,替换原来的用临时文件归档。
我在 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-Content
是 notorious for memory leaks,所以我想将其重构为 StreamReader
和 StreamWriter
,但我很难让它工作。
只要我这样做:
$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?
不,你不能。您不能同时读取文件并覆盖它。
要更新/替换现有文件,您有两种选择(请注意,对于完全可靠的解决方案,应保留原始文件的所有属性(最后写入时间和大小除外),这需要额外的工作) :
将旧内容全部读入内存,进行修改内存,然后写入修改内容回到原始文件,如顶部部分所示。
- 但是,存在数据丢失的轻微风险,即如果写回文件的过程被中断。
更安全,将修改后的内容写入临时文件,成功完成后,替换原来的用临时文件归档。