8. Redirection & Pipes¶
What & why¶
Redirection sends command output to a file instead of the screen; pipes send it to another command. Batch scripts use these constantly — to build files, suppress noise, capture results, and log. The stream numbers (1 for stdout, 2 for stderr) are where it gets fiddly.
The three streams¶
| Number | Name | Default |
|---|---|---|
0 |
stdin | keyboard |
1 |
stdout | screen |
2 |
stderr | screen |
Writing to a file¶
echo Starting > run.log &rem > overwrites (truncates first)
echo Step 1 >> run.log &rem >> appends
dir /b *.txt >> run.log
> always starts fresh; >> adds to the end (and creates the file if missing).
The null device¶
nul is a magic name that discards everything written to it:
ping -n 1 example.com >nul &rem hide normal output
copy a.txt b.txt >nul &rem hide "1 file(s) copied."
where git >nul 2>nul && echo git present &rem hide both streams, act on success
Redirecting stderr¶
somecommand 2> errors.log &rem stderr to a file, stdout still on screen
somecommand 2>nul &rem discard errors only
somecommand > out.log 2> err.log &rem split the streams
somecommand > out.log 2>&1 &rem BOTH streams into out.log
somecommand 1>&2 &rem send stdout to stderr
2>&1 means "make stream 2 go wherever stream 1 currently goes." Order matters: > out.log 2>&1 works (redirect 1 to the file, then point 2 at the same place). 2>&1 > out.log does not combine them (2 gets pointed at the screen, then 1 moves to the file).
Writing errors from your own script¶
Sending your error messages to stderr lets callers separate them from real output.
Reading from a file: <¶
rem Feed a file to a command's stdin:
sort < unsorted.txt > sorted.txt
rem Answer an interactive prompt:
del *.tmp < nul
< nul feeds an immediate end-of-input, which makes some prompting commands take their default or bail — occasionally useful, but prefer a real /q switch.
Pipes¶
dir /b /s *.log | find /c /v "" &rem count lines (= file count)
tasklist | findstr /i "chrome" &rem filter process list
type big.txt | more &rem page through output
echo %PATH% | find /i "python" &rem is python on PATH?
The left command's stdout becomes the right command's stdin. stderr is not piped unless you add 2>&1 on the left.
Each side of a pipe runs in its own cmd.exe
something | something spawns two subshells. Consequences:
- Variables
setinside a piped command do not survive:echo x | set /p "v="—vis set in the subshell and lost. - Delayed-expansion state and
setlocaldon't carry across. - It's slower than it looks (two process launches).
Workarounds are in Intermediate Lesson 3 (for /F to capture output) and Advanced Lesson 6.
Capturing command output into a variable¶
You can't do set v=$(cmd) like bash. Use for /F:
for /F "delims=" %%v in ('hostname') do set "host=%%v"
echo Running on %host%
for /F "tokens=2 delims=:" %%v in ('ipconfig ^| findstr /i "IPv4"') do set "ip=%%v"
echo IP:%ip%
Note the ^| — inside the for /F ('...') quotes, pipes and redirects must be caret-escaped.
Combining and grouping¶
(echo Line 1 & echo Line 2 & echo Line 3) > file.txt
(
echo [config]
echo debug=1
echo path=%CD%
) > app.ini
command1 & command2 &rem run both, unconditionally
command1 && command2 &rem run command2 only if command1 succeeded
command1 || command2 &rem run command2 only if command1 failed
Special characters and escaping¶
To write a literal >, <, |, & to a file, escape with ^:
Inside for /F '...' command blocks, the same applies to |, >, <.
Common mistakes¶
2>&1before the>— doesn't merge the streams. Put2>&1last.- Space in
> >fileor between2and>(2 > file) — the2gets treated as an argument. Write2>fileor2> file(space after>is fine, not before). - Expecting
setinside a pipe to stick — it runs in a subshell. - Piping when you meant to capture — use
for /Fto get output into a variable. >a file that's open/locked — "The process cannot access the file." Close it, or write elsewhere.- Trailing space before
>>:echo hi >> logwriteshi(with the space). Useecho hi>>logor accept the space. >nulnot hiding errors — that's stdout only; add2>nul.
Exercises¶
- Run a command that fails (e.g.
dir Z:\nope) and send only its error text toerrors.log, keeping stdout on screen. Then send both to one file. - Build
report.txtin one grouped( ... ) > report.txtblock: a title line, a blank line, and the output ofdate /tandtime /t. - Count how many running processes contain "svchost" using
tasklist | findstr /ipiped intofind /c /v "". - Capture the output of
hostnameinto a variable withfor /Fand printThis is <name>. - Capture your IPv4 address into a variable (
ipconfig ^| findstr /i IPv4, split on:), trimming the leading space. - Append a timestamped line to
run.logevery time the script runs:echo %date% %time% run>>run.log.
Recap¶
>overwrites,>>appends,nuldiscards.- Streams:
1=stdout,2=stderr.2>&1merges — and must come after the>. <feeds stdin from a file.|pipes stdout to the next command's stdin.- Each pipe side is a separate
cmd.exe—setinside a pipe is lost. - Capture output into a variable with
for /F "..." %%v in ('command') do set "x=%%v"(escape|/>as^|/^>inside). - Group with
( ... ); chain with&,&&,||.