cheat sheet
echo
Print text to stdout, expand environment variables, write blank lines, redirect output to files, and toggle command-echoing in cmd.exe batch scripts.
echo — Output Text and Control Command Echo
What it is
echo is a built-in cmd.exe command with two distinct roles: (1) it prints a string to standard output, expanding %VARIABLE% references along the way; and (2) it controls whether cmd.exe prints each command before running it (ECHO ON) or suppresses that display (ECHO OFF). Every batch script starts its life with @echo off to silence the echoed commands — the @ prefix suppresses the echo of that specific line before the global setting takes effect.
Availability
echo is built into cmd.exe on every Windows version. PowerShell equivalent: Write-Output (aliased echo).
echo /?
Output:
Displays messages, or turns command-echoing on or off.
ECHO [ON | OFF]
ECHO [message]
Type ECHO without parameters to display the current echo setting.
Syntax
echo takes an optional ON/OFF keyword or any message text. No quotes are needed — everything after the space is printed verbatim.
echo [ON | OFF | message]
Output: (the message or ON/OFF status)
Basic text output
Anything after echo (with a space) is sent to stdout. Environment variables are expanded before printing.
echo Hello, world!
Output:
Hello, world!
echo Current user: %USERNAME%
Output:
Current user: alicedev
echo Today is %DATE% and the time is %TIME%
Output:
Today is Mon 04/28/2026 and the time is 14:22:05.31
Blank lines with echo.
echo followed by a period (no space) prints an empty line. echo alone prints the current ON/OFF state — not a blank line.
echo Line one
echo.
echo Line three
Output:
Line one
Line three
rem Checking the current echo state
echo
Output:
ECHO is on.
ECHO ON and ECHO OFF
echo off suppresses the display of subsequent commands; echo on restores it. Prefix any single command with @ to suppress only that line's echo without changing the global state.
@echo off
echo This line is printed.
echo And this one too.
Output:
This line is printed.
And this one too.
With echo on, each command appears before its output:
echo on
echo Hello
Output:
C:\>echo Hello
Hello
Redirecting echo to a file
Redirect echo output with > (overwrite) or >> (append) to build text files without an editor.
echo [INFO] Build started > build.log
echo [INFO] Compiling src\main.c >> build.log
type build.log
Output:
[INFO] Build started
[INFO] Compiling src\main.c
rem Write a blank line as a separator in a log file
echo. >> build.log
echo [INFO] Done. >> build.log
Output: (none — written to file)
Writing multi-line content with parenthesised blocks
Wrap a series of echo statements in a parenthesised block and redirect once to write a multi-line file efficiently.
(
echo [section]
echo key=value
echo other=42
) > config.ini
type config.ini
Output:
[section]
key=value
other=42
Echoing special characters
Some characters (<, >, |, &, ^) are interpreted by cmd.exe. Escape them with ^ or enclose the entire string in quotes (quotes are then printed literally if not escaped).
echo Redirect: ^> and pipe: ^|
Output:
Redirect: > and pipe: |
rem Ampersand without escape would run two commands
echo Q^&A section
Output:
Q&A section
Suppressing the trailing newline — echo with NUL
There is no built-in flag to suppress the trailing \r\n. The common workaround is set /P with an empty string, which outputs the prompt text without a newline.
<NUL set /P DUMMY=Enter value:
Output:
Enter value:
(cursor stays on same line — useful before reading user input)
Common pitfalls
echoalone shows ON/OFF state — useecho.(no space) for a blank line, not plainecho.- Trailing space in
echo message— the space before the newline is part of the output; it is invisible on screen but can break file comparisons. - Special characters need
^escaping —echo a > bredirects instead of printinga > b; writeecho a ^> b. @echo offmust be the first line — placing it anywhere else still echoes all preceding commands.- Quotes are printed literally —
echo "hello"outputs"hello"with quotes included; there is no auto-stripping.
Real-world recipes
Print a progress header in a batch script
@echo off
echo ============================================================
echo Build Pipeline — %DATE%
echo ============================================================
echo.
echo Step 1: Cleaning output directories...
Output:
============================================================
Build Pipeline — Mon 04/28/2026
============================================================
Step 1: Cleaning output directories...
Create a minimal .env file from batch
@echo off
(
echo APP_ENV=production
echo LOG_LEVEL=warn
echo PORT=8080
) > .env
echo .env written.
Output:
.env written.
Conditional status message
@echo off
if exist C:\Logs\app.log (
echo [OK] Log file found.
) else (
echo [WARN] Log file missing — check the service.
)
Output:
[WARN] Log file missing — check the service.
Append a timestamped entry to a log
echo [%DATE% %TIME%] Deployment complete >> C:\Logs\deploy.log
type C:\Logs\deploy.log
Output:
[Mon 04/28/2026 14:30:01.12] Deployment complete
echo and the dual identity
echo looks like a single command but is really two distinct features sharing one keyword. Form 1 — echo <message> — emits text. Form 2 — echo ON|OFF — toggles command echoing for the current cmd.exe interpreter. The distinction matters when you write echo on thinking you are printing the word "on" — you are actually changing a shell setting. Use echo. on (with the trailing period) or quote tricks to print the literal words ON and OFF.
rem This toggles command echoing, does NOT print "on"
echo on
Output:
(command echoing turned on; no text printed)
rem This prints the literal word "on"
echo.on
Output:
on
echo "on"
Output:
"on"
Internal command — no .exe
echo is implemented inside cmd.exe, not a separate executable. You cannot find it with where echo, you cannot call it directly with CreateProcess, and you cannot replace it with a custom binary by putting one earlier in PATH. The only way to invoke it is through a cmd.exe interpreter (either interactively or via cmd /c).
where echo
Output:
INFO: Could not find files for the given pattern(s).
rem To use echo from outside cmd, wrap with cmd /c
cmd /c "echo Hello from cmd"
Output:
Hello from cmd
All forms of echo
| Form | Effect |
|---|---|
echo | Show current ECHO ON / OFF state |
echo <text> | Print <text> to stdout with trailing CRLF |
echo. | Print an empty line (CRLF only) |
echo. <text> | Same as echo <text> |
echo on | Re-enable command echoing |
echo off | Disable command echoing |
@echo <text> | Print <text> without echoing the echo command itself |
@echo off | Disable echoing globally and suppress this command's own echo |
echo <text> > file | Redirect to a file (overwrite) |
echo <text> >> file | Append to a file |
| `echo | cmd` |
The @ prefix in batch scripts
Without @, every command in a batch script is printed before it runs (because ECHO defaults to ON). The @ symbol suppresses the echo of that one line. Almost every batch script starts with @echo off — the @ hides the echo off command itself; thereafter, no commands are echoed.
echo off
echo Hello
Output:
C:\>echo off
Hello
(echo off was itself echoed before it took effect)
@echo off
echo Hello
Output:
Hello
(@echo off was suppressed; subsequent commands also suppressed by the global state)
For mixed scripts where you want most commands echoed but a few hidden, sprinkle @ per line:
echo ON
echo Step 1
@rem this comment is hidden
echo Step 2
Output:
C:\>echo Step 1
Step 1
C:\>echo Step 2
Step 2
Writing characters that cmd.exe interprets
cmd.exe reserves several characters: <, >, |, &, ^, (, ), %, !. To emit them literally, escape them with the caret ^ or quote the entire argument. The caret is consumed during parsing — it does not appear in output.
echo Pipe: ^| and amp: ^&
Output:
Pipe: | and amp: &
echo Parens: ^(text^) and equals signs: 1+1^=2
Output:
Parens: (text) and equals signs: 1+1=2
rem Percent sign — double it to escape
echo Loading... 50%%
Output:
Loading... 50%
rem Inside delayed-expansion blocks, escape ! as well
setlocal enabledelayedexpansion
echo Hello^^!
Output:
Hello!
Printing leading whitespace
cmd.exe trims one space after the echo keyword. To print a line that starts with extra leading whitespace, use echo: or echo. followed immediately by the spaces, or use parentheses.
echo four leading spaces
Output:
four leading spaces
(works because the visible spaces survive; only the one space after echo is trimmed)
echo: four leading spaces
Output:
four leading spaces
(
echo indented line
) > out.txt
type out.txt
Output:
indented line
echo. alternatives and quirks
echo. (with a period) is the canonical way to print a blank line. Other characters work too but behave slightly differently:
| Form | Result | Notes |
|---|---|---|
echo. | Blank line | Canonical — works everywhere |
echo: | Blank line | Same as echo. |
echo; | Blank line | Works in cmd.exe |
echo/ | Blank line | Works but rare |
echo\ | Blank line | Works |
echo, | Blank line | Works |
echo | "ECHO is on/off." | NOT a blank line — prints state |
The fastest-parsing form is echo. because the period is non-alphabetic and cmd can short-circuit the lookup. In modern Windows the speed difference is microseconds, but in tight loops the choice still matters for legibility.
echo Line 1
echo.
echo Line 3
echo:
echo Line 5
Output:
Line 1
Line 3
Line 5
Delayed expansion and !variable!
%VAR% expansion happens once when cmd.exe parses a line. Inside a parenthesised block (for, if, (...)) the expansion freezes at parse time, which surprises scripts that modify variables inside the block. Enabling delayed expansion with setlocal enabledelayedexpansion lets you use !VAR! for runtime expansion.
@echo off
set N=0
for %%i in (a b c) do (
set /A N+=1
echo Step %N% of 3 — file %%i
)
Output:
Step 0 of 3 — file a
Step 0 of 3 — file b
Step 0 of 3 — file c
(broken — %N% is captured at parse time, before the loop runs)
@echo off
setlocal enabledelayedexpansion
set N=0
for %%i in (a b c) do (
set /A N+=1
echo Step !N! of 3 — file %%i
)
Output:
Step 1 of 3 — file a
Step 2 of 3 — file b
Step 3 of 3 — file c
Echoing to stderr instead of stdout
echo always writes to stdout (handle 1). To send a message to stderr, redirect with 1>&2:
echo Error: file not found 1>&2
Output:
Error: file not found
(visible on screen, but echo ... 1>&2 | more would not pipe — stderr bypasses pipes)
rem Useful pattern: errors to stderr, normal output to stdout
echo Processing file...
echo WARNING: skipping bad row 1>&2
echo Done.
Output:
Processing file...
WARNING: skipping bad row
Done.
When redirecting both, separate them with > stdout.log 2> stderr.log. To merge stderr into stdout use 2>&1 after the primary redirect.
script.bat > combined.log 2>&1
Output: (none — all output captured in combined.log)
Echoing a CRLF vs LF
cmd.exe always terminates echo output with CRLF (\r\n, 0x0D 0x0A). There is no built-in flag to emit Unix-style LF only. If you need LF line endings (e.g. for shell scripts, JSON, .env files), pipe through find/findstr with a transform, or generate the file in PowerShell with the [char]10 newline.
rem CRLF is fine for Windows tools
echo line1>script.sh
echo line2>>script.sh
Output: (none — exits 0 on success)
# LF-only output via PowerShell
[IO.File]::WriteAllText("script.sh", "line1`nline2`n")
Output: (file created with Unix LF terminators)
PowerShell equivalents — Write-Output, Write-Host, echo
PowerShell has three commands that overlap with echo. They are not interchangeable — they differ in what they write to, whether they participate in the pipeline, and whether output can be captured.
| Cmdlet | Goes to | Captured by =? | Pipeable? | Alias |
|---|---|---|---|---|
Write-Output | Success stream (stdout-equivalent) | Yes | Yes | echo, write |
Write-Host | Console host | No | No | — |
Write-Information | Information stream (PS 5+) | Yes (with -IV) | No | — |
Write-Verbose | Verbose stream | Only via $VerbosePreference | No | — |
Write-Warning | Warning stream | Yes (with -WV) | No | — |
Write-Error | Error stream (stderr-equivalent) | Yes (in $Error) | No | — |
# Write-Output — pipeable, captured, goes through pipeline
Write-Output "Hello"
echo "Hello" # alias of Write-Output
"Hello" # implicit Write-Output
Output:
Hello
Hello
Hello
# Write-Host — bypasses pipeline, writes to console only
Write-Host "Hello" -ForegroundColor Green
Output:
Hello (green)
# Difference in capture
$captured = Write-Output "captured"
Write-Host "captured value: $captured"
$captured2 = Write-Host "not-captured" # $captured2 is $null
Write-Host "captured2 value: '$captured2'"
Output:
captured value: captured
not-captured
captured2 value: ''
echo in PowerShell is exactly Write-Output — no special behaviour. Anyone migrating batch scripts can substitute echo 1:1 but should be aware that the alias does not control echoing the way @echo off does in cmd.
Write-Host colours and formatting
Write-Host is the right choice for batch-script-style informational output that should never leak into pipelines. It supports per-call colours and -NoNewline.
Write-Host "[" -NoNewline
Write-Host "OK" -ForegroundColor Green -NoNewline
Write-Host "] Service started"
Output:
[OK] Service started
Write-Host "Warning" -ForegroundColor Yellow -BackgroundColor Black
Write-Host "Error" -ForegroundColor Red
Write-Host "Info" -ForegroundColor Cyan
Output: (each line in the specified colour)
Write-Output and the pipeline
Write-Output emits an object into the success stream. Strings, numbers, hashtables, custom objects — all go through Write-Output. The implicit return from a function is also a Write-Output. This is why scripts that mix Write-Host and Write-Output produce surprising results when redirected: only the Write-Output parts are captured.
function Get-Status {
Write-Host "Running checks..." # to console only
Write-Output "OK" # to pipeline
}
$result = Get-Status
Write-Host "Captured: $result"
Output:
Running checks...
Captured: OK
If you replace Write-Output with Write-Host, $result becomes $null.
Cmd echo vs > quirks
A trailing space before > is included in the file. Quoting helps but is not always sufficient.
echo hello >file1.txt
Output (file1.txt contents):
hello
(note the trailing space)
echo hello>file2.txt
Output (file2.txt contents):
hello
(no trailing space — > is adjacent to o)
For zero-byte file creation:
rem Wrong — creates a file with "\r\n"
echo > empty1.txt
rem Right — creates a true empty file
type nul > empty2.txt
copy nul empty3.txt
Output:
0 file(s) copied.
Numeric prefixes for redirection
cmd.exe redirection has explicit handle numbers when needed:
| Token | Means |
|---|---|
> or 1> | Redirect stdout |
2> | Redirect stderr |
< or 0< | Read stdin from |
2>&1 | Merge stderr into stdout |
1>&2 | Merge stdout into stderr |
>> | Append (stdout) |
2>> | Append (stderr) |
>nul | Discard stdout |
2>nul | Discard stderr |
>nul 2>&1 | Discard everything |
rem Capture all output, errors and all, into one file
build.bat > build.log 2>&1
Output: (none — all output redirected to build.log)
rem Discard noisy output, keep only errors
build.bat > nul
Output:
error C2065: 'undefined_var': undeclared identifier
rem Discard errors, keep stdout
build.bat 2> nul
Output: (varies)
echo in scheduled tasks and services
echo in a script that runs under Task Scheduler with "Run whether user is logged on or not" has no visible console — anything echo'd disappears unless redirected to a file. Always redirect outputs explicitly in scheduled scripts:
@echo off
echo [%DATE% %TIME%] Task started >> C:\Logs\task.log
... task work ...
echo [%DATE% %TIME%] Task finished >> C:\Logs\task.log
Output: (none — all output redirected to task.log)
In PowerShell scheduled tasks, prefer Start-Transcript to capture all console output:
Start-Transcript -Path "C:\Logs\task-$(Get-Date -Format yyyyMMdd).log" -Append
Write-Host "Task started"
# ...
Stop-Transcript
Output: (everything written to the transcript file)
Locale and code page issues
echo emits bytes in the current console code page (usually 437 or 850 on US Windows, 1252 in many western locales, UTF-8 on Windows 10/11 with chcp 65001). Non-ASCII text written with echo may be mojibake'd when read by another tool that assumes a different encoding.
chcp 65001
echo Café — naïve
> note.txt
Output:
Active code page: 65001
Café — naïve
For UTF-8 output, set the console to code page 65001 and ensure the .bat file itself is saved as UTF-8 without BOM (a BOM at the start of the file confuses cmd.exe).
# PowerShell — UTF-8 by default in 7+, explicit in 5.1
"Café — naïve" | Out-File -Encoding utf8 note.txt
Output: (file written in UTF-8)
Common pitfalls (continued)
echo ontoggles state, doesn't print "on" —echo.on(no space) is the form that prints the literal word.- Caret escaping is consumed once — to emit a literal caret, double it:
echo a ^^ bprintsa ^ b. - Trailing CRLF cannot be removed —
echoalways terminates with CRLF; for no newline use<NUL set /P =text. - Variable expansion happens once per line — inside
(...)blocks, switch to delayed expansion withsetlocal enabledelayedexpansionand!VAR!. - PowerShell
echoisWrite-Output, notWrite-Host— output goes through the pipeline, can be captured, and is silent in>nul-style redirects only viaOut-Null. %PATH%and other long values may exceed line length —echo %PATH%can fail or truncate if%PATH%plus the trailing CRLF exceeds 8191 characters; split withset PATH | findstr Path.
Real-world recipes (continued)
Self-documenting batch wrapper
A wrapper script that prints what it is about to do, runs it, and reports success — using echo for narration.
@echo off
echo ----------------------------------------
echo Step 1: Sync repository
echo ----------------------------------------
git pull
if errorlevel 1 (
echo [FAIL] git pull failed.
exit /b 1
)
echo [OK] Repository synced.
echo.
echo ----------------------------------------
echo Step 2: Build
echo ----------------------------------------
npm run build
if errorlevel 1 (
echo [FAIL] build failed.
exit /b 1
)
echo [OK] Build complete.
Output:
----------------------------------------
Step 1: Sync repository
----------------------------------------
[OK] Repository synced.
----------------------------------------
Step 2: Build
----------------------------------------
[OK] Build complete.
Print a tree-friendly status update
Combine echo with tasklist/findstr to print a snapshot of relevant processes.
@echo off
echo Running processes containing "node":
tasklist /FI "IMAGENAME eq node.exe" /NH /FO TABLE
echo.
echo Running processes containing "python":
tasklist /FI "IMAGENAME eq python.exe" /NH /FO TABLE
Output:
Running processes containing "node":
node.exe 1234 Console 1 56,432 K
Running processes containing "python":
python.exe 5678 Console 1 32,108 K
Mixed cmd and PowerShell logging
When a batch script invokes PowerShell for one task, route both outputs to the same log via >>:
@echo off
set LOG=C:\Logs\deploy_%DATE:~-4,4%%DATE:~-10,2%%DATE:~-7,2%.log
echo [%TIME%] Starting deployment >> %LOG%
powershell -NoProfile -Command "Get-Service | Where-Object Status -eq Running | Measure-Object | ForEach-Object Count" >> %LOG% 2>&1
echo [%TIME%] Done >> %LOG%
type %LOG%
Output (deploy_20260525.log):
[14:22:05.34] Starting deployment
38
[14:22:08.11] Done
PowerShell echo-equivalent with colours and timestamps
A reusable function for coloured, timestamped status output.
function Write-Stamp {
param(
[string]$Message,
[ValidateSet('Info','Warn','Error','OK')] [string]$Level = 'Info'
)
$ts = Get-Date -Format 'HH:mm:ss'
$colour = switch ($Level) {
'Info' { 'Cyan' }
'Warn' { 'Yellow' }
'Error' { 'Red' }
'OK' { 'Green' }
}
Write-Host "[$ts] [$Level] $Message" -ForegroundColor $colour
}
Write-Stamp "Sync started"
Write-Stamp "Cache miss" -Level Warn
Write-Stamp "Done" -Level OK
Output:
[14:22:05] [Info] Sync started
[14:22:06] [Warn] Cache miss
[14:22:09] [OK] Done
Generating a CSV header + rows
echo with > for the header line and >> for rows is the simplest way to assemble a small CSV without a tool.
@echo off
echo Date,Hostname,Status > status.csv
echo %DATE%,%COMPUTERNAME%,Online >> status.csv
echo %DATE%,SRV01,Online >> status.csv
echo %DATE%,SRV02,Offline >> status.csv
type status.csv
Output:
Date,Hostname,Status
Mon 05/25/2026,MYHOST,Online
Mon 05/25/2026,SRV01,Online
Mon 05/25/2026,SRV02,Offline
Sources
References consulted while writing this article. Links open in a new tab.
- Microsoft Learn — echo command reference — Authoritative flag list and parameter semantics used to build the Essential options table.
- SS64 — echo — Cross-version comparison and historical syntax notes.
See also
- set — Environment Variables —
set /P, variable expansion, and the<NUL set /Ptrick for no-newline output. - PowerShell Essentials —
Write-Output,Write-Host, streams, and transcript logging. - cls — Clear Console Screen — pairs with
echofor batch menu output. - timeout — Pause Script Execution — the standard "echo X then wait" pattern.
- findstr — Search Text in Files — common downstream of
echo'd output.