在 .bat 文件中使用 PowerShell,将一个字符串替换为多个字符串

Posted

技术标签:

【中文标题】在 .bat 文件中使用 PowerShell,将一个字符串替换为多个字符串【英文标题】:Using PowerShell in a .bat file, replace a string with multiple strings 【发布时间】:2017-06-23 10:38:08 【问题描述】:

我正在使用 .baat 将多个文件移动到另一个文件夹中,但在实际移动部分之前,我想替换 LAST 行(它是已知行),例如我有一个文件 output.txt 之类的这个:

标题
    身体
 页脚

使用这个sn-p的代码:

powershell -Command "(gc output.txt) -replace 'FOOTER', 'ONE_MORE_LINE `r`n FOOTER' | Out-File output.txt"

我期望的回报是

标题
    身体
 ONE_MORE_LINE
 页脚

但我得到的是:

标题
    身体
 ONE_MORE_LINE `r`n FOOTER

我试过了:

\n <br> "`r`n" "`n" echo ONE_MORE_LINE >> output.txt; echo. >> output.txt; echo FOOTER >> output.txt"

最后一个接近了,但结果是一些坏字符。

欢迎 PowerShell 之外的其他建议。我之所以使用它,是因为它是一种简单的添加行和替换它的方法。

编辑: 试过这个命令

powershell -Command "(gc output.txt) -replace 'FOOTER;', ""ONE_MORE_LINE `r`n FOOTER"" | Out-File output.txt "

并返回此错误:

A cadeia de caracteres não tem o terminador:“。
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString 

EDIT2 - 可能的解决方案:

我意识到使用 PowerShell 命令改变了文件的编码,破坏了echo ONE_MORE_LINE,并使用了@AnsgarWiechers 的建议,我编写了这个代码

findstr /v "FOOTER" output.sql > new_output.sql
TYPE new_output.sql > output.sql
del new_output.sql
ECHO. >> %%f
ECHO ONE_MORE_LINE >> %%f
ECHO FOOTER >> %%f
ECHO. >> %%f

它的作用是使用命令findstr /v“FOOTER”我在文件 output.sql 中查找所有不是 FOOTER 的行并将其写入 new_output.sql

然后我TYPE它回到原来的文件,DELnew_output.sql

然后我Echo所有我需要的行就在它下面。

它可以工作,但是对于大文件,我认为重写两次会花费很多时间,但我想不出其他解决方案。

【问题讨论】:

PowerShell 不会在单引号字符串中扩展转义序列。您必须在此处使用双引号字符串。将嵌套的双引号加倍以对 CMD 进行转义:powershell -Command "(...) -replace 'FOOTER', ""ONE_MORE_LINE `r`n FOOTER"" | ..." 您可以使用FOR /F 命令在纯批处理中执行此操作。您只需将前一行保存在变量中。当读取下一行时,写出上一行。当FOR 命令完成后,写出倒数第二行,然后写出将保存在前一个变量中的最后一行。 @AnsgarWiechers - 当我将嵌套的双引号加倍时,它返回一个错误,说字符链没有结尾。 @Squashman 你能告诉我如何使用纯批处理重写它吗? 请使用修改后的命令和确切的错误消息更新您的问题。 【参考方案1】:

处理大文件时,最好使用文件流。使用批处理 for /f 循环或在 PowerShell 中使用 Get-Content 将整个文件读入内存的更典型的逐行读取文件的方法可能会减慢处理大文件的过程。另一方面,使用文件流,您几乎可以立即从文件末尾回溯到最后一行的开头,插入所需的数据,然后重新组合您覆盖的字节。

以下示例将使用 PowerShell 对 .NET 方法的访问以字节流的形式打开文件,以实现快速读写。有关详细信息,请参阅内联 cmets。文件编码有望被保留。使用 .bat 扩展名保存此文件并试一试。

<# : batch portion
@echo off & setlocal

set "file=test.txt"
set "line=Line to insert!"

powershell -noprofile "iex ($%~f0 | out-string)"
goto :EOF
: end batch / begin PowerShell hybrid #>

# construct a file stream for reading and writing $env:file
$iostream = new-object IO.FileStream((gi $env:file).FullName,
    [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite)

# read BOM to determine file encoding
$reader = new-object IO.StreamReader($IOstream)
[void]$reader.Read((new-object byte[] 3), 0, 3)
$encoding = $reader.CurrentEncoding
$reader.DiscardBufferedData()

# convert line-to-insert to file's native encoding
$utf8line = [Text.Encoding]::UTF8.GetBytes("`r`n$env:line")
$line = [Text.Encoding]::Convert([Text.Encoding]::UTF8, $encoding, $utf8line)
$charSize = [math]::ceiling($line.length / $utf8line.length)

# move pointer to the end of the stream
$pos = $IOstream.Seek(0, [IO.SeekOrigin]::End)

# walk back pointer while stream returns no error
while ($char -gt -1) 
    $IOstream.Position = --$pos
    $char = $reader.Peek()
    $reader.DiscardBufferedData()

    # break out of loop when line feed preceding non-whitespace is found
    if ($foundPrintable)  if ($char -eq 10)  break  
    else  if ([char]$char -match "\S")  $foundPrintable++  


# step pointer back to carriage return and read to end into $buffer with $line prepended
$pos -= $charSize
$IOstream.Position = $pos
$buffer = $encoding.GetBytes($encoding.GetString($line) + $reader.ReadToEnd())
$IOStream.Position = $pos

"Inserting data at byte $pos"
$IOstream.Write($buffer, 0, $buffer.Length)

# Garbage collection
$reader.Dispose()
$IOstream.Dispose()

这种方法应该比从头开始读取文件,或者将整个文件复制到内存或插入新行的磁盘上要高效得多。在我的测试中,它会在大约 1/3 秒内将该行插入到 100 兆的文件中。

【讨论】:

请注意,在这种情况下,“大文件”需要几个 GiB 才能非常缓慢。即便如此,如果您只是进行基本替换,您可以使用-ReadCount 代替Get-Content 来加快速度。不过,我怀疑他们是否真的拥有需要这种复杂性的大文件。 @Joey 根据我的经验,即使解析 100 meg 文件也可能需要一个小时或更长时间。 See this thread of comments 举一个这样的例子,为什么我曾经被咬过,两次害羞,为什么我默认为此类任务推荐流解析器。如果您愿意发布答案,我很想看看您如何实施 gc -ReadCount 以加快任务速度。 我最近不得不编写一个脚本,它需要一个 10 GiB 的 CSV 文件并替换其中的字符串。它花了几分钟(令人讨厌),但绝对不是几个小时。 -ReadCount 将大大减少管道开销,但可能会使管道中 Get-Content 之后的代码复杂化。这是一个权衡。一旦我不再工作,我可能会做一些测试。

以上是关于在 .bat 文件中使用 PowerShell,将一个字符串替换为多个字符串的主要内容,如果未能解决你的问题,请参考以下文章

powershell中如何执行bat文件

powershell $ lastexitcode在运行批处理文件时不起作用

powershell脚本隐藏退出码

如何在配置文件中定义的 PowerShell 脚本中设置环境变量?

PowerShell删除指定文件

使用参数从 Powershell 脚本运行 .bat 文件