Skip to main content

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.

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.

prefix_lines.bat
@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.

process_lines.bat
@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.

dualmode.bat
@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.