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).

cmd
echo /?

Output:

vbnet
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.

cmd
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.

cmd
echo Hello, world!

Output:

code
Hello, world!
cmd
echo Current user: %USERNAME%

Output:

sql
Current user: alicedev
cmd
echo Today is %DATE% and the time is %TIME%

Output:

swift
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.

cmd
echo Line one
echo.
echo Line three

Output:

css
Line one

Line three
cmd
rem Checking the current echo state
echo

Output:

csharp
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.

cmd
@echo off
echo This line is printed.
echo And this one too.

Output:

kotlin
This line is printed.
And this one too.

With echo on, each command appears before its output:

cmd
echo on
echo Hello

Output:

bash
C:\>echo Hello
Hello

Redirecting echo to a file

Redirect echo output with > (overwrite) or >> (append) to build text files without an editor.

cmd
echo [INFO] Build started > build.log
echo [INFO] Compiling src\main.c >> build.log
type build.log

Output:

css
[INFO] Build started
[INFO] Compiling src\main.c
cmd
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.

cmd
(
    echo [section]
    echo key=value
    echo other=42
) > config.ini
type config.ini

Output:

ini
[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).

cmd
echo Redirect: ^> and pipe: ^|

Output:

yaml
Redirect: > and pipe: |
cmd
rem Ampersand without escape would run two commands
echo Q^&A section

Output:

css
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.

cmd
<NUL set /P DUMMY=Enter value: 

Output:

yaml
Enter value: 

(cursor stays on same line — useful before reading user input)

Common pitfalls

  1. echo alone shows ON/OFF state — use echo. (no space) for a blank line, not plain echo.
  2. 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.
  3. Special characters need ^ escapingecho a > b redirects instead of printing a > b; write echo a ^> b.
  4. @echo off must be the first line — placing it anywhere else still echoes all preceding commands.
  5. Quotes are printed literallyecho "hello" outputs "hello" with quotes included; there is no auto-stripping.

Real-world recipes

cmd
@echo off
echo ============================================================
echo  Build Pipeline — %DATE%
echo ============================================================
echo.
echo Step 1: Cleaning output directories...

Output:

markdown
============================================================
 Build Pipeline — Mon 04/28/2026
============================================================

Step 1: Cleaning output directories...

Create a minimal .env file from batch

cmd
@echo off
(
    echo APP_ENV=production
    echo LOG_LEVEL=warn
    echo PORT=8080
) > .env
echo .env written.

Output:

bash
.env written.

Conditional status message

cmd
@echo off
if exist C:\Logs\app.log (
    echo [OK] Log file found.
) else (
    echo [WARN] Log file missing — check the service.
)

Output:

csharp
[WARN] Log file missing — check the service.

Append a timestamped entry to a log

cmd
echo [%DATE% %TIME%] Deployment complete >> C:\Logs\deploy.log
type C:\Logs\deploy.log

Output:

