Subroutines and Return Values
As a batch file grows, writing the same process over and over becomes tedious and also breeds bugs. Subroutines (pseudo-functions) are useful in such cases.
Batch files have no “function definition” syntax like other programming languages, but you can achieve a structure close to a function by using CALL :labelname. This article walks through subroutines step by step, from the basics of creating one to passing arguments and returning numbers and strings.
Basic Structure of a Subroutine
A subroutine consists of the following elements.
| Element | Description |
|---|---|
CALL :labelname | Calls the subroutine |
:labelname | A label that marks the start of the subroutine |
EXIT /B exitcode | Returns from the subroutine to the caller |
GOTO :EOF | Equivalent to EXIT /B (the form without an exit code). Ends the subroutine |
To separate the main processing from the subroutines, the important point is to place GOTO :EOF at the end of the main code. Without it, execution flows straight into the subroutine code.
@echo off
setlocal
CALL :GREET "Alice"
CALL :GREET "Bob"
GOTO :EOF
:GREET
echo Hello, %~1!
EXIT /B 0
:EOF is a special label that means the end of the file. GOTO :EOF ends the subroutine like EXIT /B, but you cannot specify an exit code, and the previous ERRORLEVEL is left as is. If you want to specify an exit code, use EXIT /B N. Either can be used at the end of the main code.
Subroutines work wherever you write them in the file, but the common convention is to write them together after the main processing (after GOTO :EOF). This improves code readability.
Passing Arguments to a Subroutine
You can pass arguments after the label name, as in CALL :labelname arg1 arg2 .... Inside the subroutine, you access them with %1, %2, and so on.
| Notation | Meaning |
|---|---|
%1 | The 1st argument (including quotes) |
%~1 | The 1st argument (with surrounding quotes removed) |
%2 | The 2nd argument |
@echo off
setlocal
CALL :SHOW_INFO "Bob" "Tokyo" 30
GOTO :EOF
:SHOW_INFO
echo Name: %~1
echo City: %~2
echo Age: %3
EXIT /B 0

中小企業経営者のためのRPA入門 RPA導入を成功させる方法

60分でわかる! AIエージェント 超 入門

コマンドラインの黒い画面が怖いんです。

知識・才能ゼロでもらく~に月10万円稼ぐ! よくわかるAI副業超入門
If you pass an argument enclosed in quotes, such as "Bob", %1 gives "Bob" (with quotes) and %~1 gives Bob (without quotes). When handling strings, it is safer to use %~1.
Numeric Return Values (ERRORLEVEL)
To return a number from a subroutine, specify an exit code (N) with EXIT /B N. The caller receives the value with %ERRORLEVEL% or IF ERRORLEVEL N.
By convention, return 0 on success and a value of 1 or greater on an error or failure.
@echo off
setlocal
CALL :IS_EVEN 4
IF %ERRORLEVEL% EQU 0 (
echo 4 is even.
) ELSE (
echo 4 is odd.
)
CALL :IS_EVEN 7
IF %ERRORLEVEL% EQU 0 (
echo 7 is even.
) ELSE (
echo 7 is odd.
)
GOTO :EOF
:IS_EVEN
set /a CHECK=%1 %% 2
EXIT /B %CHECK%
The %% in set /a CHECK=%1 %% 2 is an escape that represents the % character itself in a batch file. %1 %% 2 calculates “the remainder of %1 divided by 2.” If the remainder is 0 the number is even, and if it is 1 the number is odd.
If you do not specify a number with EXIT /B, the current ERRORLEVEL is returned as is. To prevent unintended values from being returned, we recommend specifying it explicitly, as in EXIT /B 0.
String Return Values (The endlocal & set Trick)
Unlike numbers, returning a string is a little tricky. When you use setlocal inside a subroutine, the variable scope is closed, so all variables inside the subroutine disappear at the point you call endlocal.
The technique that solves this problem is to put endlocal & set VariableName=Value on one line.
Why Does It Work on One Line
Command Prompt parses an entire line before executing it. When you write endlocal & set RESULT=Value, the value on the right side of set is parsed before endlocal is executed. In other words, after the variable values inside the subroutine are expanded into the right side of set, endlocal runs, and the variable is set in the caller’s scope.
@echo off
setlocal
CALL :GET_GREETING "Alice" RESULT
echo %RESULT%
GOTO :EOF
:GET_GREETING
setlocal
set NAME=%~1
set GREETING=Hello, %NAME%!
endlocal & set "%~2=%GREETING%"
EXIT /B 0
In this pattern, the second argument (%~2) is given the name of the variable that stores the return value. Because the caller can freely specify the variable name, this increases versatility.
Pattern Using a Fixed Variable Name
Instead of passing the variable name as an argument, a simple and commonly used method is to use a fixed variable name (for example, RESULT).
@echo off
setlocal
CALL :FORMAT_DATE
echo Formatted date: %RESULT%
GOTO :EOF
:FORMAT_DATE
setlocal
set YEAR=%DATE:~0,4%
set MONTH=%DATE:~5,2%
set DAY=%DATE:~8,2%
set LOCAL_RESULT=%YEAR%%MONTH%%DAY%
endlocal & set "RESULT=%LOCAL_RESULT%"
EXIT /B 0
The format of %DATE% changes depending on regional settings. The example above assumes a yyyy/MM/dd format such as 2026/04/24, so the date cannot be extracted correctly in other environments. For a way to get the date independent of the environment, see the related article on getting the date and time.
endlocal & set VariableName=Value must be written on one line. If you split it into two lines, the variables inside the subroutine disappear at endlocal, and the value of set becomes empty.
When another command follows on the same line (set VariableName=Value & ...), the space before the & becomes part of the value. Also, if the value contains special characters such as & or ), a syntax error may occur. Enclosing the whole assignment in quotes, as in set "VariableName=Value", avoids these problems. Handling values that contain % or ! is complicated, so it is safest to limit this technique to returning simple strings of alphanumeric characters or similar.
By placing multiple set commands on one line, as in endlocal & set "%~2=%VAL1%" & set "%~3=%VAL2%", you can return multiple values at once. This is convenient when combined with the pattern of receiving variable names as arguments.
Practical Example: A File Existence Check Subroutine
As a practical example that combines what you have learned so far, here is a subroutine that checks the existence of multiple files and returns the number of missing files.
@echo off
setlocal
CALL :CHECK_FILES
IF %ERRORLEVEL% NEQ 0 (
echo %ERRORLEVEL% required file(s) are missing. Aborting.
GOTO :EOF
)
echo All files were found. Starting processing.
GOTO :EOF
:CHECK_FILES
setlocal
set MISSING=0
for %%f in (config.ini data.csv template.xlsx) do (
IF NOT EXIST "%%f" (
echo Warning: %%f was not found.
set /a MISSING+=1
)
)
endlocal & exit /b %MISSING%
On the last line, %MISSING% is expanded before endlocal runs, so the value of the variable inside the subroutine can be returned to the caller as the exit code.
Example run when config.ini does not exist:
When all files exist:
Summary
| Operation | Notation |
|---|---|
| Calling a subroutine | CALL :labelname arg1 arg2 ... |
| Ending a subroutine | EXIT /B exitcode |
| Referencing arguments | %1, %~1 (removes quotes) |
| Returning a number | EXIT /B N → reference %ERRORLEVEL% in the caller |
| Returning a string | Run endlocal & set VariableName=Value on one line |
| Preventing fall-through | Place GOTO :EOF at the end of the main code |