1

Тема: CMD/BAT: возврат значений из процедур через параметры

Как известно, командный процессор NT-систем позволяет организовать подобие процедур на основе меток внутри пакетного файла и вызова нового контекста пакетного файла (А можно создать в bat-файле функцию?).

Использование процедур:

@echo off
setlocal enableextensions enabledelayedexpansion

set /a i=1
set /a j=2

call :Sub %i% %j%
echo i=%i%, j=%j%, k=%k%

endlocal
exit /b 0
rem ==========================================================================

rem ==========================================================================
:Sub
    set /a k=%1 + %2

    exit /b 0
rem ==========================================================================

i=1, j=2, k=3

Вызов процедуры производится с помощью команды «call», возврат из нового контекста в старый — командой «exit /b».

Но тут есть одна проблема: в новом контексте доступны все переменные среды старого контекста, что создаёт определённые проблемы. Можно, конечно, определить в процедуре собственный уровень локализации переменных среды командой «setlocal», однако при этом становятся недоступны все переменные среды «родительского» контекста, поскольку любая переменная будет являться локальной и будет существовать только внутри процедуры.

Переменная среды «k» является локальной для процедуры:

@echo off
setlocal enableextensions enabledelayedexpansion

set /a i=1
set /a j=2

call :Sub %i% %j%
echo i=%i%, j=%j%, k=%k%

endlocal
exit /b 0
rem ==========================================================================

rem ==========================================================================
:Sub
    setlocal enableextensions enabledelayedexpansion
    set /a k=%1 + %2

    endlocal
    exit /b 0
rem ==========================================================================

i=1, j=2, k=

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

@echo off
setlocal enableextensions enabledelayedexpansion

set /a i=1
set /a j=2

call :Sub %i% %j% k
echo i=%i%, j=%j%, k=%k%

endlocal
exit /b 0
rem ==========================================================================

rem ==========================================================================
:Sub
    setlocal enableextensions enabledelayedexpansion
    
    set /a Summa=%1 + %2
    
    endlocal & set /a %3=%Summa%
    exit /b 0
rem ==========================================================================

i=1, j=2, k=3

Передача входных параметров в процедуру осуществляется стандартным образом с помощью команды «call :<Метка процедуры>»:

call :Sub %i% %j% …

Выходные параметры указываются просто именем переменной:

call :Sub … k

Возврат значений производится в одной строке с командой «endlocal» процедуры как «set <номер параметра>=<значение>»:

endlocal & set /a %3=%Summa%

Только так и не иначе. Что при этом происходит? А происходит следующее:
1. Строка разбирается командным процессором и происходит раскрытие переменных среды. Таким образом, вышеуказанная строка превращается в:

endlocal & set /a k=3

поскольку параметр «%3» раскрывается в строку «k», а переменная среды «%Summa%», находящаяся внутри процедуры «:Sub», раскрывается в её значение «3».
2. Исполняется первая команда в очереди команд:

endlocal

При этом происходит закрытие локального контекста и становится доступным родительский контекст.
3. В родительском контексте исполняется вторая команда:

set /a k=3

в результате чего переменная «k» получает значение «3».
4. Командой

exit /b …

осуществляется возврат из процедуры.


Замечание:

Чтобы стало понятнее, приведу несколько неправильных способов.

set /a k=%Summa%
endlocal

В данном случае происходит присвоение значения локальной переменной «k», что никак не отразится на родительском контексте.

set /a %3=%Summa%
endlocal

То же самое. Параметр %3 раскрывается в значение «k», а результат исполнения команды «set /a k=…» мы рассмотрели чуть выше.

endlocal
set /a %3=%Summa%

Здесь переменная %Summa% из контекста процедуры уже не существует (в понимании командных файлов будет равна пустому значению), в результате чего данная команда выродится в «set /a k=». Но мы ведь не этого хотели, правда?

set /a %3=%Summa% & endlocal

«А почему бы команды не поменять местами? Какая нам разница?» А вот какая. Вначале будет выполняться команда «set /a %3=%Summa%». После раскрытия переменных мы получим вроде бы ту же самую правильную команду «set /a k=3». Однако, в этот момент мы ещё находимся внутри локального контекста процедуры, и переменная «k» — тоже будет локальная. Значит, следующей командой («endlocal») эта переменная будет уничтожена. Вот почему такой способ тоже не годится.

Остаётся только добавить, что на данный метод я наткнулся, разбираясь со скриптом TSCMD032 FAQ: How can I convert a decimal number to binary, octal, hexadecimal? Assorted NT/2000/XP/.. CMD.EXE script tricks written by Timo Salmi, в момент публикации обнаружилось, что на том же сайте существует и отдельная статья на эту тему: TSCMD018 FAQ: How does one build re-usable script function modules? Assorted NT/2000/XP/.. CMD.EXE script tricks written by Timo Salmi.

2

Re: CMD/BAT: возврат значений из процедур через параметры

В ряде случаев, когда требуется одно возвращаемое процедурой значение, может оказаться удобным использовать для возврата этого значения не указанную параметром переменную окружения (как описано в предыдущем посте), а использовать для этого метку процедуры (получив «как бы функцию»), например:

@echo off
setlocal enableextensions enabledelayedexpansion

call :Double 5
echo Result of Double(): [%Double%]

endlocal
exit /b 0

:Double
    setlocal enableextensions enabledelayedexpansion
    set sOut=%~0
    set /a sResult = %~1 + %~1
    
    endlocal & set %sOut:~1%=%sResult%
    exit /b
Result of Double(): [10]

Автор идеи — smaharbA.

3

Re: CMD/BAT: возврат значений из процедур через параметры

Как заметил Arigato, результат функции, вычисляющей целочисленное значение, можно возвращать через код завершения errorlevel:

@echo off
setlocal enableextensions
set /p a=Enter a: 
call :sqr %a%
echo %a%^^2 = %errorlevel%
pause
endlocal
exit /b 0

:sqr
:: Квадрат числа %1
setlocal
set /a result=%1*%1
endlocal & exit /b %result%

(Таким образом можно возвращать целое 32-битное число со знаком, т.е. от -2147483648  до 2147483647, иначе оно будет преобразовано к этому диапазону.)