使用bat文件,删除XML中字符串的首次出现

时间:2016-05-03 16:31:08

标签: string batch-file

我想删除所有出现的字符串     <!-- 和     --> 从XML EXCEPT开始,我想保留一条评论。我不想删除这些字符串包含的任何文本。字符串都出现在不同的行上。我可以使用Delete certain lines in a txt file via a batch file中的提案删除字符串的所有实例,但我不确定跳过第一个的最佳方法(使用for循环?)。

XML看起来像这样:

<?xml version="1.0"?> <!-- REVISION HISTORY and file descriptions which I want to keep commented --> <!-- some code I want to uncomment --> <!-- some more code I want to uncomment -->

2 个答案:

答案 0 :(得分:1)

The best way of handling any structured markup language (XML, HTML, JSON, etc) is to parse it with the appropriate interpreter. Hacking and scraping as flat text is inviting trouble if the formatting ever changes. Save this with a .bat extension and give it a shot.

@if (@CodeSection == @Batch) @then

@echo off
setlocal

set "infile=test.xml"
set "outfile=test.xml"
cscript /nologo /e:Jscript "%~f0" "%infile%" "%outfile%" && echo Done.

goto :EOF
@end // end batch / begin JScript

var DOM = WSH.CreateObject('Msxml2.DOMDocument.6.0'),
    args = { load: WSH.Arguments(0), save: WSH.Arguments(1) };

DOM.load(args.load);
DOM.async = false;

// sanity check the XML
if (DOM.parseError.errorCode) {
    var e = DOM.parseError;
    WSH.StdErr.WriteLine('Error in ' + args.load + ' line ' + e.line + ' char '
        + e.linepos + ':\n' + e.reason + '\n' + e.srcText);
    WSH.Quit(1);
}

var comments = DOM.documentElement.selectNodes('//comment()');

// This will delete all but the first comment.
for (var i=comments.length; --i;) {
    comments[i].parentNode.removeChild(comments[i]);
}
DOM.save(args.save);

Edit: I guess if you're working with invalid XML, then manipulating the text as flat text is probably the best solution. Here's a modified version that does this:

@if (@CodeSection == @Batch) @then

@echo off
setlocal

set "infile=test.xml"
set "outfile=test2.xml"
cscript /nologo /e:Jscript "%~f0" "%infile%" "%outfile%" && echo Done.

goto :EOF
@end // end batch / begin JScript

var args = { load: WSH.Arguments(0), save: WSH.Arguments(1) },
    fso = WSH.CreateObject('Scripting.FileSystemObject'),
    fHand = fso.OpenTextFile(args.load, 1),
    matches = 0,
    XML = fHand.ReadAll().replace(/<!--|-->/g, function(m) {
        return (matches++ > 1) ? '' : m;
    });

fHand.Close();
fHand = fso.CreateTextFile(args.save, true);
fHand.Write(XML);
fHand.Close();

Or if you prefer PowerShell, here's a Batch + PowerShell hybrid script that does the same thing using the same logic.

<# : batch portion

@echo off
setlocal

set "infile=test.xml"
set "outfile=test2.xml"
powershell "iex (${%~f0} | out-string)" && echo Done.

goto :EOF
: end Batch / begin PowerShell hybrid code #>

[regex]::replace(
    (gc $env:infile | out-string),
    "<!--|-->",
    {
        if ($matches++ -gt 1) {
            ""
        } else {
            $args[0].Value
        }
    }
) | out-file $env:outfile -force

答案 1 :(得分:0)

原始答案如下;这是针对手头任务开发的更简单的方法:

以下是基于findstr命令的纯解决方案 - 让我们称之为remove-lines.bat

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "FILE=%~1"           & rem // 1st argument is the original file
set "FILE_NEW=%~2"       & rem // 2nd argument is the modified file
set "SKIP_UNTIL=-->"     & rem // don't modify lines up to 1st occurrence
set REMOVE="<^!--" "-->" & rem // no `?` and `*` allowed here!
                           rem // `%` --> `%%` & `!`  --> `^!`

if defined FILE (set FILE="%FILE%") else set "FILE="
if not defined FILE_NEW set "FILE_NEW=con"

> "%FILE_NEW%" (
    set "FLAG="
    for /F "delims=" %%L in ('findstr /N /R "^" %FILE%') do (
        set "LINE=%%L"
        setlocal EnableDelayedExpansion
        set "LINE=!LINE:*:=!"
        if defined FLAG (
            set "FOUND="
            for %%S in (!REMOVE!) do (
                echo(| set /P "=_!LINE!" | > nul findstr /L /M /C:"_%%S"
                if not ErrorLevel 1 set "FOUND=#"
            )
            if not defined FOUND echo(!LINE!
        ) else (
            echo(!LINE!
        )
        echo(| set /P "=_!LINE!" | > nul findstr /L /M /C:"_!SKIP_UNTIL!"
        if ErrorLevel 1 (endlocal) else endlocal & set "FLAG=#"
    )
)

endlocal
exit /B

基本上,脚本通过for /F %%L循环 1)读取文本文件。在这个循环的主体中,有一个标准的for %%S循环,它遍历由变量REMOVE定义的字符串。在此循环内部,只要在当前行 2)中找到任何一个字符串,就会设置变量FOUND。在循环之后,仅当FOUND仍为空时才返回该行,这意味着没有找到任何字符串。只有在设置了变量FLAG的情况下才能完成所有这些搜索,这是在变量SKIP_UNTIL中的字符串第一次遇到 2)时完成的。由于此检查是在检查变量FLAG之后完成的,因此内部循环不会处理受影响的行本身。只要未设置FLAG,每个读取行都会未经编辑返回。

1)这样的循环忽略空行;为了解决这个问题,findstr命令暂时在每行之前有一个行号,后来在循环体中被删除;这样空行就不会丢失。
2)如果要强制搜索字符串出现在当前行的开头或末尾,请将相应的开关/B/E添加到findstr命令;要强制整行与搜索字符串匹配,请添加/X开关。

