Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- @echo off
- rem v2019-04-24_1
- setlocal disabledelayedexpansion enableextensions || (echo Unable to enable extensions.& exit /b)
- set errorlevel=1&if %errorlevel% == 1 (echo This script must be run under cmd.exe.& exit /b 1) else set errorlevel=
- set _Date=%date%&set _Time=%time%
- rem Пользовательский код помещаем в блоки "User code":
- rem - в "User code 1" определяются переменные, управляющие поведением
- rem служебного кода, присваиваются значения по умолчанию переменным,
- rem определяемым через парсинг ini-файла, и переключается кодовая страница;
- rem - в "User code 2" содержится собственно код скрипта;
- rem - в "User code 3" - код вывода хелпа и восстановления ini-файла по
- rem умолчанию.
- rem Порядок заполнения блоков: 1-3-2.
- rem Внесение изменений в код восстановления ini-файла по умолчанию (блок 3)
- rem должно сопровождаться изменениями в присвоении переменным значений по
- rem умолчанию (блок 1).
- rem Код вне блоков "User code" является служебным, независимым от кода
- rem конкретного скрипта. Его правка должны сопровождаться изменением версии
- rem и переносом новой версии кода в скрипт-шаблон.
- rem В конце скрипта - место для заметок и changelog.
- rem ==============================> User code 1 ===============================
- set _DisplayBanner=^
- echo/^
- &echo Replace Last Occurrence v1.0.7^
- &echo (c^^) 2019 GCRaistlin. Licensed under GNU GPL v3.^
- &echo/
- rem Если флаг установлен, скрипт при любой ошибке в служебной части скрипта,
- rem кроме Invalid syntax, возвращает errorlevel 1.
- if defined _SameError set _SameError=
- rem Устанавливаем значения по умолчанию для переменных, определяемых через
- rem парсинг ini-файла, в т. ч. пустые.
- if defined _Bare set _Bare=
- if defined _ExecTime set _ExecTime=
- if defined _PauseOnExit set _PauseOnExit=
- if defined _RetainTmpFiles set _RetainTmpFiles=
- if defined T set T=
- if defined S set S=
- if defined R set R=
- if defined T_CaseInsensitive set T_CaseInsensitive=
- if defined S_CaseInsensitive set S_CaseInsensitive=
- rem Рабочая кодовая страница должна соответствовать кодовой странице скрипта, а
- rem кодовая страница скрипта - кодовой странице ini-файла.
- >nul chcp 1251
- rem >>> Переходим в блок "User code 2".
- rem =============================== User code 1 <==============================
- rem v2019-05-03_1
- rem Перед парсингом ini-файла определяем только необходимые для парсинга
- rem переменные-подпрограммы (ППП), т. к. при парсинге значения переменных могут
- rem измениться => их надо принудительно восстанавливать.
- rem Подробнее про ППП см. в конце скрипта.
- rem ППП _EnableDE
- rem v2019-04-18_2
- rem Включает delayed expansion, если оно выключено, и запоминает номер вызова
- rem ППП, при котором произошло включение.
- rem Изменение состояния delayed expansion с помощью _EnableDE и _DisableDE
- rem позволяет избежать лишних SETLOCAL/ENDLOCAL, выполняемых только для его
- rem переключения, при неизвестном текущем состоянии delayed expansion.
- rem Увеличивает счетчик вызовов (един для _EnableDE и _DisableDE).
- rem Возвращать delayed expansion в состояние, которое было перед вызовом ППП,
- rem следует через _Endlocal. В промежутке между _EnableDE (_DisableDE) и
- rem _Endlocal не должно быть незакрытых SETLOCAL.
- rem _EnableDE и _DisableDE должны вызываться в паре с _Endlocal.
- rem Vars in/out: _SetlocalCt (счетчик вызовов), _DEOn (вызов, на котором
- rem включили delayed expansion).
- rem Требует установки начальных значений:
- set _SetlocalCt=0
- if defined _DEOn set _DEOn=
- if defined _DEOff set _DEOff=
- rem Exit code: 0
- rem Usage on level 1 and 2:
- rem %_EnableDE%^&
- rem Usage on level 2 and deeper:
- rem %_EnableDE:^=^^^%^^^&
- set _EnableDE=^
- (if _ neq _!! (^
- (setlocal enabledelayedexpansion)^^^&^
- (set /a _DEOn=_SetlocalCt+1)^^^&^
- set /a _SetlocalCt+=1^
- ) else set /a _SetlocalCt+=1)
- rem ППП _Endlocal
- rem v2019-04-18_1
- rem Возвращает delayed expansion в состояние, бывшее до последнего вызова
- rem _EnableDE или _DisableDE.
- rem Уменьшает счетчик вызовов.
- rem Должна вызываться в паре с _EnableDE или _DisableDE.
- rem Vars in/out: _SetlocalCt, _DEOn, _DEOff.
- rem Usage on level 1 and 2:
- rem %_Endlocal%^&
- rem Usage on level 2 and deeper:
- rem %_Endlocal:^=^^^%^^^&
- set _Endlocal=^
- (if errorlevel 1 (^
- ((2^^^> nul set /a 1/(_DEOn-_SetlocalCt^^^)/(_DEOff-_SetlocalCt^^^))^^^&^^^&^
- (set /a _SetlocalCt-=1) ^^^|^^^| endlocal)^^^&^^^&^
- color 00^
- ) else (^
- ((2^^^> nul set /a 1/(_DEOn-_SetlocalCt^^^)/(_DEOff-_SetlocalCt^^^))^^^&^^^&^
- (set /a _SetlocalCt-=1) ^^^|^^^| endlocal)^
- ))
- rem ППП _IsFile
- rem v2019-03-27_3
- rem Возвращает ошибку 0, если объект является файлом, иначе ошибку 1.
- rem %%! - имя переменной, содержащей имя объекта. Не должно содержать "!"
- rem (возвращает непредсказуемый результат при наличии).
- rem Менять "not ==" на "neq" нельзя: _IsDir получаем из _IsFile заменой,
- rem которая не должна влиять на _EnableDE, поэтому используем разный синтаксис.
- rem Usage on level 1:
- rem for /f %%! in ("<obj_var>") do %_IsFile% && echo A file || echo Not a file
- rem Usage on level 2:
- rem (for /f %%! in ("<obj_var>") do %_IsFile% ^&^& echo A file ^|^| echo Not a file)^&
- rem Usage on level 2 and deeper:
- rem (for /f %%! in ("<obj_var>") do %_IsFile:^=^^^% ^^^&^^^& (echo A file) ^^^|^^^| echo Not a file)^^^&
- set _IsFile=^
- (if defined %%! (^
- %_EnableDE:^=^^^%^^^&^
- (if "!%%!:**=!" == "!%%!!" (^
- if "!%%!:?=!" == "!%%!!" (^
- if exist "!%%!!" (^
- (for %%A in ("!%%!!") do set Attr=%%~aA)^^^&^
- if /i not "-" == "!Attr:~,1!" color 00^
- ) else color 00^
- ) else color 00^
- ) else color 00) ^^^&^^^&^ %_Endlocal:^=^^^% ^^^|^^^|^
- (%_Endlocal:^=^^^%^^^& color 00)^
- ) else color 00)
- rem ППП _IsReadable
- rem v2019-03-27_4
- rem Возвращает ошибку 0, если файл доступен для чтения, иначе ошибку 1.
- rem %%! - имя переменной, содержащей имя объекта. Не должно содержать "!"
- rem (возвращает непредсказуемый результат при наличии).
- rem Usage on level 1:
- rem for /f %%! in ("<obj_var>") do %_IsReadable% && echo A readable file || echo Not a readable file
- rem Usage on level 2:
- rem (for /f %%! in ("<obj_var>") do %_IsReadable% ^&^& echo A readable file ^|^| echo Not a readable file)^&
- rem Usage on level 2 and deeper:
- rem (for /f %%! in ("<obj_var>") do %_IsReadable:^=^^^% ^^^&^^^& (echo A readable file) ^^^|^^^| echo Not a readable file)^^^&
- set _IsReadable=^
- (if defined %%! (^
- %_EnableDE:^=^^^%^^^&^
- %_IsFile:^=^^^% ^^^&^^^& (^^^> nul 2^^^>^^^&1 copy "!%%!!" nul) ^^^&^^^&^
- %_Endlocal:^=^^^% ^^^|^^^| (%_Endlocal:^=^^^%^^^& color 00)^
- ) else color 00)
- rem Парсим ini-файл. Он ищется в текущем каталоге, если не найден - в каталоге
- rem скрипта. Имя найденного файла сохраняем в переменную _Ini.
- if exist "%~n0.ini" (
- if "%cd:~-1%" == "\" (
- set "_Ini=%cd%%~n0.ini"
- ) else (
- set "_Ini=%cd%\%~n0.ini"
- )
- for /f %%! in ("_Ini") do %_IsFile% || set _Ini=
- )
- if not defined _Ini if exist "%~dpn0.ini" (
- set "_Ini=%~dpn0.ini"
- for /f %%! in ("_Ini") do %_IsFile% || set _Ini=
- )
- if defined _Ini (
- for /f %%! in ("_Ini") do %_IsReadable% && (
- rem Если строка содержит "=", выполняем SET со строкой в качестве аргумента
- for /f "delims= usebackq" %%A in ("%_Ini%") do (
- for /f "delims== tokens=1*" %%B in ("%%A_") do if not _ == _%%C set %%A
- )
- rem Восстанавливаем значения переменных
- set "_Ini=%_Ini%"
- set "_Date=%_Date%"
- set "_Time=%_Time%"
- set "_DisplayBanner=%_DisplayBanner%"
- set "_SameError=%_SameError%"
- set "_EnableDE=%_EnableDE%"
- set "_Endlocal=%_Endlocal%"
- set "_IsFile=%_IsFile%"
- set "_IsReadable=%_IsReadable%"
- set _IniInfo=echo Using ini file: %%b
- ) || (
- if not defined _Bare (
- %_DisplayBanner%
- for /f "delims=" %%A in ("%_Ini%") do echo Can't read ini file: %%A
- )
- if not defined _SameError set errorlevel=2
- goto _Exit
- )
- ) else (
- set _IniInfo=echo Ini file not found, using default settings.
- )
- rem Из значений переменных-нефлагов, к которым возможно обращение при
- rem выключенном delayed expansion, убираем возможные кавычки.
- if defined _PauseOnExit set "_PauseOnExit=%_PauseOnExit:"=%"
- rem ППП _DisableDE
- rem v2019-04-18_1
- rem Выключает delayed expansion, если оно включено, и запоминает порядковый
- rem номер вызова ППП, при котором произошло его включение.
- rem См. описание _EnableDE.
- rem Vars in/out: _SetlocalCt (счетчик вызовов), _DEOff (вызов, на котором
- rem выключили delayed expansion).
- rem Exit code: 0
- rem Usage on level 1 and 2:
- rem %_DisableDE%^&
- rem Usage on level 2 and deeper:
- rem %_DisableDE:^=^^^%^^^&
- set _DisableDE=^
- (if _ == _!! (^
- (setlocal disabledelayedexpansion)^^^&^
- (set /a _DEOff=_SetlocalCt+1)^^^&^
- set /a _SetlocalCt+=1^
- ) else set /a _SetlocalCt+=1)
- rem ППП _DisplayExecTime
- rem v2019-04-21_1
- rem Отображает количество секунд, прошедших с момента, дата и время которого
- rem были прежде сохранены в переменных _Date и _Time:
- rem set _Date=%date%&set _Time=%time%
- rem Разница в датах, если есть, всегда принимается за 1.
- rem Vars in/out: _SecWh (время выполнения в секундах - целая часть), _SecFr
- rem (время выполнения в секундах - дробная часть).
- rem Убираем ведущий ноль у компонентов времени, иначе они будут
- rem интерпретированы как 8-ричные числа. Если число часов меньше 10, %time%
- rem возвращает значение с ведущим пробелом - убираем его.
- rem Exit codes: 0 OK
- rem 1 Error
- rem Usage on level 1:
- rem %_DisplayExecTime%
- rem Usage on level 2 and deeper:
- rem %_DisplayExecTime:^=^^^%^^^&
- set _DisplayExecTime=^
- ((if defined _Date (^
- if defined _Time (^
- %_EnableDE:^=^^^%^^^&^
- (for /f "tokens=1-8 delims=:," %%A in ("!_Time!,!time!") do (^
- (set _B=%%B)^^^&^
- (if "0" == "!_B:~,1!" if 00 neq !_B! set _B=!_B:~1!)^^^&^
- (set _C=%%C)^^^&^
- (if "0" == "!_C:~,1!" if 00 neq !_C! set _C=!_C:~1!)^^^&^
- (set _D=%%D)^^^&^
- (if "0" == "!_D:~,1!" if 00 neq !_D! set _D=!_D:~1!)^^^&^
- (set /a "_A=(%%A*3600+_B*60+_C)*100+_D")^^^&^
- (set _B=%%F)^^^&^
- (if "0" == "!_B:~,1!" if not 00 == !_B! set _B=!_B:~1!)^^^&^
- (set _C=%%G)^^^&^
- (if "0" == "!_C:~,1!" if not 00 == !_C! set _C=!_C:~1!)^^^&^
- (set _D=%%H)^^^&^
- (if "0" == "!_D:~,1!" if not 00 == !_D! set _D=!_D:~1!)^^^&^
- (if !date! == !_Date! (^
- set /a "_D=(%%E*3600+_B*60+_C)*100+_D-_A",^
- _SecWh=_D/100,_SecFr=_D%%100^
- ) else (^
- set /a "_D=8640000+(%%E*3600+_B*60+_C)*100+_D-_A",^
- _SecWh=_D/100,_SecFr=_D%%100^
- ))^^^&^
- (if !_SecFr! leq 9 set _SecFr=0!_SecFr!)^^^&^
- for /f "tokens=*" %%Z in ("!_Time!") do (^
- for /f "tokens=*" %%Y in ("!time!") do (^
- if !date! == !_Date! (^
- echo Execution time: !_SecWh!,!_SecFr! s (%%Z - %%Y^^^)^
- ) else (^
- echo Execution time: !_SecWh!,!_SecFr! s (!_Date! %%Z - !date! %%Y^^^)^
- )^
- )^
- )^
- ))^^^&^
- %_Endlocal:^=^^^%^
- ) else color 00^
- ) else color 00)^^^|^^^| (^
- (echo Error displaying execution time.)^^^&^
- color 00^
- ))
- rem ППП _GetLen
- rem v2019-04-18_2
- rem Вычисляет длину строки. Максимальная длина - 8184 для Windows XP, 8191 для
- rem Windows 8.1 (предположительно - для Windows Vista и выше).
- rem %%# - имя переменной, содержащей строку.
- rem %%$ - имя переменной, в которую будет помещено значение длины.
- rem %%# и %%$ не должны содержать "!".
- rem Определение длины происходит путем раскладывания числа на степени двойки.
- rem Если соответствующая текущей степени позиция в строке (отсчитываем от
- rem позиции, соответствующей предыдущей степени) непуста, увеличиваем на
- rem степень значение длины. Если следующая позиция пуста, длина найдена
- rem (сбрасываем флаг), иначе переходим к следующей по убыванию степени.
- rem Exit code: 0 OK
- rem 1 Error
- rem Usage on level 1:
- rem for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen% || echo Error
- rem Usage on level 2:
- rem (for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen% ^|^| echo Error)^&
- rem Usage on level 2 and deeper:
- rem (for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen:^=^^^% ^^^|^^^| echo Error)^^^&
- set _GetLen=^
- ((for /f "tokens=1,2 delims=!" %%# in ("_%%#%%$_") do if _ neq _%%$ (^
- (if not defined _Bare echo Invalid variable name.)^^^&^
- color 00^
- ))^^^&^^^& (^
- if defined %%# (^
- (if not defined _A set _A=0)^^^&^
- %_EnableDE:^=^^^%^^^&^
- (if defined %%$ set %%$=)^^^&^
- for %%Z in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do if defined _A (^
- (set /a _B=%%$+%%Z,_A=_B-1)^^^&^
- for /f "tokens=1,2" %%A in ("!_A! !_B!") do (^
- if "!%%#:~%%A,1!" neq "" (^
- (if "!%%#:~%%B,1!" == "" (^
- %_Endlocal:^=^^^%^^^&^
- set "_A="^
- ))^^^&^
- set "%%$=%%B"^
- )^
- )^
- )^
- ) else set %%$=0^
- ))
- rem ==============================> User code 2 ===============================
- rem Парсим комстроку.
- set File=%1
- (for /f %%z in ("") do cd) || rem
- if not errorlevel 1 goto _Help
- if not defined File goto _Help
- if "%File:"=%" neq "%File:"=@%" set "File=%File:"=%"
- if not defined File goto _Help
- for /f "delims= eol=" %%A in ("%File%") do set "File=%%~fA"
- if not defined File goto _Help
- if "" neq "%File:~259,1%" goto _Help
- if not defined _Bare (
- %_DisplayBanner%
- for /f "tokens=1*" %%a in ("_ %_Ini%") do %_IniInfo%
- echo/
- )
- rem Проверяем, определены ли обязательные переменные.
- if not defined S (
- if not defined _Bare echo S not defined.
- if defined _SameError (
- (for /f %%z in ("") do cd) || rem
- ) else set errorlevel=3
- goto _Exit
- )
- if not defined R (
- if not defined _Bare echo R not defined.
- if defined _SameError (
- (for /f %%z in ("") do cd) || rem
- ) else set errorlevel=3
- goto _Exit
- )
- rem Проверяем необязательные переменные.
- if defined T_CaseInsensitive set T_CaseInsensitive=/i
- if defined S_CaseInsensitive set S_CaseInsensitive=/i
- rem Проверяем файл на читаемость.
- for /f %%! in ("File") do %_IsReadable% && (
- if %%~z! == 0 goto _Exit
- ) || (
- echo Can't read file.
- set errorlevel=10
- goto _Exit
- )
- rem Подготавливаем временные файлы.
- rem Счетчик временных файлов. Значение должно быть не меньше количества
- rem временных файлов, используемых в скрипте.
- set _TmpFileCt=2
- rem Формат имени переменной, в которой хранится имя временного файла:
- rem _TmpFile<n> - где <n> - номер временного файла.
- :TmpFile1
- set "_TmpFile1=%Temp%\%~n0%random%.txt"
- if exist "%_TmpFile1%" goto TmpFile1
- >nul copy nul "%_TmpFile1%" || (
- if not defined _Bare set errorlevel=4
- goto _Exit
- )
- :TmpFile2
- set "_TmpFile2=%Temp%\%~n0%random%.txt"
- if exist "%_TmpFile2%" goto TmpFile2
- >nul copy nul "%_TmpFile2%" || (
- if not defined _Bare set errorlevel=4
- goto _Exit
- )
- rem Определяем длину слова к замене и слова-триггера.
- for /f "tokens=1,2 delims==" %%# in ("S=LenS") do %_GetLen% || goto _Exit
- if defined T (
- for /f "tokens=1,2 delims==" %%# in ("T=LenT") do %_GetLen% || goto _Exit
- if defined TriggerFound set TriggerFound=
- ) else (
- set T=1
- if not defined TriggerFound set TriggerFound=1
- )
- rem Алгоритмы поиска слова-триггера и слова к замене реализованы в 2 вариантах
- rem - основном и альтернативном (медленном). Альтернативный вариант
- rem используется при регистрозависимом поиске и/или если соответствующее слово
- rem содержит "=" и/или "!".
- rem Флаг выбора альтернативного алгоритма для поиска слова-триггера.
- if not defined T_CaseInsensitive (
- if not defined T_AltAlg set T_AltAlg=1
- ) else (
- if defined T_AltAlg set T_AltAlg=
- )
- rem Флаг выбора альтернативного алгоритма для поиска слова к замене.
- if not defined S_CaseInsensitive (
- if not defined S_AltAlg set S_AltAlg=1
- ) else (
- if defined S_AltAlg set S_AltAlg=
- )
- rem Для относительной корректной обработки строк, содержащих символ NUL,
- rem требуется предварительно пропустить исходный файл через find.exe. Если
- rem find.exe недоступен, работаем с файлом напрямую.
- rem ОТКЛЮЧЕНО - всегда работаем с файлом напрямую. Причина - выпадение
- rem следующей строки после длинной (8180 символов) текущей (баг find.exe).
- 2>nul (for /f %%A in ('echo a^|find.exe "a"') do color 00
- ) && (
- >>"%_TmpFile1%" find.exe /v "" "%File%"
- rem File2 - файл, который будем собственно читать
- set "File2=%_TmpFile1%"
- set skip=skip=2
- set SkipVal=2
- ) || (
- set "File2=%File%"
- if defined skip set skip=
- if defined SkipVal set SkipVal=
- )
- rem LineX - номер целевой строки
- rem Pos - позиция начала заменяемого слова от конца целевой строки
- rem LineCt - счетчик строк в файле
- if defined LineX set LineX=
- if defined Pos set Pos=
- if defined LineCt set LineCt=
- setlocal enabledelayedexpansion
- rem Определяем необходимость в использовании альтернативных алгоритмов.
- if defined T if not defined T_AltAlg (
- for /l %%A in (0,1,%LenT%) do (
- if "!T:~%%A,1!" == "^!" (
- set T_AltAlg=1
- goto $endif
- ) else (
- if "!T:~%%A,1!" == "=" (
- set T_AltAlg=1
- goto $endif
- )
- )
- )
- )
- :$endif
- if not defined S_AltAlg (
- for /l %%A in (0,1,%LenS%) do (
- if "!S:~%%A,1!" == "^!" (
- set S_AltAlg=1
- goto $endif
- ) else (
- if "!S:~%%A,1!" == "=" (
- set S_AltAlg=1
- goto $endif
- )
- )
- )
- )
- :$endif
- rem Проверяем, есть ли в файле слово-триггер, и определяем номер последней
- rem строки, где есть слово к замене.
- rem Дополнительный внешний цикл, помимо прочего, помогает избежать падения при
- rem слишком длинной строке в сете.
- rem После добавления строки вызова ППП _GetLen ограничение на длину строки
- rem изменилось с 8183 до 8180 символов.
- for /f delims^=^ eol^= %%C in ("!T!") do (
- for /f delims^=^ eol^= %%B in ("!S!") do (
- endlocal
- (for /f "%skip% delims=" %%A in ('findstr.exe /n ".*" "%File2%"') do (
- set A=%%A
- if defined LineCt (
- set /a LineCt+=1
- ) else (
- set /a LineCt=%SkipVal%+1
- )
- setlocal enabledelayedexpansion
- rem Проверяем, не была ли пропущена предыдущая строка из-за превышения
- rem длины.
- for /f "delims=:" %%B in ("!A!") do (
- if %%B neq !LineCt! (
- echo Too long line.
- set errorlevel=12
- goto _Exit
- )
- )
- set A=!A:*:=!
- if defined A (
- if defined LineX set LineX=
- if _ == _%S_AltAlg% (
- if "!A:*%%B=!" neq "!A!" set LineX=!LineCt!
- ) else (
- rem Если в слове к замене есть "!" или "=", а в строке их нет, слова к
- rem замене в ней тоже нет.
- if defined S_CaseInsensitive^
- (for /f "delims==! tokens=2" %%A in ("_!A!_") do rem
- ) || rem
- if errorlevel 1 (
- set /a 0
- ) else (
- for /f "tokens=1,2 delims==" %%# in ("A=LenA") do %_GetLen% ||^
- goto _Exit
- rem Pos как флаг: прекратить поиск совпадения в строке.
- if defined Pos set Pos=
- rem Так как нас интересует последнее совпадение в строке, проверяем
- rem начиная с конца.
- for /l %%D in (!LenS!,1,!LenA!) do (
- if not defined Pos (
- if %S_CaseInsensitive% "!A:~-%%D,%LenS%!" == "!S!" (
- set Pos=%%D
- set LineX=!LineCt!
- )
- )
- )
- )
- )
- rem Если в триггере есть "!" или "=", а в строке их нет, триггера в ней
- rem тоже нет => устанавливаем TriggerFound для пропуска проверки.
- if _ neq _%T_AltAlg% if defined T_CaseInsensitive^
- (for /f "delims==! tokens=2" %%A in ("_!A!_") do rem
- ) || set TriggerFound=1
- if not defined TriggerFound (
- if _ == _%T_AltAlg% (
- if "!A:*%%C=!" neq "!A!" (for /f %%z in ("") do cd) || rem
- ) else (
- if not defined LenA (
- for /f "tokens=1,2 delims==" %%# in ("A=LenA") do %_GetLen% ||^
- goto _Exit
- )
- for /l %%D in (!LenT!,1,!LenA!) do (
- if not errorlevel 1 (
- if %T_CaseInsensitive% "!A:~-%%D,%LenT%!" == "!T!" (
- (for /f %%z in ("") do cd) || rem
- )
- )
- )
- )
- )
- if defined LineX (
- for /f "delims=" %%A in ("!A!") do (
- for /f "tokens=1,2" %%B in ("!LineX! !Pos!") do (
- endlocal
- if errorlevel 1 set TriggerFound=1
- set X=%%A
- set LineX=%%B
- if _ neq _%%C set Pos=%%C
- )
- )
- ) else (
- endlocal
- if errorlevel 1 set TriggerFound=1
- )
- ) else endlocal
- )) || (
- rem Если длина последней строки превышает допустимую, тоже вылетим сюда.
- if defined LineCt (
- echo Too long line.
- set errorlevel=12
- )
- goto _Exit
- )
- )
- )
- if not defined TriggerFound (
- echo Trigger not found.
- goto _Exit
- )
- if not defined LineX (
- echo Nothing to replace.
- goto _Exit
- )
- rem Y - часть целевой строки после последнего вхождения слова к замене
- rem LenY - длина части целевой строки после слова к замене
- if defined Y set Y=
- rem Pos уже определен, если использовался альтернативный алгоритм поиска слова
- rem к замене.
- setlocal enabledelayedexpansion
- if defined Pos (
- set /a LenY=Pos-LenS
- )
- if defined Pos (
- if 0 neq %LenY% set Y=!X:~-%LenY%!
- (for /f %%z in ("") do cd) || rem
- goto PosDefined
- )
- rem Находим количество вхождений слова к замене в целевую строку как разницу
- rem между ее исходной длиной и ее длиной после удаления всех вхождений слова к
- rem замене, деленную на длину слова к замене.
- for /f "tokens=1,2 delims==" %%# in ("X=LenX") do %_GetLen% || goto _Exit
- for /f delims^=^ eol^= %%A in ("!S!") do set Y=!X:%%A=!
- for /f "tokens=1,2 delims==" %%# in ("Y=LenY") do %_GetLen% || goto _Exit
- set /a OccurrencesX=(LenX-LenY)/LenS
- rem Извлекаем часть целевой строки после последнего вхождения слова к замене.
- set Y=!X!
- for /f delims^=^ eol^= %%B in ("!S!") do (
- for /l %%A in (1,1,%OccurrencesX%) do set Y=!Y:*%%B=!
- )
- for /f "tokens=1,2 delims==" %%# in ("Y=LenY") do %_GetLen% || goto _Exit
- set /a Pos=LenS+LenY
- :PosDefined
- (for /f "delims=" %%A in ("!Y!") do (
- endlocal
- if not errorlevel 1 set Pos=%Pos%
- set Y=%%A
- )) || (
- rem Заменяемое слово замыкает строку.
- endlocal
- set Pos=%LenS%
- )
- rem Помещаем строки во временный файл.
- rem Z - измененная целевая строка.
- if defined Z set Z=
- set /a LineCt=SkipVal+1
- (for /f "%skip% delims=" %%A in ('findstr.exe /n ".*" "%File2%"') do (
- set A=%%A
- setlocal enabledelayedexpansion
- if !LineCt! == !LineX! (
- set Z=!X:~,-%Pos%!!R!!Y!
- if not defined Z (
- echo Too long line.
- set errorlevel=12
- goto _Exit
- )
- >>"!_TmpFile2!" echo !Z!|| (
- if not defined _Bare set errorlevel=4
- goto _Exit
- )
- ) else (
- set A=!A:*:=!
- >>"!_TmpFile2!" echo(!A!|| (
- set errorlevel=4
- goto _Exit
- )
- )
- endlocal
- set /a LineCt+=1
- )) || goto _Exit
- rem Заменяем исходный файл на временный.
- >nul copy "%_TmpFile2%" "%File%" && del "%_TmpFile2%" && (
- echo Done.
- ) || (
- for /f "delims=" %%A in ("%_TmpFile2%") do (
- echo Can't replace file.
- echo Temporary file %%A retained.
- set _TmpFile2=
- set errorlevel=11
- )
- )
- goto _Exit
- rem >>> Переходим в блок "User code 3".
- rem =============================== User code 2 <==============================
- rem Here is the place for aux subroutines
- rem ==============================> User code 3 ===============================
- :_Help
- %_DisplayBanner%
- echo Заменяет в файле последнее вхождение слова на другое слово (опционально - при
- echo наличии слова-триггера).
- echo Ограничение на длину строк: 8180 для строк 1-9, 8179 для строк 10-99 и т. д.
- echo/
- echo Значения берутся из ini-файла. Ini-файл ищется в текущем каталоге, если не
- echo найден, то в каталоге скрипта. Для создания ini-файла со значениями по
- echo умолчанию создайте в текущем каталоге файл нулевой длины с именем скрипта и
- echo расширением ini и запустите скрипт повторно.
- echo/
- echo Usage:
- echo %~nx0 ^<file^>
- echo/
- echo Exit codes: 0 OK
- echo 1 General error
- echo 2 Can't read ini file
- echo 3 Invalid or missing ini file value
- echo 4 Can't write tmp file
- echo 10 Can't read file
- echo 11 Can't replace file
- echo 12 Too long line
- echo -1 Invalid syntax
- for %%a in ("%~n0.ini") do if _%%~za == _0 call :_RestoreIni
- set errorlevel=-1
- goto _Exit
- :_RestoreIni
- echo/
- echo Restoring default ini file...
- >"%~n0.ini" (
- setlocal enabledelayedexpansion
- echo ;Lines starting with ";" are comments.
- echo ;A flag may be either off (not defined^) or on (defined, actual value doesn't
- echo ;matter^). To clear a flag, specify no value after "=".
- echo/
- echo ;Слово-триггер. Если определено, замена произойдет только при его наличии в
- echo ;файле.
- echo ;T=Таня
- echo/
- echo ;Слово, которое заменяем. Должно быть определено.
- echo ;S=Коля
- echo/
- echo ;Слово, на которое заменяем. Должно быть определено.
- echo ;R=Изя
- echo/
- echo ;[флаг] Игнорировать различия в регистре при поиске слова-триггера. По умолчанию
- echo ;выключено.
- echo ;T_CaseInsensitive=1
- echo/
- echo ;[флаг] Игнорировать различия в регистре при поиске слова к замене. По умолчанию
- echo ;выключено.
- echo ;S_CaseInsensitive=1
- echo/
- rem -----------------------------------------------------------------------
- rem Add your entries before the line above. Do not change the lines below.
- rem -----------------------------------------------------------------------
- if defined _Bare (set State=on) else set State=off
- echo ;[flag] Don't display info and error messages. Useful if the output is going to
- echo ;be parsed. Default is !State!.
- echo ;_Bare=1
- echo/
- if defined _ExecTime (set State=on) else set State=off
- echo ;[flag] Display script execution time. Ignored if _Bare is set. Default is !State!.
- echo ;_ExecTime=1
- echo/
- echo ;Pause on exit:
- echo ;^<none^> = Never
- echo ;^<n^> = If errorlevel is equal to or higher than ^<n^>
- echo ;* = Always
- if defined _PauseOnExit (set "State=%_PauseOnExit%") else set "State=<none>".
- echo ;Ignored if invalid or if _Bare is set. Default is !State!.
- echo ;_PauseOnExit=1
- echo/
- if defined _RetainTmpFiles (set State=on) else set State=off
- echo ;[flag] Don't delete temporary files. Default is !State!.
- echo ;_RetainTmpFiles=1
- ) && echo OK.
- exit /b
- rem =============================== User code 3 <==============================
- rem v2019-05-03_2
- :_Exit
- (
- if not defined _RetainTmpFiles (
- if defined _TmpFileCt %_EnableDE%
- for /l %%B in (1,1,%_TmpFileCt%) do (
- if exist "!_TmpFile%%B!" del "!_TmpFile%%B!"
- )
- )
- if not defined _Bare (
- echo/
- echo Exit code: %errorlevel%
- if defined _ExecTime %_DisplayExecTime%
- %_DisableDE%
- for /f "delims= eol=" %%A in ("%_PauseOnExit%") do (
- if * == %%A (
- pause
- ) else (
- if %errorlevel% geq %%A pause
- )
- )
- )
- exit /b %errorlevel%
- )
- ===============================================================================
- Правила для ППП
- v2019-04-22_1
- 1. Символы <>|&) (последний - при использовании в команде, а не для разделения
- других команд) нужно эскейпить.
- При непосредственном определении в ППП 1-го уровня (вызываемых
- непосредственно) используется 1-кратное эскейпирование (^|), 2-го уровня
- (вызываемых из ППП 1-го) - 3-кратное (^^^|), 3-го уровня (вызываемых из ППП
- 2-го) - 7-кратное.
- В ППП 1-го уровня можно использовать и 3-кратное эскейпирование. Это
- позволяет сделать ППП универсальной - запускаемой на любом уровне. При этом
- на уровне 1 и 2 такую ППП можно запускать непосредственно, а для запуска на
- уровне глубже 2-го требуется замена на лету: %Test:^=^^^%; такой же формат
- вызова допустим и на 2-м уровне.
- Для универсальных ППП нужно учитывать следующее:
- 1) "|" отрубает delayed expansion в предшествующей команде, если она
- заключена в скобки. Это верно всегда, но для универсальных ППП приобретает
- особую актуальность - см. ниже;
- 2) любая команда, за которой следует другая (после "&", "|", "&&" или "||"),
- должна заключаться в скобки. Блоки FOR и IF заключаются в скобки вообще
- всегда; необязательно это только в случае, когда они являются последней
- командой в блоке команд;
- 3) после спецсимволов должен идти пробел. К спецсимволам относятся, кроме
- указанных выше, ">" и, вероятно, "<" (последний, опять-таки вероятно, должен
- предваряться пробелом). При несоблюдении этого правила (">nul" вместо
- "> nul") имя устройства при раскрытии съедается и перенаправление происходит
- в файл (">nul fc" => "> fc"), что чревато потерей информации;
- 4) разноуровневое эскейпирование недопустимо. Актуально для FOR с сетом в
- виде вывода другой команды: спецсимволы в ней нужно эскейпить и при
- использовании такой конструкции "напрямую" (не в составе ППП), поэтому
- эскейпирование там будет на уровень глубже (3-кратное для ППП 1-го уровня).
- Для обхода ограничения используем вспомогательную переменную:
- (set "_B=2>&1 >nul")%^^^&^
- for /f "delims=" %%A in ('!_B! fc.exe "!%%#!" "|"') do ...
- Если есть возможность, ППП следует делать универсальными, так как такие ППП
- не накладывают ограничений на родителя при использовании в качестве
- вложенных. Если уровень вызова ППП жестко определен, универсальной ее делать
- не нужно.
- Код универсальных ППП заключается в скобки. Отступление от этого правила
- возможно, если формат вызова ППП фиксирован (не требуется анализ кода
- возврата) и предполагает заключение в скобки команды вызова (FOR).
- 2. Конкатенатор ("&") указывается в той же строке, что и команда, за которой он
- следует.
- 3. При анализе результата выполнения ППП через && и || нужно учитывать, что &&
- и || сработают на результат выполнения последней команды, в т. ч. не
- меняющей errorlevel, например ENDLOCAL или невыполнившийся IF.
- 4. При использовании
- (for /f %%z in ("") do cd) || rem
- нужно помнить, что после разворачивания ППП эта команда должна быть
- последней в строке.
- 5. Имена ППП, для которых планируется сжатие кода, универсальных ППП и вообще
- всех, которые могут быть вложенными, не должны содержать "!".
- 6. Нужно помнить, что к последней в блоке команде прибавятся все пробелы,
- образующие отступ для закрывающей скобки блока. Это актуально в первую
- очередь для ECHO (закрывающую скобку указываем в той же строке) и SET
- (используем кавычки).
- Changelog:
- v1.0.7
- [-] Неправильно обрабатывались слова, начинающиеся с двойной кавычки.
- [-] Неправильно обрабатывались строки, начинающиеся с "?".
- [?] Если строка содержит символ NUL, он и все следующие за ним символы
- отбрасываются.
- v1.0.6
- [-] Неправильно обрабатывалась ошибка, если полный путь к файлу превышал допустимую
- длину.
- v1.0.5
- [-] Неправильно обрабатывалась ошибка, если полное имя файла состояло из одних
- кавычек.
- v1.0.4
- [*] Чуть улучшена производительность.
- v1.0.3
- [-] Если заменямое слово стояло в конце строки, при замене слово дублировалось.
- [-] Неверно обрабатывались слова, начинающиеся с "*".
- [-] Игнорировались различия в регистре при поиске, если слово не содержало "="
- или "!".
- [-] Значения переменных наследовались из внешнего процесса.
- [+] Регистронезависимый поиск.
- [+] Сообщения при успешном завершении операции.
- [*] Улучшена производительность.
- v1.0.2
- First public release.
- [+] Поддержка триггеров и слов к замене, содержащих "=" и/или "!".
- v1.0.1
- [+] Проверка строк на длину.
- v1.0.0
- [?] Триггеры и слова к замене, содержащие "=" и/или "!", не поддерживаются.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement