How to Process Piped Input (stdin) in a Batch Script
In the world of command-line tools, "Piping" is the process of taking the output of one command and feeding it directly into the input of another. For example, dir | find ".txt" pipes the file list into the find tool. Creating a Batch script that can swallow this "Standard Input" (stdin) allows you to build custom filters, loggers, and data processors that work seamlessly with other Windows commands.
This guide will explain the techniques for capturing and processing piped data in a Batch script.
The Challenge: No Native "%STDIN%" Variable
Unlike command-line arguments (%1, %2, etc.), Batch does not have a special variable for piped input. To read the data coming through the pipe, we must use a workaround involving the FOR /F command.
Method 1: Using FOR /F with findstr (Recommended)
The most reliable way to process every line coming through a pipe is to use findstr "^" as the stdin reader inside a FOR /F loop. The findstr "^" pattern matches the start of every line, effectively passing all input through without modification.
The "Filter" Script: prefix_lines.bat
This script takes any input and adds "[LOG]" to the start of every line.
@echo off
setlocal enabledelayedexpansion
:: Use findstr "^" to read every line from stdin
:: "delims=" preserves leading spaces and tabs
for /f "delims=" %%a in ('findstr /n "^"') do (
:: Remove the line number prefix added by /n (format: "1:actual line")
set "line=%%a"
set "line=!line:*:=!"
echo [LOG] !line!
)
endlocal
How to Use it:
echo This is a test | prefix_lines.bat
Output:
[LOG] This is a test
Another example:
dir /b | prefix_lines.bat
Output:
[LOG] file1.txt
[LOG] file2.doc
...
Method 2: Processing Multiline Data with Counting
If you are piping the output of a command like tasklist and need to track how many lines were processed, you can add a counter to the filter loop.
@echo off
setlocal enabledelayedexpansion
set "LineCount=0"
echo [FILTER] Processing incoming data...
echo.
:: Loop through every line of stdin
for /f "delims=" %%x in ('findstr /n "^"') do (
set "line=%%x"
set "line=!line:*:=!"
set /a "LineCount+=1"
echo [!LineCount!] !line!
)
echo.
echo [DONE] Processed !LineCount! line(s).
endlocal
How to Use it:
tasklist | process_lines.bat
Output:
[FILTER] Processing incoming data...
[1]
[2] Image Name PID Session Name Session# Mem Usage
[3] ========================= ======== ================ =========== ============
[4] System Idle Process 0 Services 0 8 K
[5] System 4 Services 0 604 K
...
[DONE] Processed 85 line(s).
Method 3: Dual-Mode Script (Piped Input or File Argument)
A professional filter script should accept input from either a pipe or a file argument, adapting automatically.
@echo off
setlocal enabledelayedexpansion
set "LineCount=0"
:: Check if a file argument was provided
if not "%~1"=="" (
if not exist "%~1" (
echo [ERROR] File not found: %~1
exit /b 1
)
echo [MODE] Reading from file: %~1
echo.
for /f "usebackq delims=" %%a in ("%~1") do (
set /a "LineCount+=1"
echo [!LineCount!] %%a
)
goto :Summary
)
:: No file argument - read from stdin (pipe)
echo [MODE] Reading from piped input...
echo.
for /f "delims=" %%a in ('findstr /n "^"') do (
set "line=%%a"
set "line=!line:*:=!"
set /a "LineCount+=1"
echo [!LineCount!] !line!
)
:Summary
echo.
echo [DONE] Processed !LineCount! line(s).
endlocal
How to Use it:
:: Piped mode:
dir /b | dualmode.bat
Output:
[MODE] Reading from piped input...
[1] file1.txt
[2] file2.doc
...
[DONE] Processed 85 line(s).
Another example:
:: File mode:
dualmode.bat mydata.txt
Output:
[MODE] Reading from file: mydata.txt
[1] line1
[2] line2
...
[DONE] Processed 10 line(s).
How to Avoid Common Errors
Wrong Way: Using more as the stdin reader
Using for /f ... in ('more') can pause and wait for a keypress when the input exceeds one screen height (typically 25 lines). This makes it unsuitable for processing large outputs like tasklist or dir /s.
Correct Way: Use findstr /n "^" as the stdin bridge. The /n flag adds line numbers (e.g., 1:line content), which prevents for /f from skipping blank lines (since every line now starts with a number). The set "line=!line:*:=!" substitution removes the line number prefix.
Problem: Blank lines being skipped
By default, FOR /F skips empty lines in the input. If your data has meaningful blank lines (like section separators), they will be silently dropped.
Solution: Use findstr /n "^" which adds line numbers to every line (including blank ones), preventing FOR /F from skipping them. Strip the numbers afterward as shown in Method 1.
Problem: Special Characters in piped data
If the data coming through the pipe contains special Batch characters (&, |, <, >, ^), they can be interpreted as commands and crash the script.
Solution: Always use setlocal enabledelayedexpansion and reference the line content with !variable! rather than %variable%. Delayed expansion treats the value as literal text rather than re-parsing it for command operators.
Best Practices and Rules
1. Identify Empty Input
If no data is piped and no file argument is provided, the findstr command will wait indefinitely for keyboard input. Always document that the script expects piped input, or implement the dual-mode pattern (Method 3) to accept file arguments as an alternative.
2. Efficiency
Reading piped input line-by-line in Batch is relatively slow compared to native C or Python code. For massive multi-gigabyte logs, consider using PowerShell instead.
3. Whitespace Handling
By default, FOR /F splits lines on spaces and tabs and can strip leading whitespace. Always use delims= (empty delimiters) if you need to preserve the exact formatting of the input data.
Conclusions
Processing piped input in Batch script is an essential skill for building advanced command-line utilities. By utilizing the FOR /F loop with findstr as a bridge between the system pipe and your variable logic, you can create scripts that filter, transform, and analyze data in real-time. This capability turns your scripts from isolated files into integrated "Chain Links" in the Windows command-level ecosystem.