bat文件替换文本文件中的字符串

时间:2011-12-21 11:26:14

标签: string batch-file text-files

这个问题已经在stackoverflow上被问了很多,但我似乎无法使它工作。任何提示赞赏。这是一个文本文件(扩展名.mpl),其中包含需要删除的有问题的文本:

plotsetup('ps', 'plotoutput = "plotfile.eps"', 'plotoptions' = "color=rgb,landscape,noborder");
print(PLOT3D(MESH(Array(1..60, 1..60, 1..3, [[[.85840734641021,0.,-0.],
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],[.857971665313419,.0917163905694189,-.16720239349226],
... more like that ...
[.858407346410207,-3.25992468340355e-015,5.96532373555817e-015]]], datatype = float[8], order = C_order)),SHADING(ZHUE),STYLE(PATCHNOGRID),TRANSPARENCY(.3),LIGHTMODEL(LIGHT_4),ORIENTATION(35.,135.),SCALING(CONSTRAINED),AXESSTYLE(NORMAL)));

我想删除以下的每个实例:

[HFloat(undefined),HFloat(undefined),HFloat(undefined)],

并且有数千个这样的实例!注意:要删除方括号和逗号。没有空间,所以我有以下页面和页面:

[HFloat(undefined),HFloat(undefined),HFloat(undefined)],   
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],   
[HFloat(undefined),HFloat(undefined),HFloat(undefined)],

我不会在这里列出所有失败的尝试。下面是我最接近的:

@echo off

SetLocal 
cd /d %~dp0

if exist testCleaned.mpl del testCleaned.mpl

SetLocal EnableDelayedExpansion

Set OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],
Set NewString=

pause

FOR /F "tokens=* delims= " %%I IN (test.mpl) DO (
    set str=%%I
    set str=!str:OldString=NewString!
    echo !str! >> testCleaned.mpl
    endlocal
)

EndLocal

以上内容是根据网络上的代码片段串联起来的,特别是在stackoverflow,例如, Problem with search and replace batch file

它的作用是产生一个截断的文件,如下所示:

plotsetup('ps', 'plotoutput = "plotfile.eps"', 'plotoptions' = "color=rgb,landscape,noborder"); 
!str! 

请不要犹豫,要求澄清。如果您觉得这个问题已经得到解答,请道歉。如果你能为我复制粘贴相关代码,我将非常感激,因为我已经尝试了几个小时。

额外奖励:这种自动命名可以起作用吗? “%%~nICleaned.mpl

4 个答案:

答案 0 :(得分:6)

您现有代码的最大问题是SetLocal enableDelayedExpansion被错放了 - 它应该在set str=%%I之后的循环内。

其他问题:

  • 将删除以;开头的行
  • 将从每行中删除前导空格
  • 将删除空白(空)行
  • 如果任何行变空或者在替换后只包含空格
  • ,则
  • 将打印ECHO is off
  • 会在每一行的末尾添加额外的空格(直到我读到jeb的答案才注意到这一点)

优化问题 - 使用>>可能相对较慢。将整个循环包含在()中然后使用>

会更快

下面是关于Windows批处理的最佳方法。我按要求自动命名输出,做得更好 - 它会自动保留原始名称的扩展名。

