使用batch / powershell脚本设置自定义行分隔符

时间:2016-12-21 16:09:06

标签: powershell batch-file

我有一个大文件> 1.5GB,它有'#@#@#'作为行分隔符。在通过Informatica处理它之前,我将用CRLF字符替换它。问题是,我在文件中有CR,LF字符,我需要在替换之前删除它们。我找到了几个选项来做到这一点,但由于尺寸的原因,我得到OutofMemory例外。

param
(
  [string]$Source,
  [string]$Destination
)

echo $Source
echo $Destination

$Writer = New-Object IO.StreamWriter $Destination
$Writer.Write( [String]::Join("", $(Get-Content $Source)) )
$Writer.Close()

我的问题是,无论如何将行分隔符设置为'#@#@#'然后逐行读取文件以删除CR,LF字符。

2 个答案:

答案 0 :(得分:3)

好吧,我的第一次尝试是无法忍受的慢。这是一个很好的解决方案,能够在2分48秒内处理1.8 GB文件: - )

我使用了混合批处理/ JScript,所以它可以在XP之后的任何Windows机器上运行 - 不需要第三方exe文件,也不需要任何编译。

我读写大约1 MB的块。逻辑实际上非常简单。

我用一个空格替换所有\ r \ n,用@ r \ n替换#@#@#。您可以轻松更改代码中的字符串值以满足您的需求。

<强> fixLines.bat

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment

::--- Batch section within JScript comment that calls the internal JScript ----
@echo off
setlocal disableDelayedExpansion

if "%~1" equ "" (
  echo Error: missing input argument
  exit /b 1
)
if "%~2" equ "" (
  set "out=%~f1.new"
) else (
  set "out=%~2"
)

<"%~1" >"%out%" cscript //nologo //E:JScript "%~f0"
if "%~2" equ "" move /y "%out%" "%~1" >nul

exit /b

----- End of JScript comment, beginning of normal JScript  ------------------*/
var delim='#@#@#',
    delimReplace='\r\n',
    nl='\r\n',
    nlReplace=' ',
    pos=0,
    str='';

var delimRegex=new RegExp(delim,"g"),
    nlRegex=new RegExp(nl,"g");

while( !WScript.StdIn.AtEndOfStream ) {
  str=str.substring(pos)+WScript.StdIn.Read(1000000);
  pos=str.lastIndexOf(delim)
  if (pos>=0) {
    pos+=delim.length;
    WScript.StdOut.Write(str.substring(0,pos).replace(nlRegex,nlReplace).replace(delimRegex,delimReplace));
  } else {
    pos=0
  }
}
if (str.length>pos) WScript.StdOut.Write(str.substring(pos).replace(nlRegex,nlReplace));

修复input.txt并将输出写入output.txt:

fixLines input.txt output.txt

覆盖原始文件test.txt

fixLines test.txt

为了解决问题,我尝试使用JREPL.BAT处理1.8 GB文件。我认为它不会起作用,因为它必须将整个文件加载到内存中。无论计算机中安装了多少内存 - JScript的最大字符串大小限制为2GB。我认为还有其他限制因素可以发挥作用。

jrepl "\r?\n:#@#@#" " :\r\n" /m /x /t : /f input.txt /o output.txt

命令失败并出现“Out Of Memory”错误需要5分钟。然后我的计算机需要很长时间才能从严重的内存滥用中恢复过来。

以下是我一次读取和写入一个字符的原始自定义批处理/ JScript解决方案。

<强> slow.bat

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment

::--- Batch section within JScript comment that calls the internal JScript ----
@echo off
setlocal disableDelayedExpansion

if "%~1" equ "" (
  echo Error: missing input argument
  exit /b 1
)
if "%~2" equ "" (
  set "out=%~f1.new"
) else (
  set "out=%~2"
)

<"%~1" >"%out%" cscript //nologo //E:JScript "%~f0"
if "%~2" equ "" move /y "%out%" "%~1" >nul

exit /b

----- End of JScript comment, beginning of normal JScript  ------------------*/
var delim='#@#@#',
    delimReplace='\r\n',
    nlReplace=' ',
    read=1,
    write=2,
    pos=0,
    char;

while( !WScript.StdIn.AtEndOfStream ) {
  chr=WScript.StdIn.Read(1);
  if (chr==delim.charAt(pos)) {
    if (++pos==delim.length) {
      WScript.StdOut.Write(delimReplace);
      pos=0;
    }
  } else {
    if (pos) {
      WScript.StdOut.Write(delim.substring(0,pos));
      pos=0;
    }
    if (chr=='\n') {
      WScript.StdOut.Write(nlReplace);
    } else if (chr!='\r') {
      WScript.StdOut.Write(chr);
    }
  }
}
if (pos) WScript.StdOut.Write(delim.substring(0,pos));

它有效,但它是一只狗。以下是处理155 MB文件的计时结果摘要:

slow.bat     3120 sec  (52 min)
jrepl.bat      55 sec
fixLines.bat   15 sec

我确认所有三种解决方案都给出了相同的结果。

答案 1 :(得分:2)

概念上简单且内存效率高,但 PowerShell解决方案:

这个PowerShell(v2 +)解决方案是,但它在概念上很简单,你不应该耗尽内存,因为使用{{1}一次处理一个输入行}作为行分隔符。

注意:此解决方案结合了您的两个步骤:

  • 它用每个单独的空格替换原始换行符,

  • 并用换行符替换每个#@#@#序列。

#@#@#

注意:

  • 当您使用# Create sample input file. @' line 1 starts here and ends here#@#@#line 2 is all on one line#@#@#line 3 spans two lines#@#@# '@ > file # Determine the input file. $inFile = 'file' # Create the output file. $outFile = 'out' $null = New-Item -Type File $outFile Get-Content -Delimiter '#@#@#' $inFile | % { Add-Content -Value ($_.Replace("`r`n", " ").Replace($sep, '')) $outFile } 时,在通过管道传递的每个项目中指定的分隔符包含(与默认行为不同,默认分隔符(换行符)为剥离)。

  • -Delimiter会自动在其输出中添加一个尾随的CRLF(在PSv5 +中,可以使用Add-Content来抑制它。)

  • 该方法使用-NoNewLine类型的[string]方法,而不是PowerShell灵活的,基于正则表达式的.Replace()运算符,因为-replace执行 literal 替换,速度更快(等效命令为
    .Replace()
    也就是说,速度增益可以忽略不计;它是文件I / O部分占用大部分时间。)

通过按需编译C#代码

,提供更快的PowerShell解决方案

dbenham's clever and elegant batch + JScript solution明显快于上述PowerShell解决方案。

以下是他在按需编译的PowerShell脚本中使用 C#代码的方法的改编

编译非常快(在我2012年末的iMac上大约0.3秒),使用编译代码处理文件可以显着提高性能。
另请注意,每个会话只执行一次编译,因此后续调用不会支付此罚金。

使用下面打印的脚本处理~1 GB文件(通过重复上述示例文件的内容创建)会产生以下结果:

Add-Content -Value (($_ -replace '\r\n', ' ') -replace '#@#@#') $outFile

实际应用程序中的执行时间因许多因素而异,但根据下面评论中提到的@ dbenham的时序,按需编译解决方案的速度是批处理+ JScript解决方案的两倍。

快速PowerShell解决方案的源代码:

Compiling...
Processing file...
Completed:
  Compilation time:      00:00:00.2343647
  File-processing time:  00:00:26.0714467
  Total:                 00:00:26.3278546