I want to write some status info that replaces the last line when I do something.

In following example bat file, I want the text Step 2 to overwrite Step 1 on the same line in the command output.

echo off

echo Step 1
REM Do some stuff...

echo Step 2
REM do some other stuff...

8 Answers 8


This script will do exactly what you asked:

@echo off

setlocal enableextensions enabledelayedexpansion
for /f %%a in ('copy /Z "%~dpf0" nul') do set "ASCII_13=%%a"

set /p "=Step 1" <NUL
REM Do some stuff...

set /p "=!ASCII_13!Step 2" <NUL
REM do some other stuff...

I found this answer by looking at the code in a batch script character manipulation library written by Dave Benham called CharLib.

This library is able to create all characters in the range 1..255.

For more info see the thread entitled "Is it possible to convert a character to its ASCII value?"

Edit 2017-4-26: It appears that the code above no longer works. I'm unsure when this behaviour would have changed, but it definitely used to work. The CR character after the = now gets stripped by the set command. Ah, it appears to be a change in the way set works between XP and later versions of Windows. See the excellent answer in Overwrite line in Windows batch file? (duplicate) for more details and extra code examples.

An alternative then is to put a non-whitespace character before the !ASCII_13! and arrange for it to be erased too, perhaps either by also inserting a backspace (which doesn't get stripped) followed by a space or by appending spaces to the end of the prompt string (which aren't stripped.)

E.g. like this:

@echo off

setlocal enableextensions enabledelayedexpansion
for /f %%a in ('copy /Z "%~dpf0" nul') do set "ASCII_13=%%a"

set /p "=Step 1" <NUL
REM Do some stuff...

set /p "=x!ASCII_13!Step 2 " <NUL
REM do some other stuff...
  • 2
    For me, this just prints "Step 1Step 2" instead of having just "Step 2" on the screen.
    – galmok
    Jun 7, 2016 at 11:34
  • @galmok This has begun to happen to me too, but I'm sure it never used to (as I have a script that used to work perfectly put recently has changed to the behaviour you describe.) It appears that the set command now strips any CR and LF characters (as well as spaces) after the =. Try putting a non-whitespace character between the = and the !ACSII_13!
    – timfoden
    Apr 26, 2017 at 6:28
  • There is a way to do all of this... I actually came to look to see if anyone else has noticed, because I Wanted to see if they had discovered how far it went. ... Can I write up an ANSWER on stack overflow? OR.. like a Question and immediately answer it? I suppose I could write it in question form about what you could do with it.. I've got to go eat something but I'll update this with a link for you. Feb 19, 2019 at 20:06
  • Write !ASCII_13! after Step 1 to prevent stripping, so in step 2 you would code: set /p "=Step 2 " <NUL
    – Zimba
    Dec 21, 2019 at 14:09

To answer the question:

@echo off
SET backspace=%result%
<nul set /p =Step 1
<nul set /p =%backspace%
<nul set /p =2
<nul set /p =%backspace%%backspace%%backspace%%backspace%%backspace%%backspace%
<nul set /p =End   

:EVALUATE           -- evaluate with VBS and return to result variable
::                  -- %~1: VBS string to evaluate
:: extra info: http://groups.google.com/group/alt.msdos.batch.nt/browse_thread/thread/9092aad97cd0f917

@IF [%1]==[] ECHO Input argument missing & GOTO :EOF 

@ECHO wsh.echo "result="^&eval("%~1") > %temp%\evaluate_tmp_67354.vbs 
@FOR /f "delims=" %%a IN ('cscript //nologo %temp%\evaluate_tmp_67354.vbs') do @SET "%%a" 
@DEL %temp%\evaluate_tmp_67354.vbs
::ECHO %result%



Basically, what the script does, is writes Step 1, then goes back one place, overwrites 1, and at the end goes completely back and overwrites Step 2 with End.

