使用 PowerShell 替换 CRLF

47

编辑注:根据提问者后来的评论,这个问题的要点是: 如何在 PowerShell 中将带有 CRLF(Windows 风格)换行符的文件转换为仅包含 LF(Unix 风格)的文件?

以下是我的 PowerShell 脚本:

 $original_file ='C:\Users\abc\Desktop\File\abc.txt'
 (Get-Content $original_file) | Foreach-Object {
 $_ -replace "'", "2"`
-replace '2', '3'`
-replace '1', '7'`
-replace '9', ''`
-replace "`r`n",'`n'
} | Set-Content "C:\Users\abc\Desktop\File\abc.txt" -Force

使用这段代码,我能够将2替换为3,1替换为7,9替换为空字符串。 但我无法将回车换行符替换为只有换行符。 但这并不起作用。


1
Set-Content命令将管道中的内容写入文件。管道中的每个项目都会写在新的一行上。 - Andrew Savinykh
7个回答

63

以下是截至Windows PowerShell v5.1 / PowerShell Core v6.2.0的最新情况:

  • 尽管Andrew Savinykh的被接受的回答已经过时,但从本文撰写之日起,其基本上存在根本性缺陷(我希望它能得到修复-评论和编辑历史中有足够的信息可以这样做)。

  • Ansgar Wiecher提供的有益回答有效,但需要直接使用.NET Framework(并且将整个文件读入内存,尽管可以更改)。 直接使用.NET Framework本身并不是问题,但对于新手来说难以掌握,一般难以记忆。

  • 未来版本的PowerShell Core可能会引入一个Convert-TextFile cmdlet,并带有-LineEnding参数,允许使用特定换行符样式更新文本文件:请参阅GitHub issue#6201

PSv5+中,现在可以使用PowerShell本机解决方案,因为Set-Content现在支持-NoNewline开关,可防止附加平台本机换行符[1]:

# Convert CRLFs to LFs only.
# Note:
#  * (...) around Get-Content ensures that $file is read *in full*
#    up front, so that it is possible to write back the transformed content
#    to the same file.
#  * + "`n" ensures that the file has a *trailing LF*, which Unix platforms
#     expect.
((Get-Content $file) -join "`n") + "`n" | Set-Content -NoNewline $file

上述内容依赖于Get-Content读取使用任意组合的CR-only、CRLF和LF-only换行符的文本文件,并逐行进行处理。

注意事项:

  • 您需要指定输出编码以匹配输入文件的编码,以便以相同的编码重新创建它。上面的命令没有指定输出编码;要指定,请使用-Encoding

  • 默认情况下不使用-Encoding

    • Windows PowerShell中,您将获得"ANSI"编码,即系统的单字节8位遗留编码,例如在美国英语系统上的Windows-1252。

    • PowerShell(Core),v6+中,您将获得UTF-8编码没有BOM

    • 输入文件的内容以及其转换后的副本必须作为一个整体装入内存,这可能会在处理大型输入文件时出现问题,但在处理文本文件时很少有问题。

    • 如果写回输入文件的过程被中断,则存在文件损坏的小风险


[1]实际上,如果有多个字符串要写入,-NoNewline也不会在它们之间放置换行符;但在这种情况下,这是无关紧要的,因为只有一个字符串被写入。


46

你没有说明版本,我假设你正在使用PowerShell v3。

尝试这个:

$path = "C:\Users\abc\Desktop\File\abc.txt"
(Get-Content $path -Raw).Replace("`r`n","`n") | Set-Content $path -Force

编辑注:正如Mike Z在评论中指出的那样,Set-Content会追加一个不需要的结尾CRLF。可使用以下命令验证:'hi'>t.txt;(Get-Content-Raw t.txt).Replace("`r`n","`n")| Set-Content t.txt;(Get-Content-Raw t.txt).EndsWith("`r`n"),结果显示为$True

请注意,此操作将整个文件加载到内存中,因此如果您要处理大文件,则可能需要不同的解决方案。

更新

对于v2版本,下面的方法可能有效(很抱歉没有地方测试):

$in = "C:\Users\abc\Desktop\File\abc.txt"
$out = "C:\Users\abc\Desktop\File\abc-out.txt"
(Get-Content $in) -join "`n" > $out
编辑说明: 请注意,此解决方案现在写入到一个不同的文件中,因此与(仍有缺陷的)v3解决方案不等价。(针对Ansgar Wiechers在评论中指出的陷阱:使用> 截断目标文件执行之前的内容)。然而更重要的是: 此解决方案也添加了一个不必要的结尾CRLF,这可能不被期望。用'hi' > t.txt; (Get-Content t.txt) -join "`n" > t.NEW.txt; [io.file]::ReadAllText((Convert-Path t.NEW.txt)).endswith("`r`n")进行验证,结果为$True

但是对于载入内存同样存在保留意见。