@echo off
SetLocal
cd /d %~dp0
Set "OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
Set "NewString="
set file="test.mpl"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
(
  for /f "skip=2 delims=" %%a in ('find /n /v "" %file%') do (
    set "ln=%%a"
    setlocal enableDelayedExpansion
    set "ln=!ln:*]=!"
    if defined ln set "ln=!ln:%OldString%=%NewString%!"
    echo(!ln!
    endlocal
  )
)>%outFile%

已知限制

  • 限制在每行8k以下,无论是在替换之前还是之后
  • 搜索字符串不能包含=!,也不能以*~
  • 开头
  • 替换字符串不能包含!
  • 搜索部分搜索和替换不区分大小写
  • 即使原版不是
  • ,最后一行也会以换行符<CR><LF>结束

除了第一个限制之外的所有限制都可以消除,但它需要大量的代码,而且会非常慢。解决方案需要逐行字符搜索每一行。最后一个限制需要进行一些尴尬的测试,以确定最后一行是否是换行符,如果没有换行,则必须使用<nul SET /P "ln=!ln!"技巧打印最后一行。

有趣的特征(或限制,取决于观点)

  • 带有<LF>的Unix样式文件结尾行将转换为Windows样式,行以<CR><LF>
  • 结尾

使用批处理的其他解决方案明显更快,但它们都有更多限制。

更新 - 我发布了一个新的纯批处理解决方案,它能够进行区分大小写的搜索,对搜索或替换字符串内容没有限制。它对行长度,尾随控制字符和行格式有更多限制。性能也不错,特别是如果替换次数很少。 http://www.dostips.com/forum/viewtopic.php?f=3&t=2710

<强>附录

根据以下评论,由于行长度限制,批处理解决方案不适用于此特定问题。

但是这个代码是基于批处理的搜索和替换实用程序的良好基础,只要您愿意忍受批处理的限制和相对较差的性能。

有更好的文本处理工具可用,但它们不是Windows的标准。我最喜欢的是GNU Utilities for Win32包内的sed。这些实用程序是免费的,不需要任何安装。

以下是使用GNU实用程序的Windows的sed解决方案

@echo off
setlocal
cd /d %~dp0
Set "OldString=\[HFloat(undefined),HFloat(undefined),HFloat(undefined)\],"
Set "NewString="
set file="test.mpl"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
sed -e"s/%OldString%/%NewString%/g" <%file% >%outfile%


更新2013-02-19

如果您在具有禁止安装从网上下载的可执行文件的规则的网站上工作,则可能无法选择使用sed。

JScript具有良好的正则表达式处理能力,它是所有现代Windows平台(包括XP)的标准配置。它是在Windows平台上执行搜索和替换操作的不错选择。

我编写了一个混合JScript / Batch搜索和替换脚本(REPL.BAT),它很容易从批处理脚本调用。少量代码提供了许多强大的功能;没有像sed那么强大,但足以处理这个任务,以及许多其他任务。它也非常快,比任何纯批量解决方案都快得多。它也没有任何固有的线路长度限制。

这是一个批处理脚本,它使用我的REPL.BAT实用程序来完成任务。

@echo off
setlocal
cd /d %~dp0
Set "OldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
Set "NewString="
set file="test.txt"
for %%F in (%file%) do set outFile="%%~nFCleaned%%~xF"
pause
call repl OldString NewString le <%file% >%outfile%

我使用L选项指定文字搜索字符串而不是正则表达式,E选项通过名称传递搜索和通过环境变量替换字符串,而不是使用字符串文字在命令行上。

这是上面代码调用的REPL.BAT实用程序脚本。完整的文档包含在脚本中。

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

::************ Documentation ***********
:::
:::REPL  Search  Replace  [Options  [SourceVar]]
:::REPL  /?
:::
:::  Performs a global search and replace operation on each line of input from
:::  stdin and prints the result to stdout.
:::
:::  Each parameter may be optionally enclosed by double quotes. The double
:::  quotes are not considered part of the argument. The quotes are required
:::  if the parameter contains a batch token delimiter like space, tab, comma,
:::  semicolon. The quotes should also be used if the argument contains a
:::  batch special character like &, |, etc. so that the special character
:::  does not need to be escaped with ^.
:::
:::  If called with a single argument of /? then prints help documentation
:::  to stdout.
:::
:::  Search  - By default this is a case sensitive JScript (ECMA) regular
:::            expression expressed as a string.
:::
:::            JScript syntax documentation is available at
:::            http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
:::  Replace - By default this is the string to be used as a replacement for
:::            each found search expression. Full support is provided for
:::            substituion patterns available to the JScript replace method.
:::            A $ literal can be escaped as $$. An empty replacement string
:::            must be represented as "".
:::
:::            Replace substitution pattern syntax is documented at
:::            http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
:::  Options - An optional string of characters used to alter the behavior
:::            of REPL. The option characters are case insensitive, and may
:::            appear in any order.
:::
:::            I - Makes the search case-insensitive.
:::
:::            L - The Search is treated as a string literal instead of a
:::                regular expression. Also, all $ found in Replace are
:::                treated as $ literals.
:::
:::            E - Search and Replace represent the name of environment
:::                variables that contain the respective values. An undefined
:::                variable is treated as an empty string.
:::
:::            M - Multi-line mode. The entire contents of stdin is read and
:::                processed in one pass instead of line by line. ^ anchors
:::                the beginning of a line and $ anchors the end of a line.
:::
:::            X - Enables extended substitution pattern syntax with support
:::                for the following escape sequences:
:::
:::                \\     -  Backslash
:::                \b     -  Backspace
:::                \f     -  Formfeed
:::                \n     -  Newline
:::                \r     -  Carriage Return
:::                \t     -  Horizontal Tab
:::                \v     -  Vertical Tab
:::                \xnn   -  Ascii (Latin 1) character expressed as 2 hex digits
:::                \unnnn -  Unicode character expressed as 4 hex digits
:::
:::                Escape sequences are supported even when the L option is used.
:::
:::            S - The source is read from an environment variable instead of
:::                from stdin. The name of the source environment variable is
:::                specified in the next argument after the option string.
:::

::************ Batch portion ***********
@echo off
if .%2 equ . (
  if "%~1" equ "/?" (
    findstr "^:::" "%~f0" | cscript //E:JScript //nologo "%~f0" "^:::" ""
    exit /b 0
  ) else (
    call :err "Insufficient arguments"
    exit /b 1
  )
)
echo(%~3|findstr /i "[^SMILEX]" >nul && (
  call :err "Invalid option(s)"
  exit /b 1
)
cscript //E:JScript //nologo "%~f0" %*
exit /b 0

:err
>&2 echo ERROR: %~1. Use REPL /? to get help.
exit /b

************* JScript portion **********/
var env=WScript.CreateObject("WScript.Shell").Environment("Process");
var args=WScript.Arguments;
var search=args.Item(0);
var replace=args.Item(1);
var options="g";
if (args.length>2) {
  options+=args.Item(2).toLowerCase();
}
var multi=(options.indexOf("m")>=0);
var srcVar=(options.indexOf("s")>=0);
if (srcVar) {
  options=options.replace(/s/g,"");
}
if (options.indexOf("e")>=0) {
  options=options.replace(/e/g,"");
  search=env(search);
  replace=env(replace);
}
if (options.indexOf("l")>=0) {
  options=options.replace(/l/g,"");
  search=search.replace(/([.^$*+?()[{\\|])/g,"\\$1");
  replace=replace.replace(/\$/g,"$$$$");
}
if (options.indexOf("x")>=0) {
  options=options.replace(/x/g,"");
  replace=replace.replace(/\\\\/g,"\\B");
  replace=replace.replace(/\\b/g,"\b");
  replace=replace.replace(/\\f/g,"\f");
  replace=replace.replace(/\\n/g,"\n");
  replace=replace.replace(/\\r/g,"\r");
  replace=replace.replace(/\\t/g,"\t");
  replace=replace.replace(/\\v/g,"\v");
  replace=replace.replace(/\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}/g,
    function($0,$1,$2){
      return String.fromCharCode(parseInt("0x"+$0.substring(2)));
    }
  );
  replace=replace.replace(/\\B/g,"\\");
}
var search=new RegExp(search,options);

if (srcVar) {
  WScript.Stdout.Write(env(args.Item(3)).replace(search,replace));
} else {
  while (!WScript.StdIn.AtEndOfStream) {
    if (multi) {
      WScript.Stdout.Write(WScript.StdIn.ReadAll().replace(search,replace));
    } else {
      WScript.Stdout.WriteLine(WScript.StdIn.ReadLine().replace(search,replace));
    }
  }
}

答案 1 :(得分:2)

下面的批处理文件对可以处理的字符的先前解决方案具有相同的限制;这些限制是所有Batch语言程序所固有的。但是,如果文件很大并且要替换的行不是太多,则此程序应该运行得更快。没有替换字符串的行不会被处理,而是直接复制到输出文件。

@echo off
setlocal EnableDelayedExpansion
set "oldString=[HFloat(undefined),HFloat(undefined),HFloat(undefined)],"
set "newString="
findstr /N ^^ inFile.mpl > numberedFile.tmp
find /C ":" < numberedFile.tmp > lastLine.tmp
set /P lastLine=<lastLine.tmp
del lastLine.tmp
call :ProcessLines < numberedFile.tmp > outFile.mpl
del numberedFile.tmp
goto :EOF

:ProcessLines
set lastProcessedLine=0
for /F "delims=:" %%a in ('findstr /N /C:"%oldString%" inFile.mpl') do (
    call :copyUpToLine %%a
    echo(!line:%oldString%=%newString%!
)
set /A linesToCopy=lastLine-lastProcessedLine
for /L %%i in (1,1,%linesToCopy%) do (
    set /P line=
    echo(!line:*:=!
)
exit /B

:copyUpToLine number
set /A linesToCopy=%1-lastProcessedLine-1
for /L %%i in (1,1,%linesToCopy%) do (
    set /P line=
    echo(!line:*:=!
)
set /P line=
set line=!line:*:=!
set lastProcessedLine=%1
exit /B

如果您可以对其他解决方案进行计时测试并发布结果,我将不胜感激。

编辑:我更改了等效的set /A lastProcessedLine+=linesToCopy+1行,但更快set lastProcessedLine=%1

答案 2 :(得分:0)

我不是批处理文件的专家,因此我无法直接解决您的问题。

但是,要解决您的问题,使用替代批处理文件可能更简单。

例如,我建议使用http://www.csscript.net/(如果你知道C#)。此工具允许您运行C#文件,如批处理文件,但是您可以使用C#编写脚本,而不是使用可怕的批处理文件语法:)

另一种选择是python,如果你知道python。

但我想重点是,这种任务在另一种编程语言中可能更容易。

答案 3 :(得分:0)

你定义了delims=<space>,如果你想要保留你的线条,这是一个坏主意,因为它在第一个空格后分​​裂。
您应该将其更改为FOR /F "tokens=* delims=" ...

您的echo !str! >> testCleaned.mpl将始终为每一行添加一个额外空格,最好使用echo(!str!>>testCleaned.mpl

您还将丢失所有空行以及所有行中的所有感叹号。

您还可以尝试Improved BatchSubstitute.bat

的代码