This going back I do with the special ASCII character 8 which is backspace. Because I don’t know how to write it in cmd, I use the :EVALUATE function which runs the VBS Chr() function, and puts the backspace-character into the variable result. If someone knows a better way, please advise.

  • 1
    In PowerShell, you can also store $host.ui.RawUI.CursorPosition into a variable and restore it later to have the cursor jump back at where you were and overwrite your output. Only an option for PowerShell, but may be a bit cleaner than your VBS stuff :-)
    – mihi
    Sep 25, 2012 at 19:08
@echo off
for /F "tokens=1 delims=# " %%a in ('"prompt #$H# & echo on & for %%b in (1) do rem"') do set "BSPACE=%%a"
<nul set /p =Step 1
ping >nul
<nul set /p =%BSPACE%
<nul set /p =2
ping >nul
<nul set /p =%BSPACE%
<nul set /p =3
ping >nul
<nul set /p =End.  


for /F "tokens=1 delims=# " %%a in ('"prompt #$H# & echo on & for %%b in (1) do rem"') do set "BSPACE=%%a"

this will set the backspace character into BSPACE variable. Now to see the result, type:

echo ab%BSPACE%c

output: ac

you can use this BSPACE variable more than once to delete multiple character.

Now, if you want to set a carriage return in a variable, use

for /F "usebackq" %%a in (copy /Z "%~dpf0" nul) DO (set "cr=%%a")

to see result, type: setlocal EnableDelayedExpansion & echo asfasdhlfashflkashflksa!CR!***

the output will be: ***asfasdhlfashflkashflksa

@davour's answer above is also beautiful but @timfoden's answer did not work for me.

  • @timfoden's answer didn't work because !CR! should go at the end of previous step. Also, instead of all those %BSPACE%s, you could also !CR! and fill with whitespaces: set /p "=.!CR!End. " <NUL&echo:
    – Zimba
    Dec 21, 2019 at 14:47

You can't. Usually you can achieve this kind of thing by including a carriage-return character (0x0D) in the file which will put the cursor back to the first column in the same line. But in this case it doesn't work; the CR is just silently eaten.