csharp
[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.

cmd
rem This toggles command echoing, does NOT print "on"
echo on

Output:

vbnet
(command echoing turned on; no text printed)
cmd
rem This prints the literal word "on"
echo.on

Output:

csharp
on
cmd
echo "on"

Output:

arduino
"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).

cmd
where echo

Output:

arduino
INFO: Could not find files for the given pattern(s).
cmd
rem To use echo from outside cmd, wrap with cmd /c
cmd /c "echo Hello from cmd"

Output:

csharp
Hello from cmd

All forms of echo

FormEffect
echoShow 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 onRe-enable command echoing
echo offDisable command echoing
@echo <text>Print <text> without echoing the echo command itself
@echo offDisable echoing globally and suppress this command's own echo
echo <text> > fileRedirect to a file (overwrite)
echo <text> >> fileAppend 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.

cmd
echo off
echo Hello

Output:

vbnet
C:\>echo off
Hello

(echo off was itself echoed before it took effect)

cmd
@echo off
echo Hello

Output:

code
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:

cmd
echo ON
echo Step 1
@rem this comment is hidden
echo Step 2

Output:

vbnet
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.

cmd
echo Pipe: ^| and amp: ^&

Output:

yaml
Pipe: | and amp: &
cmd
echo Parens: ^(text^) and equals signs: 1+1^=2

Output:

vbnet
Parens: (text) and equals signs: 1+1=2
cmd
rem Percent sign — double it to escape
echo Loading... 50%%

Output:

code
Loading... 50%
cmd
rem Inside delayed-expansion blocks, escape ! as well
setlocal enabledelayedexpansion
echo Hello^^!

Output:

code
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.

cmd
echo     four leading spaces

Output:

markdown
    four leading spaces

(works because the visible spaces survive; only the one space after echo is trimmed)

cmd
echo:    four leading spaces

Output:

markdown
    four leading spaces
cmd
(
    echo     indented line
) > out.txt
type out.txt

Output:

arduino
    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:

FormResultNotes
echo.Blank lineCanonical — works everywhere
echo:Blank lineSame as echo.
echo;Blank lineWorks in cmd.exe
echo/Blank lineWorks but rare
echo\Blank lineWorks
echo,Blank lineWorks
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.

cmd
echo Line 1
echo.
echo Line 3
echo:
echo Line 5

Output:

css
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.

cmd
@echo off
set N=0
for %%i in (a b c) do (
    set /A N+=1
    echo Step %N% of 3 — file %%i
)

Output:

csharp
Step 0 of 3file a
Step 0 of 3file b
Step 0 of 3file c

(broken — %N% is captured at parse time, before the loop runs)

cmd
@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:

csharp
Step 1 of 3file a
Step 2 of 3file b
Step 3 of 3file c

Echoing to stderr instead of stdout

echo always writes to stdout (handle 1). To send a message to stderr, redirect with 1>&2:

cmd
echo Error: file not found 1>&2

Output:

csharp
Error: file not found

(visible on screen, but echo ... 1>&2 | more would not pipe — stderr bypasses pipes)

cmd
rem Useful pattern: errors to stderr, normal output to stdout
echo Processing file...
echo WARNING: skipping bad row 1>&2
echo Done.

Output:

csharp
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.

cmd
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.

cmd
rem CRLF is fine for Windows tools
echo line1>script.sh
echo line2>>script.sh

Output: (none — exits 0 on success)

powershell
# 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.

CmdletGoes toCaptured by =?Pipeable?Alias
Write-OutputSuccess stream (stdout-equivalent)YesYesecho, write
Write-HostConsole hostNoNo
Write-InformationInformation stream (PS 5+)Yes (with -IV)No
Write-VerboseVerbose streamOnly via $VerbosePreferenceNo
Write-WarningWarning streamYes (with -WV)No
Write-ErrorError stream (stderr-equivalent)Yes (in $Error)No
powershell
# Write-Output — pipeable, captured, goes through pipeline
Write-Output "Hello"
echo "Hello"           # alias of Write-Output
"Hello"                # implicit Write-Output

Output:

code
Hello
Hello
Hello
powershell
# Write-Host — bypasses pipeline, writes to console only
Write-Host "Hello" -ForegroundColor Green

Output:

scss
Hello   (green)
powershell
# 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:

yaml
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.

powershell
Write-Host "[" -NoNewline
Write-Host "OK" -ForegroundColor Green -NoNewline
Write-Host "] Service started"

Output:

csharp
[OK] Service started
powershell
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.

powershell
function Get-Status {
    Write-Host "Running checks..."        # to console only
    Write-Output "OK"                     # to pipeline
}

$result = Get-Status
Write-Host "Captured: $result"

Output:

makefile
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.

cmd
echo hello >file1.txt

Output (file1.txt contents):

code
hello 

(note the trailing space)

cmd
echo hello>file2.txt

Output (file2.txt contents):

code
hello

(no trailing space — > is adjacent to o)

For zero-byte file creation:

cmd
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:

scss
        0 file(s) copied.

Numeric prefixes for redirection

cmd.exe redirection has explicit handle numbers when needed:

TokenMeans
> or 1>Redirect stdout
2>Redirect stderr
< or 0<Read stdin from
2>&1Merge stderr into stdout
1>&2Merge stdout into stderr
>>Append (stdout)
2>>Append (stderr)
>nulDiscard stdout
2>nulDiscard stderr
>nul 2>&1Discard everything
cmd
rem Capture all output, errors and all, into one file
build.bat > build.log 2>&1

Output: (none — all output redirected to build.log)

cmd
rem Discard noisy output, keep only errors
build.bat > nul

Output:

text
error C2065: 'undefined_var': undeclared identifier
cmd
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:

cmd
@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:

powershell
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.

cmd
chcp 65001
echo Café — naïve
> note.txt

Output:

css
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
# 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)

  1. echo on toggles state, doesn't print "on"echo.on (no space) is the form that prints the literal word.
  2. Caret escaping is consumed once — to emit a literal caret, double it: echo a ^^ b prints a ^ b.
  3. Trailing CRLF cannot be removedecho always terminates with CRLF; for no newline use <NUL set /P =text.
  4. Variable expansion happens once per line — inside (...) blocks, switch to delayed expansion with setlocal enabledelayedexpansion and !VAR!.
  5. PowerShell echo is Write-Output, not Write-Host — output goes through the pipeline, can be captured, and is silent in >nul-style redirects only via Out-Null.
  6. %PATH% and other long values may exceed line lengthecho %PATH% can fail or truncate if %PATH% plus the trailing CRLF exceeds 8191 characters; split with set 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.

cmd
@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:

markdown
----------------------------------------
 Step 1: Sync repository
----------------------------------------
[OK] Repository synced.

----------------------------------------
 Step 2: Build
----------------------------------------
[OK] Build complete.

Combine echo with tasklist/findstr to print a snapshot of relevant processes.

cmd
@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:

arduino
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 >>:

cmd
@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):

csharp
[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.

powershell
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:

css
[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.

cmd
@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:

swift
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