How to Return Multiple Values from a Subroutine in Batch Script
In standard Batch scripting, a subroutine (a labeled block of code called with call :label) can only return a single numeric value via exit /b [number]. However, many complex tasks, like parsing a file path or calculating a date, require you to return multiple pieces of information (like a filename, extension, and file size) back to the main script. To achieve this, you must use a technique that passes variable values across the endlocal boundary.
This guide will explain how to "Export" multiple variables from a local subroutine back to your main logic.
The Challenge: Variable Isolation
When you use setlocal inside a subroutine (which is a best practice to keep your script clean), all variables you create disappear when you run endlocal. To return data, you must "smuggle" it out during the transition.
Method 1: The "Variable Export" Pattern
This is the most reliable way to return strings and numbers from a subroutine. The key insight is that when endlocal and set appear on the same logical line (joined by &), the Batch parser expands the %variables% before endlocal destroys them, but the set assignments take effect after, in the caller's environment.
@echo off
setlocal
:: 1. Call the subroutine
call :GetFileInfo "C:\Windows\explorer.exe"
:: 2. Use the returned values
echo Filename: %OUT_Name%
echo Size: %OUT_Size% bytes
endlocal
pause
exit /b
:GetFileInfo
setlocal
:: Perform logic
set "name=%~nx1"
set "size=%~z1"
:: --- THE EXPORT TRICK ---
:: endlocal destroys the local scope, but %name% and %size%
:: are expanded BEFORE that happens. The SET commands then
:: execute in the caller's scope with the expanded values.
endlocal & set "OUT_Name=%name%" & set "OUT_Size=%size%"
exit /b
Why this works:
The Batch parser processes an entire logical line in two phases. First, it expands all %variable% references. Then, it executes the commands. So on the endlocal & set ... line, %name% and %size% are replaced with their values while the local scope is still active. Then endlocal runs (destroying the local scope), and the set commands run next, writing those already-expanded values into the parent scope.
Method 2: Caller-Defined Variable Names
For reusable subroutines, let the caller specify the names of the return variables as arguments. This prevents variable name collisions and makes the subroutine work like a library function.
@echo off
setlocal
:: The caller chooses the variable names
call :GetFileInfo "C:\Windows\explorer.exe" FileName FileSize
echo Filename: %FileName%
echo Size: %FileSize% bytes
:: Call it again with different variable names, no collision
call :GetFileInfo "C:\Windows\notepad.exe" Name2 Size2
echo Filename: %Name2%
echo Size: %Size2% bytes
endlocal
pause
exit /b
:GetFileInfo
:: %~1 = file path, %~2 = return var for name, %~3 = return var for size
setlocal
set "name=%~nx1"
set "size=%~z1"
endlocal & set "%~2=%name%" & set "%~3=%size%"
exit /b
Why this is better:
The caller controls the variable names, so there is no risk of collision between subroutine calls. The subroutine itself never hardcodes output variable names, making it reusable in any context.
Method 3: Handling Special Characters
If your return values might contain special characters like !, ^, or %, standard percent expansion can corrupt them. Delayed expansion provides a safer path.
@echo off
setlocal
call :BuildPath ResultPath
echo %ResultPath%
endlocal
pause
exit /b
:BuildPath
setlocal EnableDelayedExpansion
:: Value contains special characters
set "p=C:\My Files\Data (Copy)"
:: Use delayed expansion in the export line so that
:: the value is not damaged by premature expansion
endlocal & set "%~1=%p%"
exit /b
When to use this:
Use delayed expansion inside the subroutine when the computed values might contain characters that have special meaning to the Batch parser (!, ^, %, &, |, >, <). The !p! syntax delays expansion until execution time, after the parser has finished interpreting the line structure, so special characters in the value are treated as literal text.
How to Avoid Common Errors
Wrong Way: Using Global Variables Directly
You could just write set MyResult=10 inside the subroutine without using setlocal.
Why it's bad: If your subroutine uses a temporary variable called %temp_file%, and your main script also uses a variable called %temp_file%, the subroutine will overwrite the main script's data. Always use setlocal to protect your variables.
Wrong Way: Putting endlocal and set in Separate Blocks
:: BROKEN: do not do this
endlocal
set "OUT_Name=%name%"
Once endlocal runs, %name% no longer exists. The set command receives an empty string. The endlocal and set commands must be on the same logical line so that variable expansion happens before the local scope is destroyed.
Problem: Variable Collision
If your subroutine returns OUT_Name, make sure your main script doesn't already use OUT_Name for something else.
Best Practice: Use Method 2 (caller-defined variable names) so that the caller controls naming and can avoid collisions. If you use fixed names, choose a specific prefix like RET_ or OUT_ so you always know which variables came from a subroutine.
Best Practices and Rules
1. Prefer Caller-Defined Names
Passing the return variable names as arguments (Method 2) makes subroutines reusable and eliminates naming conflicts between independent parts of your script.
2. Keep the Export Line Flat
Always use the single-line form: endlocal & set "var1=%a%" & set "var2=%b%". Do not split endlocal and the set commands across separate lines or into a parenthesized block with endlocal on its own line: this breaks the expansion trick and results in empty variables.
3. Verify Returned Data
After the call, check whether the returned variables actually contain data. If the subroutine might fail, have it set the return variables to a known empty or error value so the caller can detect the failure.
call :GetFileInfo "nonexistent.txt" Name Size
if not defined Name (
echo [ERROR] Subroutine returned no data. >&2
exit /b 1
)
Conclusions
Returning multiple values from a subroutine is an advanced technique that allows you to build modular, library-like code in Windows Batch. By mastering the endlocal & set ... export pattern, and understanding why it works at the parser level, you bypass the limitations of the single-return-code system and create scripts that can process, transform, and communicate complex data across different sections of your code. This level of sophistication is required for enterprise-grade automation and complex system tools.