要将其用于XML文件,例如当前目录中的data.xml,并将结果写入同一位置的文件data_new.xml,请键入以下命令行:

"remove-lines.bat" "data.xml" "data_new.xml"

这是最初的答案,描述了一个非常复杂的方法,有两个脚本,一个调用另一个脚本,这是通过这种方式完成的,因为第一个(子)脚本已经可用(尽管它有是为完全不同的东西而开发的:

这是一个纯粹的解决方案,基于一个简单但非常灵活的搜索和替换脚本 - 让我们称之为search+replace.bat

@echo off
setlocal DisableDelayedExpansion

rem /* Define pairs of search/replace strings here, separated by spaces,
rem    each one in the format `"<search_string>=<replace_string>"`;
rem    the `""` are mandatory; `=` separates search from replace string;
rem    the replace string may be empty, but the search string must not;
rem    if the `=` is omitted, the whole string is taken as search string;
rem    both strings must not contain the characters `=`, `*`, `?` and `"`;
rem    the search string must not begin with `~`;
rem    exclamation marks must be escaped like `^!`;
rem    percent signs must be doubled like `%%`;
rem    the search is done in a case-insensitive manner;
rem    the replacements are done in the given order: */
set STRINGS="<^!--=" "-->="

set "FILE=%~1"
rem // provide a file by command line argument;
rem // if none is given, the console input is taken;
if defined FILE (set FILE="%FILE%") else set "FILE="

set "SKIP=%~2"
rem // provide number of lines to skip optionally;
set /A SKIP+=0

for /F "delims=" %%L in ('findstr /N /R "^" %FILE%') do (
    set "LINE=%%L"
    for /F "delims=:" %%N in ("%%L") do set "LNUM=%%N"
    setlocal EnableDelayedExpansion
    set "LINE=!LINE:*:=!"
    if !LNUM! GTR %SKIP% (
        for %%R in (!STRINGS!) do (
            if defined LINE (
                for /F "tokens=1,2 delims== eol==" %%S in ("%%~R") do (
                    set "LINE=!LINE:%%S=%%T!"
                )
            )
        )
    )
    echo(!LINE!
    endlocal
)

endlocal
exit /B

基本上,脚本通过for /F %%L循环 3)读取文本文件。在这个循环的主体中,有一个标准的for %%R循环,它遍历由变量STRINGS定义的搜索/替换字符串对。在这一个内部,每个字符串对被拆分为搜索并用另一个for /F %%S循环 4)替换字符串。实际的字符串替换使用标准子字符串替换语法完成 - 输入set /?以获取详细信息。

3)这样的循环忽略空行;为了解决这个问题,findstr命令暂时在每行之前有一个行号,后来在循环体中被删除;这样空行就不会丢失。
4)这将该对分割为(第一个)=符号,然后将这两个部分再次组合在一起,其间有=个符号;这通常不是必要的,但是为了避免在没有给出=符号时出现问题而完成。

STRINGS变量已根据您的需要进行调整,因此要删除文字字符串<!---->(换句话说,要用空字符串替换它们) - 请参阅脚本之上的相关注释。

要将其用于XML文件(例如当前目录中的data.xml),请键入以下命令行:

"search+replace.bat" "data.xml" 0

生成的文本将写入控制台窗口。要将其放入文件中,请使用重定向:

("search+replace.bat" "data.xml" 0)> "data_new.xml"

请注意,不得为输入和输出指定相同的文件。

0(可以省略)是一个可选参数,用于指定应从开头排除多少行以进行处理。这些行未经编辑返回。

从文本文件中删除字符串可能会导致多个空行,例如样本XML数据。要摆脱它们,可以使用以下命令行(输入命令提示符):

(for /F delims^=^ eol^= %F in ('^""search+replace.bat" "data.xml" 0^"') do @echo(%F) > "data_new.xml"

要在批处理文件中使用此代码段,您需要将%%符号加倍。

由于您希望保留第一个<!-- / -->评论(根据您的示例数据,一行内没有多条评论),您可以使用以下脚本,该脚本确定data.xml中包含-->的第一行的编号,然后使用该文件和该行号作为参数调用search+replace.bat,捕获脚本的返回数据,删除所有空行并将结果写入新文件data_new.xml

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "FILE=data.xml"
set "FILE_NEW=data_new.xml"
set "SEEK_TEXT=-->"
set "FIRST=#" &rem (set to empty string for last occurrence)

rem // Search for the first (or last) occurrence of `%SEEK%`:
set /A LINE_NUM=0
for /F "delims=:" %%N in ('
    findstr /N /L /C:"%SEEK_TEXT%" "%FILE%"
') do (
    set "LINE_NUM=%%N"
    if defined FIRST goto :CONTINUE
)
:CONTINUE

rem // Call sub-script to search and replace (remove) strings,
rem // remove all empty lines and write result to new file:
(
    for /F delims^=^ eol^= %%F in ('
        ^""%~dp0search+replace.bat" "%FILE%" %LINE_NUM%^"
    ') do (
        echo(%%F
    )
) > "%FILE_NEW%"

endlocal
exit /B