9
几乎可以了。 Set-Content 仍然会在结尾插入额外的CR/LF。 - Mike Zboray
4
太好了,我升级到了PowerShell V3,你的代码可以使用了,但是像Mike提到的一样,它仍然在末尾留下了CR/LF。我只想要所有的LF而没有CR/LF。 - Angel_Boy
1
您对PowerShell v2的建议将会擦除文件内容,因为重定向会在子shell读取文件之前创建一个新的空文件。请将其删除。 - Ansgar Wiechers
1
行为在PowerShell v2和v3中是相同的。使用重定向运算符截断文件,在Get-Content读取之前。 - Ansgar Wiechers
3
PSv5+ 提供了解决尾随 CRLF 问题的方案:Set-Content -NoNewline。使用 | Out-File …(或 | Set-Content …)代替 > 可以避免输出文件被截断。 - mklement0
显示剩余17条评论

32

不会添加虚假 CR-LF 的替代方案:

$original_file ='C:\Users\abc\Desktop\File\abc.txt'
$text = [IO.File]::ReadAllText($original_file) -replace "`r`n", "`n"
[IO.File]::WriteAllText($original_file, $text)

2
做得好(在v2中也有效)。关于使用相对路径的提示:首先使用(Convert-Path $original_file)将相对路径转换为完整路径,因为.NET框架对当前目录的理解通常与PS不同。 - mklement0
如果您想将Unix切换到Windows,但有可能它已经是Windows,那么替换子句会是什么样子? - Seth
3
请使用负回顾断言:'(?<!\r)\n', "`r`n" (仅在换行符 LF 不是由回车符 CR 之前的字符时,将 LF 替换为 CR-LF)。 - Ansgar Wiechers

3
以下是我递归转换所有文件的脚本。您可以指定要排除的文件夹或文件。
$excludeFolders = "node_modules|dist|.vs";
$excludeFiles = ".*\.map.*|.*\.zip|.*\.png|.*\.ps1"

Function Dos2Unix {
    [CmdletBinding()]
    Param([Parameter(ValueFromPipeline)] $fileName)

    Write-Host -Nonewline "."

    $fileContents = Get-Content -raw $fileName
    $containsCrLf = $fileContents | %{$_ -match "\r\n"}
    If($containsCrLf -contains $true)
    {
        Write-Host "`r`nCleaing file: $fileName"
        set-content -Nonewline -Encoding utf8 $fileName ($fileContents -replace "`r`n","`n")
    }
}

Get-Childitem -File "." -Recurse |
Where-Object {$_.PSParentPath -notmatch $excludeFolders} |
Where-Object {$_.PSPath -notmatch $excludeFiles} |
foreach { $_.PSPath | Dos2Unix }

1
注意:个人建议使用utf8编码,不要在末尾添加新行。我之前因为将整个项目推送到版本控制系统时使用了crlf,导致gradle构建失败。 - geisterfurz007
再深入挖掘一下,这是由于PowerShell在文件开头添加BOM导致的。要绕过这个问题,请检查这里,或者不要使用PowerShell来重写你的文件 :') - geisterfurz007

2

在@ricky89和@mklement0的示例基础上,增加了一些改进版本:

要处理的脚本:

  • 当前文件夹中的*.txt文件
  • 将LF替换为CRLF(Unix到Windows行尾)
  • 保存结果文件到CR-to-CRLF子文件夹
  • 在100MB+文件上进行测试,PS v5;

LF-to-CRLF.ps1:

# get current dir
$currentDirectory = Split-Path $MyInvocation.MyCommand.Path -Parent

# create subdir CR-to-CRLF for new files
$outDir = $(Join-Path $currentDirectory "CR-to-CRLF")
New-Item -ItemType Directory -Force -Path $outDir | Out-Null

# get all .txt files
Get-ChildItem $currentDirectory -Force | Where-Object {$_.extension -eq ".txt"} | ForEach-Object {
  $file = New-Object System.IO.StreamReader -Arg $_.FullName
  # Resulting file will be in CR-to-CRLF subdir
  $outstream = [System.IO.StreamWriter] $(Join-Path  $outDir $($_.BaseName + $_.Extension))
  $count = 0 
  # read line by line, replace CR with CRLF in each by saving it with $outstream.WriteLine
  while ($line = $file.ReadLine()) {
        $count += 1
        $outstream.WriteLine($line)
    }
  $file.close()
  $outstream.close()
  Write-Host ("$_`: " + $count + ' lines processed.')
}

1

对于CMD,一行只能使用LF:

powershell -NoProfile -command "((Get-Content 'prueba1.txt') -join \"`n\") + \"`n\" | Set-Content -NoNewline 'prueba1.txt'"

所以您可以创建一个 .bat 文件。

0
以下代码可以快速处理非常大的文件。
$file = New-Object System.IO.StreamReader -Arg "file1.txt"
$outstream = [System.IO.StreamWriter] "file2.txt"
$count = 0 

while ($line = $file.ReadLine()) {
      $count += 1
      $s = $line -replace "`n", "`r`n"
      $outstream.WriteLine($s)
  }

$file.close()
$outstream.close()

Write-Host ([string] $count + ' lines have been processed.')

4
在Windows系统上,这段代码可以将LF(换行符)转换为CRLF(回车符+换行符),这与原帖子想要的相反。但这其实是偶然发生的情况:System.IO.StreamReader可以读取仅包含LF的文件,并且.ReadLine()返回的一行文本“不包含”其原始的行结束符(无论是LF还是CRLF),因此-replace操作不起作用。 在Windows系统上,当使用.WriteLine()时,System.IO.StreamReader会自动添加CRLF,因此输出文件中会出现CRLF换行符。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接