GCRaistlin

replace_last_occurrence.cmd

Dec 21st, 2013
104
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. @echo off
  2. rem v2019-04-24_1
  3.  
  4. setlocal disabledelayedexpansion enableextensions || (echo Unable to enable extensions.& exit /b)
  5. set errorlevel=1&if %errorlevel% == 1 (echo This script must be run under cmd.exe.& exit /b 1) else set errorlevel=
  6. set _Date=%date%&set _Time=%time%
  7.  
  8. rem Пользовательский код помещаем в блоки "User code":
  9. rem - в "User code 1" определяются переменные, управляющие поведением
  10. rem служебного кода, присваиваются значения по умолчанию переменным,
  11. rem определяемым через парсинг ini-файла, и переключается кодовая страница;
  12. rem - в "User code 2" содержится собственно код скрипта;
  13. rem - в "User code 3" - код вывода хелпа и восстановления ini-файла по
  14. rem умолчанию.
  15. rem Порядок заполнения блоков: 1-3-2.
  16. rem Внесение изменений в код восстановления ini-файла по умолчанию (блок 3)
  17. rem должно сопровождаться изменениями в присвоении переменным значений по
  18. rem умолчанию (блок 1).
  19. rem Код вне блоков "User code" является служебным, независимым от кода
  20. rem конкретного скрипта. Его правка должны сопровождаться изменением версии
  21. rem и переносом новой версии кода в скрипт-шаблон.
  22. rem В конце скрипта - место для заметок и changelog.
  23.  
  24. rem ==============================> User code 1 ===============================
  25.  
  26. set _DisplayBanner=^
  27. echo/^
  28. &echo Replace Last Occurrence v1.0.7^
  29. &echo (c^^) 2019 GCRaistlin. Licensed under GNU GPL v3.^
  30. &echo/
  31.  
  32. rem Если флаг установлен, скрипт при любой ошибке в служебной части скрипта,
  33. rem кроме Invalid syntax, возвращает errorlevel 1.
  34. if defined _SameError set _SameError=
  35.  
  36. rem Устанавливаем значения по умолчанию для переменных, определяемых через
  37. rem парсинг ini-файла, в т. ч. пустые.
  38.  
  39. if defined _Bare set _Bare=
  40. if defined _ExecTime set _ExecTime=
  41. if defined _PauseOnExit set _PauseOnExit=
  42. if defined _RetainTmpFiles set _RetainTmpFiles=
  43. if defined T set T=
  44. if defined S set S=
  45. if defined R set R=
  46. if defined T_CaseInsensitive set T_CaseInsensitive=
  47. if defined S_CaseInsensitive set S_CaseInsensitive=
  48.  
  49. rem Рабочая кодовая страница должна соответствовать кодовой странице скрипта, а
  50. rem кодовая страница скрипта - кодовой странице ini-файла.
  51. >nul chcp 1251
  52.  
  53. rem >>> Переходим в блок "User code 2".
  54.  
  55. rem =============================== User code 1 <==============================
  56.  
  57. rem v2019-05-03_1
  58.  
  59. rem Перед парсингом ini-файла определяем только необходимые для парсинга
  60. rem переменные-подпрограммы (ППП), т. к. при парсинге значения переменных могут
  61. rem измениться => их надо принудительно восстанавливать.
  62. rem Подробнее про ППП см. в конце скрипта.
  63.  
  64. rem ППП _EnableDE
  65. rem v2019-04-18_2
  66. rem Включает delayed expansion, если оно выключено, и запоминает номер вызова
  67. rem ППП, при котором произошло включение.
  68. rem Изменение состояния delayed expansion с помощью _EnableDE и _DisableDE
  69. rem позволяет избежать лишних SETLOCAL/ENDLOCAL, выполняемых только для его
  70. rem переключения, при неизвестном текущем состоянии delayed expansion.
  71. rem Увеличивает счетчик вызовов (един для _EnableDE и _DisableDE).
  72. rem Возвращать delayed expansion в состояние, которое было перед вызовом ППП,
  73. rem следует через _Endlocal. В промежутке между _EnableDE (_DisableDE) и
  74. rem _Endlocal не должно быть незакрытых SETLOCAL.
  75. rem _EnableDE и _DisableDE должны вызываться в паре с _Endlocal.
  76. rem Vars in/out: _SetlocalCt (счетчик вызовов), _DEOn (вызов, на котором
  77. rem включили delayed expansion).
  78. rem Требует установки начальных значений:
  79. set _SetlocalCt=0
  80. if defined _DEOn set _DEOn=
  81. if defined _DEOff set _DEOff=
  82. rem Exit code: 0
  83. rem Usage on level 1 and 2:
  84. rem %_EnableDE%^&
  85. rem Usage on level 2 and deeper:
  86. rem %_EnableDE:^=^^^%^^^&
  87. set _EnableDE=^
  88. (if _ neq _!! (^
  89. (setlocal enabledelayedexpansion)^^^&^
  90. (set /a _DEOn=_SetlocalCt+1)^^^&^
  91. set /a _SetlocalCt+=1^
  92. ) else set /a _SetlocalCt+=1)
  93.  
  94. rem ППП _Endlocal
  95. rem v2019-04-18_1
  96. rem Возвращает delayed expansion в состояние, бывшее до последнего вызова
  97. rem _EnableDE или _DisableDE.
  98. rem Уменьшает счетчик вызовов.
  99. rem Должна вызываться в паре с _EnableDE или _DisableDE.
  100. rem Vars in/out: _SetlocalCt, _DEOn, _DEOff.
  101. rem Usage on level 1 and 2:
  102. rem %_Endlocal%^&
  103. rem Usage on level 2 and deeper:
  104. rem %_Endlocal:^=^^^%^^^&
  105. set _Endlocal=^
  106. (if errorlevel 1 (^
  107. ((2^^^> nul set /a 1/(_DEOn-_SetlocalCt^^^)/(_DEOff-_SetlocalCt^^^))^^^&^^^&^
  108. (set /a _SetlocalCt-=1) ^^^|^^^| endlocal)^^^&^^^&^
  109. color 00^
  110. ) else (^
  111. ((2^^^> nul set /a 1/(_DEOn-_SetlocalCt^^^)/(_DEOff-_SetlocalCt^^^))^^^&^^^&^
  112. (set /a _SetlocalCt-=1) ^^^|^^^| endlocal)^
  113. ))
  114.  
  115. rem ППП _IsFile
  116. rem v2019-03-27_3
  117. rem Возвращает ошибку 0, если объект является файлом, иначе ошибку 1.
  118. rem %%! - имя переменной, содержащей имя объекта. Не должно содержать "!"
  119. rem (возвращает непредсказуемый результат при наличии).
  120. rem Менять "not ==" на "neq" нельзя: _IsDir получаем из _IsFile заменой,
  121. rem которая не должна влиять на _EnableDE, поэтому используем разный синтаксис.
  122. rem Usage on level 1:
  123. rem for /f %%! in ("<obj_var>") do %_IsFile% && echo A file || echo Not a file
  124. rem Usage on level 2:
  125. rem (for /f %%! in ("<obj_var>") do %_IsFile% ^&^& echo A file ^|^| echo Not a file)^&
  126. rem Usage on level 2 and deeper:
  127. rem (for /f %%! in ("<obj_var>") do %_IsFile:^=^^^% ^^^&^^^& (echo A file) ^^^|^^^| echo Not a file)^^^&
  128. set _IsFile=^
  129. (if defined %%! (^
  130. %_EnableDE:^=^^^%^^^&^
  131. (if "!%%!:**=!" == "!%%!!" (^
  132. if "!%%!:?=!" == "!%%!!" (^
  133. if exist "!%%!!" (^
  134. (for %%A in ("!%%!!") do set Attr=%%~aA)^^^&^
  135. if /i not "-" == "!Attr:~,1!" color 00^
  136. ) else color 00^
  137. ) else color 00^
  138. ) else color 00) ^^^&^^^&^ %_Endlocal:^=^^^% ^^^|^^^|^
  139. (%_Endlocal:^=^^^%^^^& color 00)^
  140. ) else color 00)
  141.  
  142. rem ППП _IsReadable
  143. rem v2019-03-27_4
  144. rem Возвращает ошибку 0, если файл доступен для чтения, иначе ошибку 1.
  145. rem %%! - имя переменной, содержащей имя объекта. Не должно содержать "!"
  146. rem (возвращает непредсказуемый результат при наличии).
  147. rem Usage on level 1:
  148. rem for /f %%! in ("<obj_var>") do %_IsReadable% && echo A readable file || echo Not a readable file
  149. rem Usage on level 2:
  150. rem (for /f %%! in ("<obj_var>") do %_IsReadable% ^&^& echo A readable file ^|^| echo Not a readable file)^&
  151. rem Usage on level 2 and deeper:
  152. rem (for /f %%! in ("<obj_var>") do %_IsReadable:^=^^^% ^^^&^^^& (echo A readable file) ^^^|^^^| echo Not a readable file)^^^&
  153. set _IsReadable=^
  154. (if defined %%! (^
  155. %_EnableDE:^=^^^%^^^&^
  156. %_IsFile:^=^^^% ^^^&^^^& (^^^> nul 2^^^>^^^&1 copy "!%%!!" nul) ^^^&^^^&^
  157. %_Endlocal:^=^^^% ^^^|^^^| (%_Endlocal:^=^^^%^^^& color 00)^
  158. ) else color 00)
  159.  
  160. rem Парсим ini-файл. Он ищется в текущем каталоге, если не найден - в каталоге
  161. rem скрипта. Имя найденного файла сохраняем в переменную _Ini.
  162.  
  163. if exist "%~n0.ini" (
  164. if "%cd:~-1%" == "\" (
  165. set "_Ini=%cd%%~n0.ini"
  166. ) else (
  167. set "_Ini=%cd%\%~n0.ini"
  168. )
  169. for /f %%! in ("_Ini") do %_IsFile% || set _Ini=
  170. )
  171. if not defined _Ini if exist "%~dpn0.ini" (
  172. set "_Ini=%~dpn0.ini"
  173. for /f %%! in ("_Ini") do %_IsFile% || set _Ini=
  174. )
  175. if defined _Ini (
  176. for /f %%! in ("_Ini") do %_IsReadable% && (
  177. rem Если строка содержит "=", выполняем SET со строкой в качестве аргумента
  178. for /f "delims= usebackq" %%A in ("%_Ini%") do (
  179. for /f "delims== tokens=1*" %%B in ("%%A_") do if not _ == _%%C set %%A
  180. )
  181. rem Восстанавливаем значения переменных
  182. set "_Ini=%_Ini%"
  183. set "_Date=%_Date%"
  184. set "_Time=%_Time%"
  185. set "_DisplayBanner=%_DisplayBanner%"
  186. set "_SameError=%_SameError%"
  187. set "_EnableDE=%_EnableDE%"
  188. set "_Endlocal=%_Endlocal%"
  189. set "_IsFile=%_IsFile%"
  190. set "_IsReadable=%_IsReadable%"
  191. set _IniInfo=echo Using ini file: %%b
  192. ) || (
  193. if not defined _Bare (
  194. %_DisplayBanner%
  195. for /f "delims=" %%A in ("%_Ini%") do echo Can't read ini file: %%A
  196. )
  197. if not defined _SameError set errorlevel=2
  198. goto _Exit
  199. )
  200. ) else (
  201. set _IniInfo=echo Ini file not found, using default settings.
  202. )
  203.  
  204. rem Из значений переменных-нефлагов, к которым возможно обращение при
  205. rem выключенном delayed expansion, убираем возможные кавычки.
  206.  
  207. if defined _PauseOnExit set "_PauseOnExit=%_PauseOnExit:"=%"
  208.  
  209. rem ППП _DisableDE
  210. rem v2019-04-18_1
  211. rem Выключает delayed expansion, если оно включено, и запоминает порядковый
  212. rem номер вызова ППП, при котором произошло его включение.
  213. rem См. описание _EnableDE.
  214. rem Vars in/out: _SetlocalCt (счетчик вызовов), _DEOff (вызов, на котором
  215. rem выключили delayed expansion).
  216. rem Exit code: 0
  217. rem Usage on level 1 and 2:
  218. rem %_DisableDE%^&
  219. rem Usage on level 2 and deeper:
  220. rem %_DisableDE:^=^^^%^^^&
  221. set _DisableDE=^
  222. (if _ == _!! (^
  223. (setlocal disabledelayedexpansion)^^^&^
  224. (set /a _DEOff=_SetlocalCt+1)^^^&^
  225. set /a _SetlocalCt+=1^
  226. ) else set /a _SetlocalCt+=1)
  227.  
  228. rem ППП _DisplayExecTime
  229. rem v2019-04-21_1
  230. rem Отображает количество секунд, прошедших с момента, дата и время которого
  231. rem были прежде сохранены в переменных _Date и _Time:
  232. rem set _Date=%date%&set _Time=%time%
  233. rem Разница в датах, если есть, всегда принимается за 1.
  234. rem Vars in/out: _SecWh (время выполнения в секундах - целая часть), _SecFr
  235. rem (время выполнения в секундах - дробная часть).
  236. rem Убираем ведущий ноль у компонентов времени, иначе они будут
  237. rem интерпретированы как 8-ричные числа. Если число часов меньше 10, %time%
  238. rem возвращает значение с ведущим пробелом - убираем его.
  239. rem Exit codes: 0 OK
  240. rem 1 Error
  241. rem Usage on level 1:
  242. rem %_DisplayExecTime%
  243. rem Usage on level 2 and deeper:
  244. rem %_DisplayExecTime:^=^^^%^^^&
  245. set _DisplayExecTime=^
  246. ((if defined _Date (^
  247. if defined _Time (^
  248. %_EnableDE:^=^^^%^^^&^
  249. (for /f "tokens=1-8 delims=:," %%A in ("!_Time!,!time!") do (^
  250. (set _B=%%B)^^^&^
  251. (if "0" == "!_B:~,1!" if 00 neq !_B! set _B=!_B:~1!)^^^&^
  252. (set _C=%%C)^^^&^
  253. (if "0" == "!_C:~,1!" if 00 neq !_C! set _C=!_C:~1!)^^^&^
  254. (set _D=%%D)^^^&^
  255. (if "0" == "!_D:~,1!" if 00 neq !_D! set _D=!_D:~1!)^^^&^
  256. (set /a "_A=(%%A*3600+_B*60+_C)*100+_D")^^^&^
  257. (set _B=%%F)^^^&^
  258. (if "0" == "!_B:~,1!" if not 00 == !_B! set _B=!_B:~1!)^^^&^
  259. (set _C=%%G)^^^&^
  260. (if "0" == "!_C:~,1!" if not 00 == !_C! set _C=!_C:~1!)^^^&^
  261. (set _D=%%H)^^^&^
  262. (if "0" == "!_D:~,1!" if not 00 == !_D! set _D=!_D:~1!)^^^&^
  263. (if !date! == !_Date! (^
  264. set /a "_D=(%%E*3600+_B*60+_C)*100+_D-_A",^
  265. _SecWh=_D/100,_SecFr=_D%%100^
  266. ) else (^
  267. set /a "_D=8640000+(%%E*3600+_B*60+_C)*100+_D-_A",^
  268. _SecWh=_D/100,_SecFr=_D%%100^
  269. ))^^^&^
  270. (if !_SecFr! leq 9 set _SecFr=0!_SecFr!)^^^&^
  271. for /f "tokens=*" %%Z in ("!_Time!") do (^
  272. for /f "tokens=*" %%Y in ("!time!") do (^
  273. if !date! == !_Date! (^
  274. echo Execution time: !_SecWh!,!_SecFr! s (%%Z - %%Y^^^)^
  275. ) else (^
  276. echo Execution time: !_SecWh!,!_SecFr! s (!_Date! %%Z - !date! %%Y^^^)^
  277. )^
  278. )^
  279. )^
  280. ))^^^&^
  281. %_Endlocal:^=^^^%^
  282. ) else color 00^
  283. ) else color 00)^^^|^^^| (^
  284. (echo Error displaying execution time.)^^^&^
  285. color 00^
  286. ))
  287.  
  288. rem ППП _GetLen
  289. rem v2019-04-18_2
  290. rem Вычисляет длину строки. Максимальная длина - 8184 для Windows XP, 8191 для
  291. rem Windows 8.1 (предположительно - для Windows Vista и выше).
  292. rem %%# - имя переменной, содержащей строку.
  293. rem %%$ - имя переменной, в которую будет помещено значение длины.
  294. rem %%# и %%$ не должны содержать "!".
  295. rem Определение длины происходит путем раскладывания числа на степени двойки.
  296. rem Если соответствующая текущей степени позиция в строке (отсчитываем от
  297. rem позиции, соответствующей предыдущей степени) непуста, увеличиваем на
  298. rem степень значение длины. Если следующая позиция пуста, длина найдена
  299. rem (сбрасываем флаг), иначе переходим к следующей по убыванию степени.
  300. rem Exit code: 0 OK
  301. rem 1 Error
  302. rem Usage on level 1:
  303. rem for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen% || echo Error
  304. rem Usage on level 2:
  305. rem (for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen% ^|^| echo Error)^&
  306. rem Usage on level 2 and deeper:
  307. rem (for /f "tokens=1,2 delims==" %%# in ("<string_var>=<len_var>") do %_GetLen:^=^^^% ^^^|^^^| echo Error)^^^&
  308. set _GetLen=^
  309. ((for /f "tokens=1,2 delims=!" %%# in ("_%%#%%$_") do if _ neq _%%$ (^
  310. (if not defined _Bare echo Invalid variable name.)^^^&^
  311. color 00^
  312. ))^^^&^^^& (^
  313. if defined %%# (^
  314. (if not defined _A set _A=0)^^^&^
  315. %_EnableDE:^=^^^%^^^&^
  316. (if defined %%$ set %%$=)^^^&^
  317. for %%Z in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do if defined _A (^
  318. (set /a _B=%%$+%%Z,_A=_B-1)^^^&^
  319. for /f "tokens=1,2" %%A in ("!_A! !_B!") do (^
  320. if "!%%#:~%%A,1!" neq "" (^
  321. (if "!%%#:~%%B,1!" == "" (^
  322. %_Endlocal:^=^^^%^^^&^
  323. set "_A="^
  324. ))^^^&^
  325. set "%%$=%%B"^
  326. )^
  327. )^
  328. )^
  329. ) else set %%$=0^
  330. ))
  331.  
  332. rem ==============================> User code 2 ===============================
  333.  
  334. rem Парсим комстроку.
  335.  
  336. set File=%1
  337. (for /f %%z in ("") do cd) || rem
  338. if not errorlevel 1 goto _Help
  339. if not defined File goto _Help
  340. if "%File:"=%" neq "%File:"=@%" set "File=%File:"=%"
  341. if not defined File goto _Help
  342. for /f "delims= eol=" %%A in ("%File%") do set "File=%%~fA"
  343. if not defined File goto _Help
  344. if "" neq "%File:~259,1%" goto _Help
  345.  
  346. if not defined _Bare (
  347. %_DisplayBanner%
  348. for /f "tokens=1*" %%a in ("_ %_Ini%") do %_IniInfo%
  349. echo/
  350. )
  351.  
  352. rem Проверяем, определены ли обязательные переменные.
  353. if not defined S (
  354. if not defined _Bare echo S not defined.
  355. if defined _SameError (
  356. (for /f %%z in ("") do cd) || rem
  357. ) else set errorlevel=3
  358. goto _Exit
  359. )
  360. if not defined R (
  361. if not defined _Bare echo R not defined.
  362. if defined _SameError (
  363. (for /f %%z in ("") do cd) || rem
  364. ) else set errorlevel=3
  365. goto _Exit
  366. )
  367.  
  368. rem Проверяем необязательные переменные.
  369. if defined T_CaseInsensitive set T_CaseInsensitive=/i
  370. if defined S_CaseInsensitive set S_CaseInsensitive=/i
  371.  
  372. rem Проверяем файл на читаемость.
  373. for /f %%! in ("File") do %_IsReadable% && (
  374. if %%~z! == 0 goto _Exit
  375. ) || (
  376. echo Can't read file.
  377. set errorlevel=10
  378. goto _Exit
  379. )
  380.  
  381. rem Подготавливаем временные файлы.
  382. rem Счетчик временных файлов. Значение должно быть не меньше количества
  383. rem временных файлов, используемых в скрипте.
  384. set _TmpFileCt=2
  385. rem Формат имени переменной, в которой хранится имя временного файла:
  386. rem _TmpFile<n> - где <n> - номер временного файла.
  387. :TmpFile1
  388. set "_TmpFile1=%Temp%\%~n0%random%.txt"
  389. if exist "%_TmpFile1%" goto TmpFile1
  390. >nul copy nul "%_TmpFile1%" || (
  391. if not defined _Bare set errorlevel=4
  392. goto _Exit
  393. )
  394. :TmpFile2
  395. set "_TmpFile2=%Temp%\%~n0%random%.txt"
  396. if exist "%_TmpFile2%" goto TmpFile2
  397. >nul copy nul "%_TmpFile2%" || (
  398. if not defined _Bare set errorlevel=4
  399. goto _Exit
  400. )
  401.  
  402. rem Определяем длину слова к замене и слова-триггера.
  403. for /f "tokens=1,2 delims==" %%# in ("S=LenS") do %_GetLen% || goto _Exit
  404. if defined T (
  405. for /f "tokens=1,2 delims==" %%# in ("T=LenT") do %_GetLen% || goto _Exit
  406. if defined TriggerFound set TriggerFound=
  407. ) else (
  408. set T=1
  409. if not defined TriggerFound set TriggerFound=1
  410. )
  411.  
  412. rem Алгоритмы поиска слова-триггера и слова к замене реализованы в 2 вариантах
  413. rem - основном и альтернативном (медленном). Альтернативный вариант
  414. rem используется при регистрозависимом поиске и/или если соответствующее слово
  415. rem содержит "=" и/или "!".
  416. rem Флаг выбора альтернативного алгоритма для поиска слова-триггера.
  417. if not defined T_CaseInsensitive (
  418. if not defined T_AltAlg set T_AltAlg=1
  419. ) else (
  420. if defined T_AltAlg set T_AltAlg=
  421. )
  422. rem Флаг выбора альтернативного алгоритма для поиска слова к замене.
  423. if not defined S_CaseInsensitive (
  424. if not defined S_AltAlg set S_AltAlg=1
  425. ) else (
  426. if defined S_AltAlg set S_AltAlg=
  427. )
  428.  
  429. rem Для относительной корректной обработки строк, содержащих символ NUL,
  430. rem требуется предварительно пропустить исходный файл через find.exe. Если
  431. rem find.exe недоступен, работаем с файлом напрямую.
  432. rem ОТКЛЮЧЕНО - всегда работаем с файлом напрямую. Причина - выпадение
  433. rem следующей строки после длинной (8180 символов) текущей (баг find.exe).
  434. 2>nul (for /f %%A in ('echo a^|find.exe "a"') do color 00
  435. ) && (
  436. >>"%_TmpFile1%" find.exe /v "" "%File%"
  437. rem File2 - файл, который будем собственно читать
  438. set "File2=%_TmpFile1%"
  439. set skip=skip=2
  440. set SkipVal=2
  441. ) || (
  442. set "File2=%File%"
  443. if defined skip set skip=
  444. if defined SkipVal set SkipVal=
  445. )
  446.  
  447. rem LineX - номер целевой строки
  448. rem Pos - позиция начала заменяемого слова от конца целевой строки
  449. rem LineCt - счетчик строк в файле
  450. if defined LineX set LineX=
  451. if defined Pos set Pos=
  452. if defined LineCt set LineCt=
  453.  
  454. setlocal enabledelayedexpansion
  455. rem Определяем необходимость в использовании альтернативных алгоритмов.
  456. if defined T if not defined T_AltAlg (
  457. for /l %%A in (0,1,%LenT%) do (
  458. if "!T:~%%A,1!" == "^!" (
  459. set T_AltAlg=1
  460. goto $endif
  461. ) else (
  462. if "!T:~%%A,1!" == "=" (
  463. set T_AltAlg=1
  464. goto $endif
  465. )
  466. )
  467. )
  468. )
  469. :$endif
  470. if not defined S_AltAlg (
  471. for /l %%A in (0,1,%LenS%) do (
  472. if "!S:~%%A,1!" == "^!" (
  473. set S_AltAlg=1
  474. goto $endif
  475. ) else (
  476. if "!S:~%%A,1!" == "=" (
  477. set S_AltAlg=1
  478. goto $endif
  479. )
  480. )
  481. )
  482. )
  483. :$endif
  484.  
  485. rem Проверяем, есть ли в файле слово-триггер, и определяем номер последней
  486. rem строки, где есть слово к замене.
  487. rem Дополнительный внешний цикл, помимо прочего, помогает избежать падения при
  488. rem слишком длинной строке в сете.
  489. rem После добавления строки вызова ППП _GetLen ограничение на длину строки
  490. rem изменилось с 8183 до 8180 символов.
  491. for /f delims^=^ eol^= %%C in ("!T!") do (
  492. for /f delims^=^ eol^= %%B in ("!S!") do (
  493. endlocal
  494. (for /f "%skip% delims=" %%A in ('findstr.exe /n ".*" "%File2%"') do (
  495. set A=%%A
  496. if defined LineCt (
  497. set /a LineCt+=1
  498. ) else (
  499. set /a LineCt=%SkipVal%+1
  500. )
  501. setlocal enabledelayedexpansion
  502. rem Проверяем, не была ли пропущена предыдущая строка из-за превышения
  503. rem длины.
  504. for /f "delims=:" %%B in ("!A!") do (
  505. if %%B neq !LineCt! (
  506. echo Too long line.
  507. set errorlevel=12
  508. goto _Exit
  509. )
  510. )
  511. set A=!A:*:=!
  512. if defined A (
  513. if defined LineX set LineX=
  514. if _ == _%S_AltAlg% (
  515. if "!A:*%%B=!" neq "!A!" set LineX=!LineCt!
  516. ) else (
  517. rem Если в слове к замене есть "!" или "=", а в строке их нет, слова к
  518. rem замене в ней тоже нет.
  519. if defined S_CaseInsensitive^
  520. (for /f "delims==! tokens=2" %%A in ("_!A!_") do rem
  521. ) || rem
  522. if errorlevel 1 (
  523. set /a 0
  524. ) else (
  525. for /f "tokens=1,2 delims==" %%# in ("A=LenA") do %_GetLen% ||^
  526. goto _Exit
  527. rem Pos как флаг: прекратить поиск совпадения в строке.
  528. if defined Pos set Pos=
  529. rem Так как нас интересует последнее совпадение в строке, проверяем
  530. rem начиная с конца.
  531. for /l %%D in (!LenS!,1,!LenA!) do (
  532. if not defined Pos (
  533. if %S_CaseInsensitive% "!A:~-%%D,%LenS%!" == "!S!" (
  534. set Pos=%%D
  535. set LineX=!LineCt!
  536. )
  537. )
  538. )
  539. )
  540. )
  541. rem Если в триггере есть "!" или "=", а в строке их нет, триггера в ней
  542. rem тоже нет => устанавливаем TriggerFound для пропуска проверки.
  543. if _ neq _%T_AltAlg% if defined T_CaseInsensitive^
  544. (for /f "delims==! tokens=2" %%A in ("_!A!_") do rem
  545. ) || set TriggerFound=1
  546. if not defined TriggerFound (
  547. if _ == _%T_AltAlg% (
  548. if "!A:*%%C=!" neq "!A!" (for /f %%z in ("") do cd) || rem
  549. ) else (
  550. if not defined LenA (
  551. for /f "tokens=1,2 delims==" %%# in ("A=LenA") do %_GetLen% ||^
  552. goto _Exit
  553. )
  554. for /l %%D in (!LenT!,1,!LenA!) do (
  555. if not errorlevel 1 (
  556. if %T_CaseInsensitive% "!A:~-%%D,%LenT%!" == "!T!" (
  557. (for /f %%z in ("") do cd) || rem
  558. )
  559. )
  560. )
  561. )
  562. )
  563. if defined LineX (
  564. for /f "delims=" %%A in ("!A!") do (
  565. for /f "tokens=1,2" %%B in ("!LineX! !Pos!") do (
  566. endlocal
  567. if errorlevel 1 set TriggerFound=1
  568. set X=%%A
  569. set LineX=%%B
  570. if _ neq _%%C set Pos=%%C
  571. )
  572. )
  573. ) else (
  574. endlocal
  575. if errorlevel 1 set TriggerFound=1
  576. )
  577. ) else endlocal
  578. )) || (
  579. rem Если длина последней строки превышает допустимую, тоже вылетим сюда.
  580. if defined LineCt (
  581. echo Too long line.
  582. set errorlevel=12
  583. )
  584. goto _Exit
  585. )
  586. )
  587. )
  588.  
  589. if not defined TriggerFound (
  590. echo Trigger not found.
  591. goto _Exit
  592. )
  593. if not defined LineX (
  594. echo Nothing to replace.
  595. goto _Exit
  596. )
  597.  
  598. rem Y - часть целевой строки после последнего вхождения слова к замене
  599. rem LenY - длина части целевой строки после слова к замене
  600. if defined Y set Y=
  601.  
  602. rem Pos уже определен, если использовался альтернативный алгоритм поиска слова
  603. rem к замене.
  604. setlocal enabledelayedexpansion
  605. if defined Pos (
  606. set /a LenY=Pos-LenS
  607. )
  608. if defined Pos (
  609. if 0 neq %LenY% set Y=!X:~-%LenY%!
  610. (for /f %%z in ("") do cd) || rem
  611. goto PosDefined
  612. )
  613.  
  614. rem Находим количество вхождений слова к замене в целевую строку как разницу
  615. rem между ее исходной длиной и ее длиной после удаления всех вхождений слова к
  616. rem замене, деленную на длину слова к замене.
  617. for /f "tokens=1,2 delims==" %%# in ("X=LenX") do %_GetLen% || goto _Exit
  618. for /f delims^=^ eol^= %%A in ("!S!") do set Y=!X:%%A=!
  619. for /f "tokens=1,2 delims==" %%# in ("Y=LenY") do %_GetLen% || goto _Exit
  620. set /a OccurrencesX=(LenX-LenY)/LenS
  621.  
  622. rem Извлекаем часть целевой строки после последнего вхождения слова к замене.
  623. set Y=!X!
  624. for /f delims^=^ eol^= %%B in ("!S!") do (
  625. for /l %%A in (1,1,%OccurrencesX%) do set Y=!Y:*%%B=!
  626. )
  627. for /f "tokens=1,2 delims==" %%# in ("Y=LenY") do %_GetLen% || goto _Exit
  628. set /a Pos=LenS+LenY
  629.  
  630. :PosDefined
  631. (for /f "delims=" %%A in ("!Y!") do (
  632. endlocal
  633. if not errorlevel 1 set Pos=%Pos%
  634. set Y=%%A
  635. )) || (
  636. rem Заменяемое слово замыкает строку.
  637. endlocal
  638. set Pos=%LenS%
  639. )
  640.  
  641. rem Помещаем строки во временный файл.
  642. rem Z - измененная целевая строка.
  643. if defined Z set Z=
  644. set /a LineCt=SkipVal+1
  645. (for /f "%skip% delims=" %%A in ('findstr.exe /n ".*" "%File2%"') do (
  646. set A=%%A
  647. setlocal enabledelayedexpansion
  648. if !LineCt! == !LineX! (
  649. set Z=!X:~,-%Pos%!!R!!Y!
  650. if not defined Z (
  651. echo Too long line.
  652. set errorlevel=12
  653. goto _Exit
  654. )
  655. >>"!_TmpFile2!" echo !Z!|| (
  656. if not defined _Bare set errorlevel=4
  657. goto _Exit
  658. )
  659. ) else (
  660. set A=!A:*:=!
  661. >>"!_TmpFile2!" echo(!A!|| (
  662. set errorlevel=4
  663. goto _Exit
  664. )
  665. )
  666. endlocal
  667. set /a LineCt+=1
  668. )) || goto _Exit
  669.  
  670. rem Заменяем исходный файл на временный.
  671. >nul copy "%_TmpFile2%" "%File%" && del "%_TmpFile2%" && (
  672. echo Done.
  673. ) || (
  674. for /f "delims=" %%A in ("%_TmpFile2%") do (
  675. echo Can't replace file.
  676. echo Temporary file %%A retained.
  677. set _TmpFile2=
  678. set errorlevel=11
  679. )
  680. )
  681. goto _Exit
  682.  
  683. rem >>> Переходим в блок "User code 3".
  684.  
  685. rem =============================== User code 2 <==============================
  686.  
  687. rem Here is the place for aux subroutines
  688.  
  689. rem ==============================> User code 3 ===============================
  690.  
  691. :_Help
  692. %_DisplayBanner%
  693. echo Заменяет в файле последнее вхождение слова на другое слово (опционально - при
  694. echo наличии слова-триггера).
  695. echo Ограничение на длину строк: 8180 для строк 1-9, 8179 для строк 10-99 и т. д.
  696. echo/
  697. echo Значения берутся из ini-файла. Ini-файл ищется в текущем каталоге, если не
  698. echo найден, то в каталоге скрипта. Для создания ini-файла со значениями по
  699. echo умолчанию создайте в текущем каталоге файл нулевой длины с именем скрипта и
  700. echo расширением ini и запустите скрипт повторно.
  701. echo/
  702. echo Usage:
  703. echo %~nx0 ^<file^>
  704. echo/
  705. echo Exit codes: 0 OK
  706. echo 1 General error
  707. echo 2 Can't read ini file
  708. echo 3 Invalid or missing ini file value
  709. echo 4 Can't write tmp file
  710. echo 10 Can't read file
  711. echo 11 Can't replace file
  712. echo 12 Too long line
  713. echo -1 Invalid syntax
  714. for %%a in ("%~n0.ini") do if _%%~za == _0 call :_RestoreIni
  715. set errorlevel=-1
  716. goto _Exit
  717.  
  718. :_RestoreIni
  719. echo/
  720. echo Restoring default ini file...
  721. >"%~n0.ini" (
  722. setlocal enabledelayedexpansion
  723. echo ;Lines starting with ";" are comments.
  724. echo ;A flag may be either off (not defined^) or on (defined, actual value doesn't
  725. echo ;matter^). To clear a flag, specify no value after "=".
  726. echo/
  727. echo ;Слово-триггер. Если определено, замена произойдет только при его наличии в
  728. echo ;файле.
  729. echo ;T=Таня
  730. echo/
  731. echo ;Слово, которое заменяем. Должно быть определено.
  732. echo ;S=Коля
  733. echo/
  734. echo ;Слово, на которое заменяем. Должно быть определено.
  735. echo ;R=Изя
  736. echo/
  737. echo ;[флаг] Игнорировать различия в регистре при поиске слова-триггера. По умолчанию
  738. echo ;выключено.
  739. echo ;T_CaseInsensitive=1
  740. echo/
  741. echo ;[флаг] Игнорировать различия в регистре при поиске слова к замене. По умолчанию
  742. echo ;выключено.
  743. echo ;S_CaseInsensitive=1
  744. echo/
  745. rem -----------------------------------------------------------------------
  746. rem Add your entries before the line above. Do not change the lines below.
  747. rem -----------------------------------------------------------------------
  748. if defined _Bare (set State=on) else set State=off
  749. echo ;[flag] Don't display info and error messages. Useful if the output is going to
  750. echo ;be parsed. Default is !State!.
  751. echo ;_Bare=1
  752. echo/
  753. if defined _ExecTime (set State=on) else set State=off
  754. echo ;[flag] Display script execution time. Ignored if _Bare is set. Default is !State!.
  755. echo ;_ExecTime=1
  756. echo/
  757. echo ;Pause on exit:
  758. echo ;^<none^> = Never
  759. echo ;^<n^> = If errorlevel is equal to or higher than ^<n^>
  760. echo ;* = Always
  761. if defined _PauseOnExit (set "State=%_PauseOnExit%") else set "State=<none>".
  762. echo ;Ignored if invalid or if _Bare is set. Default is !State!.
  763. echo ;_PauseOnExit=1
  764. echo/
  765. if defined _RetainTmpFiles (set State=on) else set State=off
  766. echo ;[flag] Don't delete temporary files. Default is !State!.
  767. echo ;_RetainTmpFiles=1
  768. ) && echo OK.
  769. exit /b
  770.  
  771. rem =============================== User code 3 <==============================
  772.  
  773. rem v2019-05-03_2
  774.  
  775. :_Exit
  776. (
  777. if not defined _RetainTmpFiles (
  778. if defined _TmpFileCt %_EnableDE%
  779. for /l %%B in (1,1,%_TmpFileCt%) do (
  780. if exist "!_TmpFile%%B!" del "!_TmpFile%%B!"
  781. )
  782. )
  783. if not defined _Bare (
  784. echo/
  785. echo Exit code: %errorlevel%
  786. if defined _ExecTime %_DisplayExecTime%
  787. %_DisableDE%
  788. for /f "delims= eol=" %%A in ("%_PauseOnExit%") do (
  789. if * == %%A (
  790. pause
  791. ) else (
  792. if %errorlevel% geq %%A pause
  793. )
  794. )
  795. )
  796. exit /b %errorlevel%
  797. )
  798.  
  799. ===============================================================================
  800.  
  801. Правила для ППП
  802. v2019-04-22_1
  803. 1. Символы <>|&) (последний - при использовании в команде, а не для разделения
  804. других команд) нужно эскейпить.
  805. При непосредственном определении в ППП 1-го уровня (вызываемых
  806. непосредственно) используется 1-кратное эскейпирование (^|), 2-го уровня
  807. (вызываемых из ППП 1-го) - 3-кратное (^^^|), 3-го уровня (вызываемых из ППП
  808. 2-го) - 7-кратное.
  809. В ППП 1-го уровня можно использовать и 3-кратное эскейпирование. Это
  810. позволяет сделать ППП универсальной - запускаемой на любом уровне. При этом
  811. на уровне 1 и 2 такую ППП можно запускать непосредственно, а для запуска на
  812. уровне глубже 2-го требуется замена на лету: %Test:^=^^^%; такой же формат
  813. вызова допустим и на 2-м уровне.
  814. Для универсальных ППП нужно учитывать следующее:
  815. 1) "|" отрубает delayed expansion в предшествующей команде, если она
  816. заключена в скобки. Это верно всегда, но для универсальных ППП приобретает
  817. особую актуальность - см. ниже;
  818. 2) любая команда, за которой следует другая (после "&", "|", "&&" или "||"),
  819. должна заключаться в скобки. Блоки FOR и IF заключаются в скобки вообще
  820. всегда; необязательно это только в случае, когда они являются последней
  821. командой в блоке команд;
  822. 3) после спецсимволов должен идти пробел. К спецсимволам относятся, кроме
  823. указанных выше, ">" и, вероятно, "<" (последний, опять-таки вероятно, должен
  824. предваряться пробелом). При несоблюдении этого правила (">nul" вместо
  825. "> nul") имя устройства при раскрытии съедается и перенаправление происходит
  826. в файл (">nul fc" => "> fc"), что чревато потерей информации;
  827. 4) разноуровневое эскейпирование недопустимо. Актуально для FOR с сетом в
  828. виде вывода другой команды: спецсимволы в ней нужно эскейпить и при
  829. использовании такой конструкции "напрямую" (не в составе ППП), поэтому
  830. эскейпирование там будет на уровень глубже (3-кратное для ППП 1-го уровня).
  831. Для обхода ограничения используем вспомогательную переменную:
  832. (set "_B=2>&1 >nul")%^^^&^
  833. for /f "delims=" %%A in ('!_B! fc.exe "!%%#!" "|"') do ...
  834. Если есть возможность, ППП следует делать универсальными, так как такие ППП
  835. не накладывают ограничений на родителя при использовании в качестве
  836. вложенных. Если уровень вызова ППП жестко определен, универсальной ее делать
  837. не нужно.
  838. Код универсальных ППП заключается в скобки. Отступление от этого правила
  839. возможно, если формат вызова ППП фиксирован (не требуется анализ кода
  840. возврата) и предполагает заключение в скобки команды вызова (FOR).
  841. 2. Конкатенатор ("&") указывается в той же строке, что и команда, за которой он
  842. следует.
  843. 3. При анализе результата выполнения ППП через && и || нужно учитывать, что &&
  844. и || сработают на результат выполнения последней команды, в т. ч. не
  845. меняющей errorlevel, например ENDLOCAL или невыполнившийся IF.
  846. 4. При использовании
  847. (for /f %%z in ("") do cd) || rem
  848. нужно помнить, что после разворачивания ППП эта команда должна быть
  849. последней в строке.
  850. 5. Имена ППП, для которых планируется сжатие кода, универсальных ППП и вообще
  851. всех, которые могут быть вложенными, не должны содержать "!".
  852. 6. Нужно помнить, что к последней в блоке команде прибавятся все пробелы,
  853. образующие отступ для закрывающей скобки блока. Это актуально в первую
  854. очередь для ECHO (закрывающую скобку указываем в той же строке) и SET
  855. (используем кавычки).
  856.  
  857. Changelog:
  858.  
  859. v1.0.7
  860. [-] Неправильно обрабатывались слова, начинающиеся с двойной кавычки.
  861. [-] Неправильно обрабатывались строки, начинающиеся с "?".
  862. [?] Если строка содержит символ NUL, он и все следующие за ним символы
  863. отбрасываются.
  864.  
  865. v1.0.6
  866. [-] Неправильно обрабатывалась ошибка, если полный путь к файлу превышал допустимую
  867. длину.
  868.  
  869. v1.0.5
  870. [-] Неправильно обрабатывалась ошибка, если полное имя файла состояло из одних
  871. кавычек.
  872.  
  873. v1.0.4
  874. [*] Чуть улучшена производительность.
  875.  
  876. v1.0.3
  877. [-] Если заменямое слово стояло в конце строки, при замене слово дублировалось.
  878. [-] Неверно обрабатывались слова, начинающиеся с "*".
  879. [-] Игнорировались различия в регистре при поиске, если слово не содержало "="
  880. или "!".
  881. [-] Значения переменных наследовались из внешнего процесса.
  882. [+] Регистронезависимый поиск.
  883. [+] Сообщения при успешном завершении операции.
  884. [*] Улучшена производительность.
  885.  
  886. v1.0.2
  887. First public release.
  888. [+] Поддержка триггеров и слов к замене, содержащих "=" и/или "!".
  889.  
  890. v1.0.1
  891. [+] Проверка строк на длину.
  892.  
  893. v1.0.0
  894. [?] Триггеры и слова к замене, содержащие "=" и/или "!", не поддерживаются.
RAW Paste Data