如何在 Powershell 中一次替换多个 .txt 文件中的数据?
Posted
技术标签:
【中文标题】如何在 Powershell 中一次替换多个 .txt 文件中的数据?【英文标题】:How can i replace data in multiple .txt files at once in Powershell? 【发布时间】:2021-05-17 18:49:15 【问题描述】:我在一个文件夹中有多个 .txt 文件,它们具有特定的数据格式,其中每一行都从 3 位数字开始,例如。
101,3333,35899,BufferC1,99,02,333
102,3344,30079,BufferD2,89,03,444
.... and so on.
现在,如果记录以“101”开头,那么我必须用一些文本替换同一行的第 3 和第 5 个索引元素。替换所有文件后,我必须将所有修改后的文件复制到同一台机器上的另一个目录/文件夹中。
我写了一些不工作的代码。请帮助我,因为我是 powershell 的新手。我的方法是否正确?
$FileNamesList = @(Get-ChildItem C:\TestFiles\*.txt | Select-Object -ExpandProperty Name)
for($i=0; $i -lt $FileNamesList.count; $i++)
# original folder path
$FilePath1 = 'C:\TestFiles\' + $FileNamesList[$i]
#modified files after text is replaced will be copied in another folder
$FilePath2 = 'C:\TestFilesModified\' + $FileNamesList[$i]
$OriginalFileData = Get-Content $FilePath1
# Is this correct, can i assign foreach cmd result to a variable?
$ModifiedFileData= ForEach($Row in $OriginalFileData )
If($Row.Split(",")[0] -eq "101")
$Row -replace($Row.Split(",")[3]),"Test File"
$Row -replace($Row.Split(",")[5]),"Test Data"
else
$Row
Out-File -FilePath $FilePath2 -InputObject $ModifiedFileData
【问题讨论】:
所有文件的列数是否相同?他们有标题行,还是只有数据? @ Mathias R. Jessen,这些只是普通的 .txt 文件,没有标题,只有数据。此外,这些文件没有相同的列数。我只需要更改第一个元素中以“101”开头的行。 【参考方案1】:我喜欢@Theo's approach。我开始沿着同样的思路思考并编写类似的代码。但是,我对 CSV 的想法很感兴趣,最终找到了一些可供分享的替代方法。
CSV 方法存在一些问题:
-
缺少标题
难以重新导出,因为
Export-Csv
没有 -NoHeader
参数
如 cmets 中所述处理可变数量的列
我的第一个方法是使用ConvertFrom-String
cmdlet 从原始数据中获取正式对象。
$SourceFolder = "C:\Temp\SourceFolder"
$DestinationFolder = "C:\Temp\destination"
ForEach( $File in Get-ChildItem $SourceFolder -Filter *.txt )
$DestinationFile = Join-Path -Path $DestinationFolder -ChildPath $File.Name
$File |
Get-Content |
ConvertFrom-String -Delimiter "," |
ForEach-Object
If( $_.P1 -eq 101 )
$_.P3 = "P3 Replacement Value"
$_.P5 = "P5 Replacement Value"
$_.PSObject.Properties.Value -join "," # Output from the loop
|
Set-Content -Path $DestinationFile
这样做的好处是您不需要知道任何 1 个文件甚至给定文件的任何 1 行中有多少字段。 ConvertFrom-String
只会为给定的行添加额外的属性。使用 $_.PSObject.Properties.Value
展开值还允许使用有限代码的任意数量的属性。
第二种方法需要知道最大列数。出于示例的目的,假设我们有可变数量的列,但最多只能说 8 个。我们可以将 -Header
参数与 Import-Csv
命令一起使用。
$Header = "P1","P2","P3","P4","P5","P6","P7","P8"
$SourceFolder = "C:\Temp\SourceFolder"
$DestinationFolder = "C:\Temp\destination"
ForEach( $File in Get-ChildItem C:\temp\SourceFolder -filter *.txt )
$DestinationFile = Join-Path -Path $DestinationFolder -ChildPath $File.Name
Import-Csv -Path $File.FullName -Header $Header |
ForEach-Object
If( $_.P1 -eq 101 )
$_.P3 = "P3 Replacement Value"
$_.P5 = "P5 Replacement Value"
($_.PSObject.Properties.Value -join ",").TrimEnd(",") # Output from the loop
|
Set-Content -Path $DestinationFile
注意:我想即使您不知道可以将大数组分配给 $Header
变量的最大字段数。
注意:.TrimEnd(",")
方法。由于给定行上的字段可能少于$Header
数组中的元素,Import-Csv
将添加一个属性并为其分配一个空值。反过来,这可能会导致-join
产生的字符串中出现额外的逗号。
警告:如果有合法的空尾随字段,这也可能会造成问题。
最后,我确实找到了一种进一步利用 *-Csv
cmdlet 的方法,但它仅适用于 ConvertFrom-String
示例:
$SourceFolder = "C:\Temp\SourceFolder"
$DestinationFolder = "C:\Temp\destination"
ForEach( $File in Get-ChildItem $SourceFolder -Filter *.txt )
$DestinationFile = Join-Path -Path $DestinationFolder -ChildPath $File.Name
$File |
Get-Content |
ConvertFrom-String -Delimiter "," |
ForEach-Object
If( $_.P1 -eq 101 )
$_.P3 = "P3 Replacement Value"
$_.P5 = "P5 Replacement Value"
$_
|
ConvertTo-Csv -NoTypeInformation |
Select-Object -Skip 1 |
Set-Content -Path $DestinationFile
这不需要-join
表达式,但是它以额外的Select-Object
管道为代价。
【讨论】:
感谢您提供非常详细的答案。代码中的P1、P3、P5是什么。我使用的方法是在 Array 中转换 Get-Content,然后通过 Index $Array[0]、$Array[3] ... 等引用元素? 此外,在输出文件夹中,新文件内容会不断地一次又一次地附加到同一个文件中。如果我想运行脚本并希望文件相同但每次都应使用最新的脚本运行内容完全修改整个文件内容,因为我每次都会在文件中显示 Get-Date,这可能吗? 嗨史蒂文,我面临一个问题。现在,在修改后的文件中,开始的 0 被截断。就像该行从 023,3333,4444,Test3....an 开始一样,然后在修改后的文件中我看到:23,3333,4444,Test3 P1...P3...P5 是由ConvertFrom-String
cmdlet 生成的通用属性。在*-csv
示例中,P 是因为我给它的标题名称。您可以更改这些以更好地表示您的数据。一旦它被转换为一个对象,我们将引用属性名称而不是数组元素。
csv 示例不会截断。但是,那里也存在潜在的问题。他们已经在答案中注明。请注意:虽然这是一个有趣的思考练习,但@theo 的回答是完全有效的,并且在您不必在预先存在的 cmdlet 中寻找警告的意义上更方便。您可能应该在接受该答案的同时追求这些样本或 som 混合。【参考方案2】:
很遗憾,这些文件没有标题,这将使它们成为正确的 Csv 文件,并且使用起来更加可靠..
您现在可以做的是逐行遍历这些文件并在分隔符 ,
处拆分。
编辑
代码现在使用正则表达式来分割逗号上的每个字符串,除非这个逗号在带引号的字段内。
# create a regex string to split on the delimiter character ',' unless it isinside quotes
$commaUnlessQuoted = ',(?=(?:[^"]*"[^"]*")*[^"]*$)'
Get-ChildItem -Path 'D:\Test'-Filter '*.txt' -File | ForEach-Object
$file = $_.FullName
$data = switch -Regex -File $file
'^101,'
# first field is '101', replace element index 3 with "Test File" and element index 5 with "Test Data"
$fields = $_ -split $commaUnlessQuoted
$fields[3] = "Test File"
$fields[5] = "Test Data"
# rejoin the fields with a comma
$fields -join ','
'^002,'
# first field is '002', replace element index 3 with the current date
$fields = $_ -split $commaUnlessQuoted
$fields[3] = (Get-Date).ToLongDateString()
$fields -join ','
# you can add as many regex conditions here as you like.
# default means no conditions above matched, so return te line as-is
default $_
$data | Set-Content -Path $file -Force
# copy the modified file to somewhere else
Copy-Item -Path $file -Destination 'C:\TestFilesModified'
$commaUnlessQuoted
的正则表达式详细信息:
, Match the character “,” literally
(?= Assert that the regex below can be matched, starting at this position (positive lookahead)
(?: Match the regular expression below
[^"] Match any character that is NOT a “"”
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
" Match the character “"” literally
[^"] Match any character that is NOT a “"”
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
" Match the character “"” literally
)* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
[^"] Match any character that is NOT a “"”
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
$ Assert position at the end of the string (or before the line break at the end of the string, if any)
)
根据您对this question 的评论,如果您还需要抛出非终止异常,只需更改以下几行:
$data | Set-Content -Path $file -Force
# copy the modified file to somewhere else
Copy-Item -Path $file -Destination 'C:\TestFilesModified'
进入
try
$data | Set-Content -Path $file -Force -ErrorAction Stop
# copy the modified file to somewhere else
Copy-Item -Path $file -Destination 'C:\TestFilesModified' -ErrorAction Stop
catch throw # just rethrow the exception so it 'bubbles up' to the calling script
【讨论】:
感谢您的帮助!这是一个非常好的方法。 @nick235 这也是最快的方法,而且..ConvertFrom-String
也有它的错误,您可以在 this question 中看到
@nick235 我已编辑 -split
以使用正则表达式,以确保代码不会在引号字段内的逗号上拆分。
1.如果我需要替换其他行中的数据,比如第一个元素是 ^002,那么我想要 $_.P4 = Get-Date。我可以在代码中使用 Elseif 吗? 2.如果其中一行有空元素,那么脚本会失败,例如; 002,3333,4444,Test2,ccd3,,,,,210215。有空元素,我仍然想更新 P4。 3.如果我有多个记录,例如2000行以“01”开头,那我该如何实现?
你的答案是最好的;0以上是关于如何在 Powershell 中一次替换多个 .txt 文件中的数据?的主要内容,如果未能解决你的问题,请参考以下文章