Тема: CMD/BAT: Пакетные макросы с параметрами
С некоторого времени стал почитывать ресурс dostips.com. Наткнулся на интересное решение, выведенное в название темы.
Существует два способа использования повторяющего кода
1. Подпрограмма
2. Макрос
Макрос быстрее так как отсутствуют накладные расходы на (чаще всего) передачу параметров и (всегда) вызов подпрограммы. Тем не менее, использовать макросы в компилируемых языках надо с осторожностью, так как это приводит к увеличению размера программы. Однако это несущественно в языках интерпретируемых.
По этой ссылке Batch "macros" with arguments - Major Update приведена наиболее завершенная реализация макросов. Вообще, интересно почитать все обсуждение (5 страниц). Там много текста и очень много кода, однако если удалить все неплохие "красивости" и оставить только самое главное получится следующее.
Макрос в пакетном сценарии это переменная коружения:
set "my_macro=echo Hello, world!"
%my_macro%
%my_macro%
И так, один макрос - множество вызовов. Но более сложные и полезные макросы писать сложнее.
Для начала рассмотрим подпрограмму CMD/BAT: Нахождение длины строки. Здесь все относительно просто: есть метка, куда осушествляется вызов, запоминается текущее положение, исполняется код, затем возврат на текущее положение
@echo off
setlocal
set "STRING=hello, world! from function"
call :strlen STRING STRLEN
set STR
endlocal
goto :EOF
:: call :strlen STRVAR [STRLEN]
:strlen
setlocal enabledelayedexpansion
set "str=A!%~1!"
set "len=0"
for /l %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal & if "%~2" neq "" (set %~2=%%v) else (echo:%%v)
goto :EOF
Чтобы перевести этот код в макрос требуется существенно дописать. Обратите внимание, как производится конкатенация строк кода, так чтобы сохранить прежнее форматирование и правильность кода:
@echo off
setlocal
:: макрос, который позволяет передавать параметры вызываемым макросам
set macro.call=for /f "tokens=1-26" %%a in
:: макрос вычисления длины строки
set macro.strlen=do (^
setlocal enableDelayedExpansion^
^& set "str=A!%%~a!"^
^& set "len=0"^
^& ( for /l %%A in (12,-1,0) do^
set /a "len|=1<<%%A"^
^& for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"^
)^
^&(for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set %%~b=%%v) else (echo:%%v)^
)
set "STRING=hello, world! from macro"
%macro.call% ( "STRING STRLEN" ) %macro.strlen%
set STR
endlocal
goto :EOF
Коллеги в том обсуждении пошли чуть дальше и добавили немного синтаксическогосахара. Кажется так горяздо лучше - достаточно позаботиться о том, чтобы в конце каждой строки присутствовал макрос %\n%. Но необходимо не забывать об экранировании спец.символов: &, |, <, >.
@echo off
setlocal
:: Определяет переменную ВОЗВРАТ КАРЕТКИ (используется как !CR!)
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
:: Определяет переменную ПЕРЕВОД СТРОКИ (используется как !LF!)
set LF=^
:: Две пустые строки сверху важны - не удалять!
:: ПЕРЕВОД СТРОКИ, который может использоваться как %xLF%
set ^"xLF=^^^%LF%%LF%^%LF%%LF%"
:: Определяет новую строку с продолжением строки
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
:: макрос, который позволяет передавать параметры вызываемым макросам
set macro.call=for /f "tokens=1-26" %%a in
:: макрос вычисления длины строки
set macro.strlen=do (%\n%
setlocal enabledelayedexpansion%\n%
set "str=A!%%~a!"%\n%
set "len=0"%\n%
for /l %%A in (12,-1,0) do (%\n%
set /a "len|=1<<%%A"%\n%
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
)%\n%
for %%v in (!len!) do endlocal ^& if "%%~b" neq "" (set %%~b=%%v) else (echo:%%v)%\n%
)
set "STRING=hello, world! from macro"
%macro.call% ( "STRING STRLEN" ) %macro.strlen%
set STR
endlocal
goto :EOF
Личное мнение - красиво, но сложно для использования в большинстве сценариев. Но может быть весьма полезна там, где требуется скорость. Но стоит ли для этого использовать интерпретатор?
Хотя коллега с того форума написал на чистом dos shell игру Змейка, используя описанную здесь технологию.