bat файл для замены строки в текстовом файле - PullRequest
2 голосов
/ 21 декабря 2011

Этот вопрос часто задавался о 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, например, Проблема с поиском и заменойпакетный файл

Он создает усеченный файл следующим образом:

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

Пожалуйста, не стесняйтесь запрашивать разъяснения.Извиняюсь, если вы чувствуете, что на этот вопрос уже дан ответ.Я был бы очень признателен, если бы вы скопировали и вставили соответствующий код для меня, как я пытался в течение нескольких часов.

Бонус: можно ли заставить работать это автоматическое именование?"%%~nICleaned.mpl"

Ответы [ 4 ]

6 голосов
/ 21 декабря 2011

Самая большая проблема в вашем существующем коде - это то, что SetLocal enableDelayedExpansion неправильно написано - оно должно быть в цикле после set str=%%I.

Другие проблемы:

  • будет убирать строкиначиная с;
  • удалит начальные пробелы из каждой строки
  • удалит пустые (пустые) строки
  • выведет ECHO is off, если какие-либо строки станут пустыми или будут содержать только пробелы послезамена
  • добавит дополнительное пространство в конце каждой строки (не замечал этого, пока я не прочитал ответ Джеба)

Проблема оптимизации - использование >> может быть относительно медленным.Быстрее заключить весь цикл в (), а затем использовать >

Ниже приведено лучшее из того, что вы можете сделать с помощью пакета 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!" трюк, если новая строка не нужна.

Интересная функция (или ограничение, в зависимости отв перспективе)

  • Файлы стилей Unix, заканчивающиеся строками <LF>, будут преобразованы в стиль Windows со строками, заканчивающимися <CR><LF>

Существуют и другие решения, использующие пакетныезначительно быстрее, но все они имеют больше ограничений.

Обновление - я опубликовал новое чистое пакетное решение, которое способно выполнять поиск с учетом регистра и не имеет ограничений на поиск или замену содержимого строки.Он имеет больше ограничений по длине строки, конечным управляющим символам и формату строки.Производительность неплохая, особенно если количество замен низкое. http://www.dostips.com/forum/viewtopic.php?f=3&t=2710

Приложение

На основе приведенных ниже комментариев пакетное решение не будет работатьдля этой конкретной проблемы из-за ограничения длины строки.

Но этот код является хорошей основой для утилиты поиска и замены на основе пакета, если вы готовы мириться с ограничениями и относительно низкой производительностью пакета.

Доступны гораздо лучшие инструменты для обработки текста, хотя они не являются стандартными для Windows.Мой фаворит - sed в пакете GNU Utilities для Win32 .Утилиты бесплатны и не требуют никакой установки.

Вот решение sed для Windows с использованием утилит GNU

@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));
    }
  }
}
2 голосов
/ 23 декабря 2011

Пакетный файл ниже имеет те же ограничения предыдущих решений для символов, которые могут быть обработаны; Эти ограничения присущи всем программам на языке 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.

0 голосов
/ 21 декабря 2011

Вы определили delims=<space>, это плохая идея, если вы хотите сохранить свои строки, так как они разделяются после первого пробела.
Вы должны изменить это на FOR /F "tokens=* delims=" ....

Ваш echo !str! >> testCleaned.mpl всегда будет добавлять один дополнительный пробел к каждой строке, лучше использовать echo(!str!>>testCleaned.mpl.

Вы также потеряете все пустые строки и все восклицательные знаки во всех строках.

Вы также можете попробовать код Improved BatchSubstitute.bat

0 голосов
/ 21 декабря 2011

Я не специалист по пакетным файлам, поэтому не могу предложить прямое решение вашей проблемы.

Однако для решения вашей проблемы может быть проще использовать альтернативу пакетным файлам.

Например, я бы рекомендовал использовать http://www.csscript.net/ (если вы знаете C #). Этот инструмент позволит вам запускать C # -файлы, такие как пакетные файлы, но дает вам возможность писать свой сценарий с использованием C # вместо ужасного синтаксиса пакетного файла:)

Другой альтернативой будет python, если вы знаете python.

Но я думаю, дело в том, что такого рода задачи могут быть проще на другом языке программирования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...