如何在 Powershell 中一次替换多个 .txt 文件中的数据?
How can i replace data in multiple .txt files at once in Powershell?
我在一个文件夹中有多个 .txt 文件,这些文件具有特定的数据格式,其中每一行都从 3 位数字开始,例如。
101,3333,35899,BufferC1,99,02,333
102,3344,30079,BufferD2,89,03,444
.... and so on.
现在,如果记录以“101”开头,那么我必须用一些文本替换同一行的第 3 个和第 5 个索引元素。替换所有文件后,我必须将所有修改后的文件复制到同一台机器上的另一个 directory/folder。
我写了一些无法运行的代码。请帮助我,因为我是 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
}
遗憾的是这些文件没有 headers 这将使它们成为正确的 Csv 文件并且使用起来更加可靠..
您现在可以做的是遍历这些文件 line-by 行并拆分分隔符 ,
.
编辑
该代码现在使用正则表达式在逗号上拆分每个字符串,除非该逗号位于带引号的字段内。
# 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 的评论,如果您还需要抛出 non-terminating 异常,只需更改以下行:
$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
我喜欢@Theo's approach。我开始沿着相同的思路思考并编写类似的代码。但是,我对 CSV 的想法很着迷,最终得到了一些可供分享的替代方法。
CSV 方法存在一些问题:
- 缺少headers
- 困难 re-exporting 因为
Export-Csv
没有 -NoHeader
参数
- 处理评论中提到的可变数量的列
我的第一个方法是使用 ConvertFrom-String
cmdlet 从原始数据中获取正式的 objects。
$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
管道为代价的。
我在一个文件夹中有多个 .txt 文件,这些文件具有特定的数据格式,其中每一行都从 3 位数字开始,例如。
101,3333,35899,BufferC1,99,02,333
102,3344,30079,BufferD2,89,03,444
.... and so on.
现在,如果记录以“101”开头,那么我必须用一些文本替换同一行的第 3 个和第 5 个索引元素。替换所有文件后,我必须将所有修改后的文件复制到同一台机器上的另一个 directory/folder。
我写了一些无法运行的代码。请帮助我,因为我是 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
}
遗憾的是这些文件没有 headers 这将使它们成为正确的 Csv 文件并且使用起来更加可靠..
您现在可以做的是遍历这些文件 line-by 行并拆分分隔符 ,
.
编辑
该代码现在使用正则表达式在逗号上拆分每个字符串,除非该逗号位于带引号的字段内。
# 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 的评论,如果您还需要抛出 non-terminating 异常,只需更改以下行:
$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
我喜欢@Theo's approach。我开始沿着相同的思路思考并编写类似的代码。但是,我对 CSV 的想法很着迷,最终得到了一些可供分享的替代方法。
CSV 方法存在一些问题:
- 缺少headers
- 困难 re-exporting 因为
Export-Csv
没有-NoHeader
参数 - 处理评论中提到的可变数量的列
我的第一个方法是使用 ConvertFrom-String
cmdlet 从原始数据中获取正式的 objects。
$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
管道为代价的。