Furthermore getting the CR in there is kinda tricky and at least here involved a text editor. I'd suggest you write a little utility program that will do this for you, it actually isn't very hard. The following little C program might suffice (if you don't need Unicode):

#include <stdio.h>

int main(int argc, char* argv[]) {
  if (argc < 2) return 1;
  printf("\r%s", argv[1]);

It does nothing more than printing a CR character and then the text you specify as its first argument. Usage as follows:

@echo off
<nul set /P X=Step 1
pause>nul 2>nul
over.exe "Step 2"

The last line is the call to that program. The second line is the normal batch idiom for printing text without a trailing line break (which is important in this case because otherwise you couldn't overwrite the line). You could just as well use the program above as well in that place, though.

A lightly hacky way, but the only one where you can be sure where you end up, would be to use cls prior to your step output. Will flicker but that way you always write to the upper-left. And clobber everything that was visible in the console (which is why I wouldn't recommend it); most users don't like that too much.

  • OK, I had a suspicion it was not possible to do with clean command line. If I want to make a utility do do this, I might as well put the entire thing in a utility. What I really wanted was just a visible countdown that wait 5 seconds, counting down each second till finished.
    – awe
    Dec 14, 2009 at 7:59
  • 4
    awe: If you are on Vista or later, then timeout 5 will work.
    – Joey
    Dec 14, 2009 at 8:02
  • Great! thank you! timeout 5 works!
    – awe
    Dec 14, 2009 at 8:30
  • 4
    I should have posted my real issue the first time...
    – awe
    Dec 14, 2009 at 8:31
  • Of course you can; just add carriage return after step 1 ! Furthermore, here's some code to get carriage return without involving text editor: for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
    – Zimba
    Dec 21, 2019 at 14:20

Get newline character, write next step, go back to start of line, write next line:

@echo off
setlocal enabledelayedexpansion
echo Here's how it's done:

for /f %%a in ('copy /Z "%~f0" nul') do set "Newline=%%a"
set /p "=Step 1!Newline!" <NUL
REM Do some stuff...
ping -n 2 localhost > nul
set /p "=Step 2!Newline!" <NUL
ping -n 2 localhost > nul
set /p "=Step 3 " <NUL
REM do some other stuff...
ping -n 2 localhost > nul
echo Done.
  • This will only work if the Step 1 Step 2 Step 3 are all the same length. If not the text of the first set /p remains. Not sure how to fix this.
    – Ste
    Apr 1, 2020 at 13:42
  • That problem can be fixed by using additional whitespace characters in the next prompt. to cover any previous characters. Example set /p "=Step 2SPACESPACESPACESPACE!Newline!" <NUL as opposed to set /p "=Step 2!Newline!" <NUL. Exchange SPACE for the character.
    – Ste
    Apr 2, 2020 at 20:06
  • If previous lines are longer, then add spaces to blank out extra characters, or backspace from end of line to beginning, or just backspace to the extra characters.
    – Zimba
    Apr 4, 2020 at 17:29

You'd need a screen-cursor library like curses or ncurses, but I'm not overly familiar with their use. Both libraries were developed for Unix systems, but you can find them for Windows (in the Cygwin environment, or GnuWin32).

I don't know of any easy way to access them in a DOS-style batch file. You might be better off programming in a compiled language. Take a look at the Ncurses HOWTO to get a better idea of whether it will be useful.

  • curses are pretty much for terminals and terminal emulators only. They don't really map to Windows's native console API.
    – Joey
    Dec 14, 2009 at 9:17
  • NO need for external libraries; works fine with CMD tools.
    – Zimba
    Dec 21, 2019 at 14:29

Solution 1

With Notepad++ it's possible to insert the Backspace ← character (ASCII 0x08) directly, using its ASCII Codes Insertion Panel (Edit > Character Panel).

My solution is to insert the [BS] character directly and then, like other solutions posted here, use it multiple times to delete previous characters:




REM Do some stuff...

REM Do some other stuff...


Notepad++ screenshot

Windows batch overwrite


CMD output

Solution 2

Another possibility is to use cecho (echo command with colors support) to insert a Carriage Return ↵ as a unicode character (U+000D):




cecho Step 1
REM Do some stuff...

REM Do some other stuff...


NB: cecho_x64.exe runs significantly faster on my machine than cecho.exe.


Windows 10 users have the joy of Virtual terminal escape sequences, However they take a bit of learning, and unlike most windows commands, there's no usage information for them available from the command line. Deleting lines, or even a number of characters from the current cursor position, is quite simple, However, when using multiple VT codes in combination, say to: Hide the cursor, shift it's position, change FG/BG color and remove a numer of cells from the current line, the sequences used can rapidly become a nearly unreadable command line that's difficult to maintain.

Virtual Terminal sequences do however provide a means to fine control of display properties and prettyfied output, and are well worth using.

To simplify the use of Virtual Terminal sequences and make code maintenance easier, I created the below Batch Macro for handling multiple aspects of Cursor output in the windows 10 command prompt, complete with help file for usage to assist new batch scripters in learning what can be done with Virtual Terminal Sequences.

Supports and Shows examples of:

  • Cursor Positioning
    • Absolute
    • Relative to last Cursor Position ; left right by n columns ; up down by n lines
    • Combinations of Relative and Absolute Position.
  • Show / Hide Cursor
  • Cursor Graphics properties [ Color ; Foreground and Background ]
    • Same line multicolor output
    • Easily chain multiple VT graphics sequences.
  • Clearing of all text on a line from a given position.
  • Deletion of a number of characters to the right of the cursor on the current line.
  • Optionally Save the Cursor position at the time of expansion as independent Y and X values.
  • Switching between Screen buffers.


I've included below the final usage example a command line that uses VT codes to achieve the same result as that example, to illustrate the difference in readability when using multiple Terminal sequences in the same Cursor output.

NOTES On changing Buffers:

Cursor position is tied to the active buffer; It is not availale when switching to an alternate buffer.
When reverting to the main buffer:
The cursor position originally occupied in the main buffer is restored, and the content of the alternate buffer is discarded.

This macro completes the it's execution in 1/100th of a second - That's with the maximum number of args & switches (on the slowest of my pc's, with a clock speed of 1.6Ghz). In the examples, a delay is effected between each expansion to allow time to observe the changes in output.

::: Cout cursor Macro. Author: T3RRY ::: Filename: Cout.bat
::: OS requirement: Windows 10
::: Purpose: Facilitate advanced console display output with the easy use of Virtual terminal codes
::: Uses a macro function to effect display without users needing to memorise or learn specific
::: virtual terminal sequences.
::: Enables output of text in 255 bit color at absolute or relative Y;X positions.
::: Allows cursor to be hidden or shown during and after text output. See help for more info.

@Echo off & Setlocal EnableExtensions
============================================== :# Usage
 If not "%~1" == "" Echo/%~1.|findstr /LIC:"/?" > nul && (
  Cls &  Mode 1000,50 & Color 30
  If "%~2" == "DE" ( Color 04 & Echo/                      --- Delayed expansion detected^^^! Must not be enabled prior to calling %~n0 ---&( Echo/n|choice /n /C:o 2> nul ))
  If not Exist "%TEMP%\%~n0helpfile.~tmp" (For /F "Delims=" %%G in ('Type "%~f0"^| Findstr.exe /BLIC:":::" 2^> nul ')Do (
   For /F "Tokens=2* Delims=[]" %%v in ("%%G")Do Echo(^|%%v^|
  Type "%TEMP%\%~n0helpfile.~tmp" | More
  timeout /T 60 > nul
  Color 07
  If "%~2" == "DE" (Exit)Else Exit /B 0
 If "!![" == "[" Call "%~f0" "/?" "DE"
:::[ cout /?                                                                                                             ]
:::[ %COUT% Cursor output macro.                                                                                         ]
:::[ * Valid Args for COUT: {/Y:Argvalue} {/X:Argvalue} {/S:Argvalue} {/C:Argvalue}                                      ]
:::[       - Args Must be encased in curly braces. Arg order does not matter ; Each Arg is optional.                     ]
:::[ * Valid Switches for COUT: /Save /Alt /Main                                                                         ]
:::[ /Save - Stores the Y and X position at the start of the current expansion to .lY and .lX variables                  ]
:::[ /Alt  - Switch console to alternate screen Buffer. Persists until /Main switch is used.                             ]
:::[ /Main - Restore console to main screen Buffer. Console default is the main buffer.                                  ]
:::[                                                                                                                     ]
:::[   USAGE:                                                                                                            ]
:::[ * ArgValue Options ; '#' is an integer:                                                                             ]
:::[   {/Y:up|down|#} {/Y:up#|down#|#} {/Y:#up|#down|#} {/X:left|right|#} {/X:left#|right#|#} {/X:#left|#right|#}        ]
:::[  * note: {/Y:option} {/X:option} - 1 option only per Arg.                                                           ]
:::[        - directions: 'up' 'down' 'left' 'right' are relative to the cursors last position.                          ]
:::[         - /Y and /X options - #direction or direction#:                                                             ]
:::[           Positions the cursor a number of cells from the current position in the given direction.                  ]
:::[           Example; To move the cursor 5 rows up in the same column, without displaying any new text:                ]
:::[            %COUT%{/Y:5up}                                                                                           ]
:::[        - '#' (Absolute position) is the column number {/X:#} or row number {/Y:#} the cursor                        ]
:::[         * Integers for absolute positions contained in variables must be Expanded: {/Y:%varname%}                   ]
:::[           is to be positioned at, allowing cursor position to be set on single or multiple axis.                    ]
:::[         * Absolute Y and X positions capped at line and column maximum of the console display.                      ]
:::[         * Exceeding the maximum Y positions the cursor at the start of the last line in the console display.        ]
:::[         * Exceeding the maximum X positions the cursor at the start of the next line                                ]
:::[                                                                                                                     ]
:::[   {/S:Output String} {/S:(-)Output String} {/S:Output String(+)} {/S:Output String(K)} {/S:Output String(.#.)}      ]
:::[  * note: (-) Hide or (+) Show the Cursor during output of the string.                                               ]
:::[          (K) Clears the row of text from the position (K) occurs.                                                   ]
:::[          Example; Delete 5 characters from the current row to the right of the curser:                              ]
:::[           %COUT%{/S:(.5.)}                                                                                          ]
:::[   {/C:VTcode} {/C:VTcode-VTcode} {/C:VTcode-VTcode-VTcode}                                                          ]
:::[  * note: Chain multiple graphics rendition codes using '-'                                                          ]
:::[  See:      https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#text-formatting      ]
:::[  See also: https://www.rapidtables.com/web/color/RGB_Color.html                                                     ]

============================================== :# PreScript variable definitions

================== :# Screen Dimensions
 Mode 100,30 & Cls

rem /* Get screen dimensions [lines] [columns]. Must be done before delayed expansion is enabled. */
 For /F "tokens=1,2 Delims=:" %%G in ('Mode')Do For %%b in (%%H)Do For %%a in (%%G)Do Set "%%a=%%b"

rem /* NON ENGLISH VERSIONS : Users will need to Adjust to their desired console size */
 If not defined columns (Set "columns=100"& Set "lines=30")

rem /* generate Vitual Terminal Escape Control Character */
 For /F %%a in ( 'Echo prompt $E ^| cmd' )Do Set "\E=%%a"
rem /* https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences */
(Set \n=^^^

%= Newline variable for macro definitions. DO NOT MODIFY this line or above 2 lines. =%)
rem /* Cursor position codes - https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#simple-cursor-positioning */
 Set "left=D"&Set "right=C"&Set "up=A"&set "down=B"
 For /L %%n in (1 1 60)Do (Set "%%ndown=[%%nB"&Set "down%%n=[%%nB"& set "%%nup=[%%nA"&Set "up%%n=[%%nA")
 For /L %%n in (1 1 240)Do (Set "%%nleft=[%%nD"&Set "left%%n=[%%nD"&set "%%nright=[%%nC"&set "right%%n=[%%nC")

 Set COUT=For %%n in (1 2)Do If %%n==2 ( %\n%
  If "!Args:}=!" == "!Args!" (CLS^&Echo/Usage Error. Args must be enclosed in curly braces ^& Exit /B 0) %\n%
  Set ".Y=" ^& Set ".X=" ^& Set ".Str=" ^& Set ".C=" %\n%
  Set "Arg1=" ^& Set "Arg2=" ^& Set "Arg3=" ^& Set "Arg4=" %\n%
  For /F "Tokens=1,2,3,4 Delims={}" %%1 in ("!Args!")Do ( %\n%
   Set "Arg1=%%~1" %\n%
   Set "Arg2=%%~2" %\n%
   Set "Arg3=%%~3" %\n%
   Set "Arg4=%%~4" %\n%
  ) %\n%
  If not "!Args:/Save=!" == "!Args!" (%\n%
   Set "Cpos=" ^&Set "Char="%\n%
   For /L %%l in (2 1 12)Do (%\n%
    If not "!Char!" == "R" (%\n%
     ^<nul set /p "=%\E%[6n" %\n%
     FOR /L %%z in (1 1 %%l) DO pause ^< CON ^> NUL%\n%
      Set "Char=;"%\n%
      for /F "tokens=1 skip=1 delims=*" %%C in ('"REPLACE /W ? . < con"') DO (Set "char=%%C")%\n%
     If "!Cpos!" == "" (Set "Cpos=!Char!")Else set "Cpos=!Cpos!!char:R=!"%\n%
   For /F "tokens=1,2 Delims=;" %%X in ("!Cpos!")Do Set ".lY=%%X" ^& Set ".LX=%%Y" %\n%
  For %%i in (1 2 3 4)Do For %%S in (Y X C S)Do If not "!Arg%%i!" == "" ( %\n%
   If not "!Arg%%i:/%%S:=!" == "!Arg%%i!" ( %\n%
    Set "Arg%%i=!Arg%%i:/%%S:=!" %\n%
    For %%v in ("!Arg%%i!")Do ( %\n%
     If "%%S" == "Y" ( %\n%
      If Not "!%%~v!" == "" ( %\n%
       Set ".Y=%\E%!%%~v!" %\n%
      )Else ( %\n%
       Set /A ".Y=!Arg%%i!" %\n%
       If !.Y! GEQ !Lines! (Set /A ".Y=Lines-1") %\n%
       Set ".Y=%\E%[!.Y!d" %\n%
     )) %\n%
     If "%%S" == "X" ( %\n%
      If Not "!%%~v!" == "" ( %\n%
       Set ".X=%\E%!%%~v!" %\n%
      )Else ( %\n%
       Set /A ".X=!Arg%%i!" %\n%
       If !.X! GEQ !Columns! (Set ".X=1"^& Set ".Y=%\E%!Down!") %\n%
       Set ".X=%\E%[!.X!G" %\n%
     )) %\n%
     If "%%S" == "C" ( %\n%
      Set ".C=%\E%[!Arg%%i!" %\n%
      Set ".C=!.C:-=m%\E%[!" %\n%
      Set ".C=!.C!m" %\n%
     ) %\n%
     If "%%S" == "S" ( %\n%
      Set ".Str=!Arg%%i!" %\n%
      Set ".Str=!.Str:(-)=%\E%[?25l!" %\n%
      Set ".Str=!.Str:(+)=%\E%[?25h!" %\n%
      Set ".Str=!.Str:(K)=%\E%[K!" %\n%
      Set ".Str=!.Str:(.=%\E%[!" %\n%
      Set ".Str=!.Str:.)=P!" %\n%
     ) %\n%
  ))) %\n%
  If not "!Args:/Main=!" == "!Args!" ( %\n%
   ^< nul Set /P "=%\E%[?1049l!.Y!!.X!!.C!!.Str!%\E%[0m" %\n%
  )Else If not "!Args:/Alt=!" == "!Args!" ( %\n%
   ^< nul Set /P "=%\E%[?1049h!.Y!!.X!!.C!!.Str!%\E%[0m" %\n%
  )Else ( ^< nul Set /P "=!.Y!!.X!!.C!!.Str!%\E%[0m" ) %\n%
 )Else Set Args=
rem /* Simple subsecond delay macro. Uses call to a non existentent label # number of times to delay script execution. */
 For /F "tokens=1,2 delims==" %%G in ('wmic cpu get maxclockspeed /format:value')Do Set /A "%%G=%%H/20" 2> nul
 If not defined Maxclockspeed Set "Maxclockspeed=200"
 Set "Hash=#"& Set "delay=(If "!Hash!" == "#" (Set /A "Delay.len=Maxclockspeed")Else Set "Delay.len=#")& For /L %%i in (1 1 !Delay.Len!)Do call :[_false-label_] 2> Nul"

============================================== :# Script Body [Demo]
rem /* Enable Delayed Expansion after macro definiton in order to expand macro. */
 Setlocal EnableDelayedExpansion & CD "%TEMP%"

rem /* Usage examples */
 %COUT%{/X:10}{/Y:5}{/C:34}{"/S:(-)hello there^^^!"}
rem /* Example use of mixed foreground / background color and other graphics rendition properties */
 %COUT%{"/C:31-1-4-48;2;0;80;130"}{/S:Bye for now.}{/Y:down}
 %COUT%{"/S:(K)^_^"}{/X:right}{/C:32}{/Y:down} /Save
rem /* Switch to Alternate screen buffer: /Alt */
 %COUT%{"/S:(-)(K)^_^"}{/X:.lX+1}{/Y:6}{/C:33}{/Y:down} /Alt
 %COUT%{"/S:They'll be back"}{/Y:down}{/X:left}{/C:31}
rem /* Return to Main screen buffer: /Main */
 %COUT%{/X:3left}{/Y:5up}{"/S:That's all folks."} /Save /Main
rem /* restore cursor position /Save .lX value with +7 offset ; Overwrite all and delete 6 following characters:(.6.) ; restore cursor: (+) */
rem /* The same as the above line using VT codes manually. */
 ::: <nul Set /P "=%\E%[10D%\E%[32mHow%\E%[6P%\E%[?25l"
 Goto :eof

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .