CMD Shell Tips and Pitfalls: Difference between revisions

From miki
Jump to navigation Jump to search
(New page: == Frequent Mistakes == <source lang="winbatch"> rem =============================================================================================================================== rem WIN...)
 
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
== External References ==
* Excellent site on http://www.ss64.com/nt/
** Also check pages on syntax http://www.ss64.com/nt/syntax.html
* On redirection http://www.robvanderwoude.com/index.html

== Frequent Mistakes ==
== Frequent Mistakes ==
<source lang="winbatch">
rem ===============================================================================================================================
rem WIN2000 BATCH FILES FREQUENT MISTAKES
rem ===============================================================================================================================


* <font color="red">'''WRONG'''</font> - Use <tt>=</tt> for string comparison.
goto :EOF& rem to avoid execution of this example file...
* <font color="green">'''CORRECT'''</font> - Use <tt>==</tt> for string comparison.
* <font color="green">'''BETTER'''</font> - <tt>EQU</tt> is less confusing and less exposed to mistake.


<source lang="bash">
rem -------------------------------------------------------------------------------------------------------------------------------
if "0" = "0" echo Hello # WRONG
if "0" == "0" echo Hello # CORRECT
if "0" EQU "0" echo Hello # BETTER
</source>


rem WRONG
if "0" = "0" (
echo Hello
)


* <font color="red">'''WRONG'''</font> - Closing bracket will close the IF statement!
rem GOOD
* <font color="green">'''CORRECT'''</font> - Use <tt>[]</tt> instead
if "0" == "0" (
* <font color="green">'''BETTER'''</font> - Escape parenthesis with <tt>^</tt> instead
echo Hello
)


<source lang="bash">
rem BETTER - 'EQU' is less confusing and exposed to mistake
if "0" EQU "0" (
if "0" EQU "0" (
rem do something
echo Hello
echo I am doing something (and something)... # WRONG
)
)

rem -------------------------------------------------------------------------------------------------------------------------------

rem WRONG - Closing bracket will close the IF statement!
if "0" EQU "0" (
if "0" EQU "0" (
rem do something
rem do something
echo I'm doing something (and something)...
echo I am doing something [and something]... # CORRECT
)
)

rem GOOD - Use [] instead
if "0" EQU "0" (
if "0" EQU "0" (
rem do something
rem do something
echo I'm doing something [and something]...
echo I am doing something ^(and something^)... # BETTER
)
)
</source>




* <font color="red">'''WRONG'''</font> - Closing bracket will close the '''IF''' statement '''even in variable expansion!'''
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - Always quote variables with <tt>"..."</tt>
* <font color="green">'''CORRECT'''</font> - Quotes " not necessary with <tt>%%</tt> variable


<source lang="bash">
rem WRONG - Closing bracket will close the IF statement EVEN IN VARIABLE EXPANSION!
set MYVAR=Closing)
set MYVAR=Closing)
if "0" EQU "0" (
if "0" EQU "0" (
rem do something
rem do something
echo My beautiful var %MYVAR%...
echo My beautiful var %MYVAR%... # WRONG
)
)


rem GOOD - Englobe bracket with quotes " "
set MYVAR=Closing)
set MYVAR=Closing)
if "0" EQU "0" (
if "0" EQU "0" (
rem do something
rem do something
echo My beautiful var "%MYVAR%"...
echo My beautiful var "%MYVAR%"... # CORRECT
)
)


rem GOOD - Quotes " not necessary with double % variable
for /F %%i in ("bracket)") do (
for /F %%i in ("bracket)") do (
echo My Closing bracket: %%i
echo My Closing bracket: %%i # CORRECT
)
)
</source>



*Some Variable Expansion:
rem -------------------------------------------------------------------------------------------------------------------------------
** !!! Use <tt>%%</tt> for FOR statement in batch file only
** !!! Use single <tt>%</tt> for command line parameters only


<source lang="bash">
rem Some Variable Expansion
set MYVAR=My Var
set MYVAR=My Var
echo %MYVAR &rem EXPAND TO MYVAR
echo %MYVAR # expands to MYVAR
echo %%MYVAR &rem EXPAND TO %MyVar
echo %%MYVAR # expands to %MyVar
echo %MYVAR% &rem EXPAND TO My Var
echo %MYVAR% # expands to My Var
echo "%MYVAR" &rem EXPAND TO "MYVAR"
echo "%MYVAR" # expands to "MYVAR"
echo "%%MYVAR" &rem EXPAND TO "%MYVAR"
echo "%%MYVAR" # expands to "%MYVAR"
echo "%MYVAR%" &rem EXPAND TO "My Var"
echo "%MYVAR%" # expands to "My Var"
</source>


rem Use %% for FOR statement in batch file only
rem Use single % for command line parameters only


* <font color="red">'''WRONG'''</font> - <tt>%VAR%</tt> are expanded when read not when executed (early expansion)!
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - Use a subroutine !
* <font color="green">'''BETTER'''</font> - Use '''Variable Delayed Expansion''' and syntax <tt>!VAR!</tt>(enabled with <tt>cmd.exe /V</tt> or <tt>setlocal enabledelayedexpansion</tt>)!


<source lang="bash">
rem WRONG: %VAR% are expanded when read not when executed (early expansion)!
setlocal ENABLEDELAYEDEXPANSION
set MYVAR=
set MYVAR=
for /F %%i in ("TEST") do (
for /F %%i in ("TEST") do (
echo TEST: %%i &rem EXPAND TO TEST: TEST
echo TEST: %%i # expands to TEST: TEST
set MYVAR=%%i
set MYVAR=%%i
echo MYVAR: "%MYVAR%" &rem EXPAND TO TEST: ""
echo MYVAR: "%MYVAR%" # WRONG - expands to TEST: ""
call :myset %%i # CORRECT - expands to TEST: "TEST" - call subroutine
echo MYVAR: "!MYVAR!" # BETTER - expands to TEST: "TEST" - delayed expansion
)
)
goto :EOF


rem GOOD: Use subroutine!
set MYVAR=
for /F %%i in ("TEST") do (
echo TEST: %%i &rem EXPAND TO TEST: TEST
call :myset %%i
)
goto :EOF
:myset
:myset
set MYVAR=%1
set MYVAR=%1
echo MYVAR: "%MYVAR%" &rem EXPAND TO TEST: "TEST"
echo MYVAR: "%MYVAR%" # CORRECT - within sub, expands to TEST: "TEST"
goto :EOF
goto :EOF
</source>


rem GOOD: Delay use of MYVAR!
set MYVAR=
for /F %%i in ("TEST") do (
echo TEST: %%i &rem EXPAND TO TEST: TEST
set MYVAR=%%i
)
echo MYVAR: "%MYVAR%" &rem EXPAND TO TEST: "TEST"


* <font color="red">'''WRONG'''</font> - Possible syntax error if <tt>MYVAR</tt> is empty in <tt>IF ... EQU</tt> expression
rem GOOD: Use Delayed Expansion (activated by CMD /V)!
* <font color="green">'''CORRECT'''</font> - Always quote variables (we'll never repeat this enough) !
set MYVAR=
for /F %%i in ("TEST") do (
echo TEST: %%i &rem EXPAND TO TEST: TEST
set MYVAR=%%i
echo MYVAR: "!MYVAR!" &rem EXPAND TO TEST: "TEST"
)


<source lang="bash">
rem -------------------------------------------------------------------------------------------------------------------------------

rem WRONG: Possible syntax error if MYVAR is empty in IF ... EQU expression
set MYVAR=
set MYVAR=
if MYVAR EQU YES echo failed
if MYVAR EQU YES echo failed # WRONG
if "MYVAR" EQU "YES" echo failed # CORRECT
</source>


rem GOOD: Always surround var with quote
set MYVAR=
if "MYVAR" EQU "YES" echo failed


* <font color="red">'''WRONG'''</font> - <tt>SET</tt> command includes '''trailing blanks'''
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - Remove all trailing blanks in <tt>SET</tt> command
* <font color="green">'''BETTER'''</font> - Configure your favorite editor to always remove trailing blanks on every line on save


<source lang="bash">
rem WRONG: SET command includes TRAILING BLANKS
# WRONG - set with trailing blanks
set MYDIR=C:\TEMP\
# CORRECT - set without trailing blanks
set MYDIR=C:\TEMP\
set MYDIR=C:\TEMP\
</source>


rem GOOD: SET command doesn't include TRAILING BLANKS
set MYDIR=C:\TEMP\


* <font color="red">'''WRONG'''</font> - '''trailing blanks''' inside path makes <tt>%~n1</tt> to fail ('''!!! very frequent when using an env_var with trailing blanks !!!''')
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - remove '''trailing blanks''' when using <tt>%~n1</tt>
''EDIT: This bug seems fixed in Windows XP now.''


<source lang="bash">
rem WRONG: trailing blanks inside path makes %~n1 to fail (!!! VERY FREQUENT WHEN USING AN ENV_VAR WITH TRAILING BLANKS !!!)
call :strip "C:\TEMP \MYFILE.ext"
call :strip "C:\TEMP \MYFILE.ext" # WRONG... will output 'FILE'
rem
rem ... will output 'FILE'


call :strip "C:\TEMP\MYFILE.ext" # CORRECT... will output 'MYFILE'
rem GOOD: remove TRAILING BLANKS when using %~n1
call :strip "C:\TEMP\MYFILE.ext"
rem ... will output 'MYFILE'


goto :EOF
goto :EOF
Line 144: Line 134:
echo %~n1
echo %~n1
goto :EOF
goto :EOF
</source>


rem -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: %* not modified by SHIFT
* <font color="red">'''WRONG'''</font> - <tt>%*</tt> not modified by <tt>SHIFT</tt>
* <font color="green">'''CORRECT'''</font> - Use a loop instead with <tt>%~1</tt>

<source lang="bash">
echo %*
echo %*
SHIFT
SHIFT
echo %* # WRONG... will output the same string
echo %*
rem ... will output the same string


rem GOOD: Use a loop instead
if defined FILELIST set FILELIST=
:getFileList
:getFileList
if "%~1" EQU "" goto :noMoreParam
if "%~1" EQU "" goto :noMoreParam
echo "%~1" # CORRECT... Loop with %~1
set FILELIST=%FILELIST% "%~1"
SHIFT
SHIFT
goto :getFileList
goto :getFileList
:noMoreParam
:noMoreParam
</source>


rem -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: Never use "%1" or %1
* <font color="red">'''WRONG'''</font> - Never use <tt>"%1"</tt> or <tt>%1</tt>
* <font color="green">'''CORRECT'''</font> - Always use <tt>"%~1"</tt> (always correctly quoted)
type "%1"


<source lang="bash">
rem GOOD: Always use "%~1"
type "%~1"
type "%1" # WRONG
type "%~1" # CORRECT
</source>


rem -------------------------------------------------------------------------------------------------------------------------------


rem WRONG: in W2K, for /r skip SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
* <font color="red">'''WRONG'''</font> - in W2K, <tt>FOR /R</tt> skips SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
* <font color="green">'''CORRECT'''</font> - Use DIR instead (here we list all files that is not a directory...)
for /r "." %%i in (*.*) do @echo %%i
* <font color="green">'''CORRECT'''</font> - Use DIR instead (here we list all directories)


<source lang="bash">
rem GOOD: Use DIR instead (here we list all files that is not a directory...)
for /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i
for /r "." %%i in (*.*) do @echo %%i # WRONG - Skips SYSTEM files/directories
for /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i # CORRECT (all files that is not a directory)
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i # CORRECT (all directories)
</source>


rem GOOD: Use DIR instead (here we list all directories)
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i


* <font color="red">'''WRONG'''</font> - <tt>cmd.exe</tt> remove surrounding quotes, except in some very specific cases (see <tt>CMD /?</tt>).
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - Always surround with quotes and always use flag <tt>cmd.exe /S</tt> to force quote removal.


<source lang="bash">
rem WRONG: CMD remove surrounding quotes, except in some very specific cases (see CMD /?).
rem
rem Hence first quote in %MYBAT% and last quote in %MYPARAM% will be removed.
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /C %MYBAT% %MYPARAM%
cmd /V:F /C %MYBAT% %MYPARAM% # WRONG - quotes will be removed from parameters


rem GOOD: Always surround with quotes and always use flag /S to force quote removal
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /S /C "%MYBAT% %MYPARAM%"
cmd /V:F /S /C "%MYBAT% %MYPARAM%" # CORRECT - First quote in %MYBAT% and last quote in %MYPARAM% will be removed.
</source>



* <font color="red">'''WRONG'''</font> - <tt>FOR /F</tt> with other delims NOK if <tt>usebackq</tt> used (it seems there is a buffer overflow in cmd.exe)
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - <tt>FOR /F</tt> with other delims is ok if <tt>usebackq</tt> not used
''EDIT: This bug seems fixed in Windows XP now.''


<source lang="bash">
rem WRONG: FOR /F with other delims NOK if usebackq used (IT SEEMS THERE IS A BUFFER OVERFLOW IN CMD.EXE)
set STRING=MaskOSLib Maker~1:dir:1
set STRING=MaskOSLib Maker~1:dir:1
for /F "usebackqtokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l
for /F "usebackq tokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l # WRONG
goto :EOF
goto :EOF

rem GOOD: FOR /F with other delims is ok if usebackq not used
set STRING="MaskOSLib Maker~1:dir:1"
set STRING="MaskOSLib Maker~1:dir:1"
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l # CORRECT
goto :EOF
goto :EOF
</source>


rem WRONG: FOR /F to enumerate items in a list
set STRING=value1 value2 value3
for /F %%i in ("%STRING%") do echo %%i
goto :EOF


rem GOOD: Use FOR to enumerate items in a list (space, comma and semi-colon are used as separators)
* <font color="red">'''WRONG'''</font> - Use <tt>FOR /F</tt> to enumerate items in a list
* <font color="green">'''CORRECT'''</font> - Use <tt>FOR</tt> to enumerate items in a list (space, comma and semi-colon are used as separators)

<source lang="bash">
set STRING=value1 value2 value3
set STRING=value1 value2 value3
for %%i in (%STRING%) do echo %%i
for /F %%i in ("%STRING%") do echo %%i # WRONG
for %%i in (%STRING%) do echo %%i # CORRECT
goto :EOF
goto :EOF
</source>




* <font color="red">'''WRONG'''</font> - returning an ERRORLEVEL to VBS with an ending goto :EOF (or whatever)
rem -------------------------------------------------------------------------------------------------------------------------------
* <font color="green">'''CORRECT'''</font> - Set ERRORLEVEL as the very last operation executed

rem WRONG: RETURNING AN ERRORLEVEL TO VBS WITH AN ENDING GOTO :EOF (or whatever) : ERRORLEVEL IS SEEN AS = 0 BY VBS SCRIPT


<source lang="bash">
:: following set ERRORLEVEL to 1, and then exit
:: following set ERRORLEVEL to 1, and then exit
VERIFY OTHER 2
VERIFY OTHER 2>/NUL
goto :EOF # WRONG - Calling script will see an ERRORLEVEL 0
goto :EOF


::-----------------------------

rem GOOD: DO VERIFY OTHER 2 AS THE VERY LAST OPERATION. TO AVOID HIDDEN ENDLOCAL THAT WOULD BE DONE AFTER, CLOSE ALL LOCALS


setlocal
setlocal
:: Do some stuffs...
:: ...
if ... goto :FINISHFAIL
:: We must fail for some condition
if %COND% equ "YES" goto :FINISHFAIL
:: Do some stuffs and finish...
:: Do some stuffs and finish...
endlocal
endlocal
Line 239: Line 234:
:: close locals and set ERRORLEVEL
:: close locals and set ERRORLEVEL
endlocal
endlocal
VERIFY OTHER 2>/NUL # CORRECT - Calling script will see an ERRORLEVEL > 0
VERIFY OTHER 2>NUL
</source>
</source>


== Hints and Tips ==
== Hints and Tips ==
<source lang="winbatch">
rem ===============================================================================================================================
rem WIN2000 BATCH FILES TIPS AND TRICKS
rem ===============================================================================================================================


=== Miscellaneous ===
goto :EOF& rem to avoid execution of this example file...
==== Bypass pause ====
To bypass all pause commands, or to answer next <tt>set /P</tt> command


<source lang="winbatch">
rem To bypass all pause commands, or to answer next set /P command
echo Y| batchpgm.bat
echo Y| batchpgm.bat
</source>


==== Enumerate list ====
rem To enumerate elements in a list

Enumerate elements in a list

<source lang="winbatch">
set SOURCEFILEEXT=.c .h
set SOURCEFILEEXT=.c .h
set TEXTFILEEXT=%SOURCEFILEEXT% .txt .ddf
set TEXTFILEEXT=%SOURCEFILEEXT% .txt .ddf
for %i in (%TEXTFILEEXT%) do echo %i
for %i in (%TEXTFILEEXT%) do echo %i
</source>


rem alternate rem
==== Alternate rem ====

<source lang="winbatch">
:: This is also a remark
:: This is also a remark
</source>


==== Inlined rem ====


<source lang="winbatch">
rem inlined rem
echo Hello world &rem ampersand must be used for inlined rem (all space on the left are part of echo command!)
echo Hello world &rem ampersand must be used for inlined rem (all space on the left are part of echo command!)
</source>


==== Persistent errorlevel ====
rem to make errorlevel persistent - env. var override ERRORLEVEL if defined
Make errorlevel persistent - env. var override ERRORLEVEL if defined

<source lang="winbatch">
if ERRORLEVEL 1 set ERRORLEVEL=%ERRORLEVEL%
if ERRORLEVEL 1 set ERRORLEVEL=%ERRORLEVEL%
echo %ERRORLEVEL%
echo %ERRORLEVEL%
</source>


rem change dir and drive
==== Change dir and drive ====

<source lang="winbatch">
cd /D "%TEMP%"
cd /D "%TEMP%"
</source>


rem parsing command result
==== Parsing command result ====

<source lang="winbatch">
for /F "usebackq tokens=*" %%i in (`isText /sv %1`) do echo ... invalid ASCII: "%%i"
for /F "usebackq tokens=*" %%i in (`isText /sv %1`) do echo ... invalid ASCII: "%%i"
</source>


rem echo empty line
==== Echo an empty line ====

<source lang="winbatch">
echo.
echo.
</source>


rem scan directory and subdir - %%~pnxi is used to remove trailing dot
==== Scan directory and subdir ====

Scan directory and subdir - <tt>%%~pnxi</tt> is used to remove trailing dot

<source lang="winbatch">
for /R "%~1\" %%i in (.) do echo "%%~pnxi"
for /R "%~1\" %%i in (.) do echo "%%~pnxi"
</source>


rem ask user input
==== Ask user input ====

Ask user input (note that these scripts required ''Delayed Expansion'' to be enabled)

<source lang="winbatch">
setlocal enabledelayedexpansion
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]?
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]?
if /I "%CONFIRM:~0,1%" EQU "Y" (
if /I "!CONFIRM:~0,1!" EQU "Y" (
echo user says YES
echo user says YES
) else (
) else (
echo user says NO
echo user says NO
)
)
</source>


Ask user input with default value
rem to set errorlevel in subroutine

<source lang="winbatch">
setlocal enabledelayedexpansion
set USER=Y
set /P USER=Do you want to DISABLE it (Y/n^)
if /I "!USER:~0,1!" EQU "y" (
echo user said YES
) else (
echo user said NO
)
</source>

==== Set errorlevel in subroutine ====

<source lang="winbatch">
Call :MySub "1"
Call :MySub "1"
goto :Continue
goto :Continue
Line 298: Line 341:


:Continue
:Continue
</source>


rem to remove surrounding quotes in argument
==== Remove surrounding quotes in argument ====

<source lang="winbatch">
echo with or without quote: %1
echo with or without quote: %1
echo always without quote: %~1
echo always without quote: %~1
echo always with quote: "%~1"
echo always with quote: "%~1"
</source>


rem Use `echo %ENV_VAR%` instead of '%ENV_VAR%' in FOR loops
==== Use <tt>`echo %ENV_VAR%`</tt> in FOR loops ====

Use <tt>`echo %ENV_VAR%`</tt> instead of <tt>'%ENV_VAR%'</tt> in FOR loops

<source lang="winbatch">
rem Assume ENV_VAR is set to a very long list of items delimited by ;
rem Assume ENV_VAR is set to a very long list of items delimited by ;
rem The following fails when ENV_VAR is too long
rem The following fails when ENV_VAR is too long
Line 310: Line 361:
rem The following works when ENV_VAR is too long
rem The following works when ENV_VAR is too long
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do echo %%i
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do echo %%i
</source>


==== Enumerate items in a list separated by a blank ====
Enumerate items in a list separated by a blank - '''!!! The list may not contain joker like <tt>*</tt> or <tt>&</tt> !!!'''


<source lang="winbatch">
rem To enumerate items in a list separated by a blank - !!! LIST MAY NOT CONTAIN JOKER LIKE * or & !!!
set TEXTFILEEXT=.txt .ddf .c .h
set TEXTFILEEXT=.txt .ddf .c .h
for %%i in (%TEXTFILEEXT%) do echo *%%i
for %%i in (%TEXTFILEEXT%) do echo *%%i
</source>


rem To enumerate items in a list separated by an another token (list can be quite long)
==== Enumerate items in a list separated by another token ====
Enumerate items in a list separated by an another token (list can be quite long)

<source lang="winbatch">
set ENV_VAR="item1";"item2";"item3"
set ENV_VAR="item1";"item2";"item3"
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do call :enumerate %%i
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do call :enumerate %%i
Line 326: Line 384:
SHIFT
SHIFT
goto :enumerate
goto :enumerate
</source>


rem ANOTHER SOLUTION To enumerate items in a list separated by an another token (list can be quite long)
'''Yet another solution''' To enumerate items in a list separated by an another token (list can be quite long)

<source lang="winbatch">
set ENV_VAR="item1";"item2";"item3"; my item
set ENV_VAR="item1";"item2";"item3"; my item
call :enumerate %ENV_VAR:;= %
call :enumerate %ENV_VAR:;= %
Line 336: Line 397:
SHIFT
SHIFT
goto :enumerate
goto :enumerate
</source>


rem To enable/disable Command Processor Extension or Delayed Extension
==== Enable Command Processor Extensions / Delayed Expansion ====

<source lang="winbatch">
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
Line 343: Line 407:
SETLOCAL DISABLEDELAYEDEXPANSION
SETLOCAL DISABLEDELAYEDEXPANSION
rem ... setlocal set errorlevel to 0 if successful - can be used to detect if extensions are supported
rem ... setlocal set errorlevel to 0 if successful - can be used to detect if extensions are supported
</source>


rem To know whether a name refers to an existing file or to an existing dir
==== Know whether a name refers to an existing file or to an existing dir ====

<source lang="winbatch">
if exist "%~1" goto :isFile
if exist "%~1" goto :isFile
pushd "%~1"
pushd "%~1"
Line 355: Line 422:
:doesntexist
:doesntexist
rem %1 does not exist
rem %1 does not exist
</source>


==== String length subroutine ====
rem A sub-routine that returns the length of a string
An optimized sub-routine that returns the length of a string (requires ''Delayed Expansion'' to be enabled)

<source lang="winbatch">
::------ GetLength ----------------------------------------------------------------------------------------------------------------
::------ GetLength ----------------------------------------------------------------------------------------------------------------
:: %1 = string whose length must be returned
:: %1 = string whose length must be returned
Line 374: Line 445:
)
)
goto :nextchar
goto :nextchar
</source>


==== Escaping characters ====
rem ------------------------------------------------------------------------------------------------
Use <tt>^)</tt> to escape parenthesis in a command block
rem Redirection
<source lang="winbatch">
rem info from http://www.robvanderwoude.com/index.html
for %%i in (*.*) do (
rem ------------------------------------------------------------------------------------------------
echo first one: %%i (hello^)
echo second one: %%i (hello again^)
)
</source>


==== Test whether a task or service is running ====
rem command > file Write standard output of command to file
Here an example to test whether a given task or process is currently running:
rem command 1> file Write standard output of command to file (same as previous)
rem command 2> file Write standard error of command to file (OS/2 and NT)
rem command > file 2>&1 Write both standard output and standard error of command to file (OS/2 and NT)
rem command >> file Append standard output of command to file
rem command 1>> file Append standard output of command to file (same as previous)
rem command 2>> file Append standard error of command to file (OS/2 and NT)
rem command >> file 2>&1 Append both standard output and standard error of command to file (OS/2 and NT)
rem commandA ¦ commandB Redirect standard output of commandA to standard input of commandB
rem command < file Command gets standard input from file
rem command 2>&1 Command's standard error is redirected to standard output (OS/2 and NT)
rem command 1>&2 Command's standard output is redirected to standard error (OS/2 and NT)


<source lang="winbatch">
rem location of 2>&1 is critical. It must be placed at the end of the line, or right before the next pipe.
rem Test whether a service is running....
net start | find /i "automatic updates" > \nul
if ERRORLEVEL 1 (
echo No, it is not running
)


rem Test whether a task is running....
rem To redirect both standard output and standard error to a file, use 2>&1 at the end:
tasklist | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
echo No, it is not running
)

rem Test whether a task is running (more reliable)....
tasklist /FI "IMAGENAME eq task" 2>\nul | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
echo No, it is not running
)
</source>

==== Parsing <tt>tasklist</tt> output ====
It seems difficult to parse the output generated by the command <tt>tasklist</tt>. For instance the following command doesn't work as foreseen.
<source lang="winbatch">
> tasklist /svc | find /i "exe"
</source>
We can circumvent this using a for loop. Type in a batch file:
<source lang="winbatch">
@echo off
setlocal enabledelayedexpansion

for /F "usebackq tokens=1,2,3" %%i in (`tasklist /svc`) do (
if /I "%%k" EQU "wudfsvc" set UDF_PID=%%j
)
echo PID is %UDF_PID%
goto :EOF
</source>

=== Redirections ===
Info from http://www.robvanderwoude.com/index.html

<source lang="winbatch">
command > file Write standard output of command to file
command 1> file Write standard output of command to file (same as previous)
command 2> file Write standard error of command to file (OS/2 and NT)
command > file 2>&1 Write both standard output and standard error of command to file (OS/2 and NT)
command >> file Append standard output of command to file
command 1>> file Append standard output of command to file (same as previous)
command 2>> file Append standard error of command to file (OS/2 and NT)
command >> file 2>&1 Append both standard output and standard error of command to file (OS/2 and NT)
commandA ¦ commandB Redirect standard output of commandA to standard input of commandB
command < file Command gets standard input from file
command 2>&1 Command's standard error is redirected to standard output (OS/2 and NT)
command 1>&2 Command's standard output is redirected to standard error (OS/2 and NT)
</source>

Location of <tt>2>&1</tt> is critical. It must be placed at the end of the line, or right before the next pipe.

To redirect both standard output and standard error to a file, use <tt>2>&1<tt> at the end:

<source lang="winbatch">
echo Everything from stdout and stderr to a single file > file 2>&1
echo Everything from stdout and stderr to a single file > file 2>&1
</source>


rem Redirection can be placed at the beginning of the line to increase readability, but beware of side effects:
Redirection can be placed at the beginning of the line to increase readability, but beware of side effects:

<source lang="winbatch">
ECHO Directory of all files on C: >> LOG1.LOG
ECHO Directory of all files on C: >> LOG1.LOG
DIR C:\ /S >> LOG1.LOG
DIR C:\ /S >> LOG1.LOG
Line 408: Line 534:
rem is not the same as the line below (where it is VER that is redirected to LOG1.LOG !!!)
rem is not the same as the line below (where it is VER that is redirected to LOG1.LOG !!!)
> LOG1.LOG VER ¦ TIME
> LOG1.LOG VER ¦ TIME
</source>


rem Each redirection device exists in every directory on every drive so redirection to a device like
Each redirection device exists in every directory on every drive so redirection to a device like NUL, AUX, LPTn, COMn, PRN COST 1 file handle per device per directory where the redirection is done !!! To avoid this, avoid redirect to NUL, but redirect to '''\NUL''' or better '''%TEMP%\NUL'''. Also use '''PRINT''' instead of redirecting to '''LPTn'''.
rem NUL, AUX, LPTn, COMn, PRN COST 1 file handle per device per directory where the redirection is done !!!
rem To avoid this, avoid redirect to NUL, but redirect to \NUL or better %TEMP%\NUL
rem Use PRINT instead of redirecting to LPTn


<source lang="winbatch">
echo A way to trash output>NUL
echo This is a better way >\NUL
</source>


=== Using SED in a CMD batch file ===
rem ------------------------------------------------------------------------------------------------
rem Using SED in a batch file
rem ------------------------------------------------------------------------------------------------


rem - Escaping " in sed command
Main problem of using SED is escaping the '''quote''' <tt>"</tt> in sed command.


rem In general, use \" to escape "
* In general, use <tt>\"</tt> to escape <tt>"</tt>


<source lang="winbatch">
sed "s!\"Micro!\"Macro!g;" "temp.txt" ::Change '"Micro' into '"Macro'
sed "s!\"Micro!\"Macro!g;" "temp.txt" ::Change '"Micro' into '"Macro'
</source>


rem ... but if query contains \"[^\"]*
* ... but it doesn't work anymore if query contains <tt>\"[^\"]*</tt>


<source lang="winbatch">
sed "s!\"[^\"]*soft!hard!g;" "temp.txt" ::DOESN'T WORK
sed "s!\"[^\"]*soft!hard!g;" "temp.txt" ::DOESN'T WORK
</source>


rem SOLUTION: Don't quote the sed command (Ok if no space in it)!
* <u>Solution</u>: Don't quote the sed command (Ok if no space in it)!


<source lang="winbatch">
sed s!\"[^\"]*soft!hard!g; "temp.txt" ::WORKS !
sed s!\"[^\"]*soft!hard!g; "temp.txt" ::WORKS !
</source>


rem (if space, escape them using \x20 or \t )
* <u>Solution (cont'd)</u>: if there is space, escape them using <tt>\x20</tt> or <tt>\t</tt>.


rem Note that [^\"]*\" works even if quoted
* Note that <tt>[^\"]*\"</tt> works even if quoted:


<source lang="winbatch">
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" ::WORKS !
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" ::WORKS !
</source>


rem - Escaping " and redirecting output
* Escaping <tt>"</tt> and redirecting output
** <tt>\"</tt> is not recognised by cmd.exe, but considered as a backslash followed by an opening/closing quote
** &rarr; so redirection will work only if not enclosed in quotes according to cmd.exe:


<source lang="winbatch">
rem \" is not recognised by cmd.exe, but considered as a backslash followed by an opening/closing quote
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" > "result.txt" ::WORKS ! (even number of \")
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt" ::DOESN'T WORK ! (odd number of \")
</source>


* <u>Solution</u>: Use a sub-routine:
rem --> so redirection will work only if not enclosed in quotes according to cmd.exe:


<source lang="winbatch">
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" > "result.txt" ::WORKS ! (even number of \")
call :DOIT > "result.txt"
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt" ::DOESN'T WORK ! (odd number of \")
rem ...

rem SOLUTION: Use a sub-routine:

call :DOIT > "result.txt"
rem ...


:DOIT
:DOIT
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt"
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt"
</source>
</source>

Latest revision as of 13:29, 3 September 2009

External References

Frequent Mistakes

  • WRONG - Use = for string comparison.
  • CORRECT - Use == for string comparison.
  • BETTER - EQU is less confusing and less exposed to mistake.
if "0" = "0" echo Hello    # WRONG
if "0" == "0" echo Hello   # CORRECT
if "0" EQU "0" echo Hello  # BETTER


  • WRONG - Closing bracket will close the IF statement!
  • CORRECT - Use [] instead
  • BETTER - Escape parenthesis with ^ instead
if "0" EQU "0" (
  rem do something
  echo I am doing something (and something)...     # WRONG
)
if "0" EQU "0" (
  rem do something
  echo I am doing something [and something]...     # CORRECT
)
if "0" EQU "0" (
  rem do something
  echo I am doing something ^(and something^)...   # BETTER
)


  • WRONG - Closing bracket will close the IF statement even in variable expansion!
  • CORRECT - Always quote variables with "..."
  • CORRECT - Quotes " not necessary with %% variable
set MYVAR=Closing)
if "0" EQU "0" (
  rem do something
  echo My beautiful var %MYVAR%...        # WRONG
)

set MYVAR=Closing)
if "0" EQU "0" (
  rem do something
  echo My beautiful var "%MYVAR%"...      # CORRECT
)

for /F %%i in ("bracket)") do (
  echo My Closing bracket: %%i            # CORRECT
)


  • Some Variable Expansion:
    • !!! Use %% for FOR statement in batch file only
    • !!! Use single % for command line parameters only
set MYVAR=My Var
echo %MYVAR       # expands to MYVAR
echo %%MYVAR      # expands to %MyVar
echo %MYVAR%      # expands to My Var
echo "%MYVAR"     # expands to "MYVAR"
echo "%%MYVAR"    # expands to "%MYVAR"
echo "%MYVAR%"    # expands to "My Var"


  • WRONG - %VAR% are expanded when read not when executed (early expansion)!
  • CORRECT - Use a subroutine !
  • BETTER - Use Variable Delayed Expansion and syntax !VAR!(enabled with cmd.exe /V or setlocal enabledelayedexpansion)!
setlocal ENABLEDELAYEDEXPANSION
set MYVAR=
for /F %%i in ("TEST") do (
  echo TEST: %%i           # expands to TEST: TEST
  set MYVAR=%%i
  echo MYVAR: "%MYVAR%"    # WRONG   - expands to TEST: ""
  call :myset %%i          # CORRECT - expands to TEST: "TEST" - call subroutine
  echo MYVAR: "!MYVAR!"    # BETTER  - expands to TEST: "TEST" - delayed expansion
)
goto :EOF

:myset
set MYVAR=%1
echo MYVAR: "%MYVAR%"      # CORRECT - within sub, expands to TEST: "TEST"
goto :EOF


  • WRONG - Possible syntax error if MYVAR is empty in IF ... EQU expression
  • CORRECT - Always quote variables (we'll never repeat this enough) !
set MYVAR=
if MYVAR EQU YES echo failed       # WRONG
if "MYVAR" EQU "YES" echo failed   # CORRECT


  • WRONG - SET command includes trailing blanks
  • CORRECT - Remove all trailing blanks in SET command
  • BETTER - Configure your favorite editor to always remove trailing blanks on every line on save
# WRONG - set with trailing blanks
set MYDIR=C:\TEMP\  
# CORRECT - set without trailing blanks
set MYDIR=C:\TEMP\


  • WRONG - trailing blanks inside path makes %~n1 to fail (!!! very frequent when using an env_var with trailing blanks !!!)
  • CORRECT - remove trailing blanks when using %~n1

EDIT: This bug seems fixed in Windows XP now.

call :strip "C:\TEMP  \MYFILE.ext"    # WRONG... will output 'FILE'
rem

call :strip "C:\TEMP\MYFILE.ext"      # CORRECT... will output 'MYFILE'

goto :EOF
:strip
echo %~n1
goto :EOF


  • WRONG - %* not modified by SHIFT
  • CORRECT - Use a loop instead with %~1
echo %*
SHIFT
echo %*                               # WRONG... will output the same string

:getFileList
if "%~1" EQU "" goto :noMoreParam
echo "%~1"                            # CORRECT... Loop with %~1
SHIFT
goto :getFileList
:noMoreParam


  • WRONG - Never use "%1" or %1
  • CORRECT - Always use "%~1" (always correctly quoted)
type "%1"             # WRONG
type "%~1"            # CORRECT


  • WRONG - in W2K, FOR /R skips SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
  • CORRECT - Use DIR instead (here we list all files that is not a directory...)
  • CORRECT - Use DIR instead (here we list all directories)
for /r "." %%i in (*.*) do @echo %%i                        # WRONG - Skips SYSTEM files/directories
for /F "usebackq" %%i in (`dir /a:-d /s /b`) do @echo %%i   # CORRECT (all files that is not a directory)
for /F "usebackq" %%i in (`dir /a:d /s /b`) do @echo %%i    # CORRECT (all directories)


  • WRONG - cmd.exe remove surrounding quotes, except in some very specific cases (see CMD /?).
  • CORRECT - Always surround with quotes and always use flag cmd.exe /S to force quote removal.
rem
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /C %MYBAT% %MYPARAM%                # WRONG - quotes will be removed from parameters

set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /S /C "%MYBAT% %MYPARAM%"           # CORRECT - First quote in %MYBAT% and last quote in %MYPARAM% will be removed.


  • WRONG - FOR /F with other delims NOK if usebackq used (it seems there is a buffer overflow in cmd.exe)
  • CORRECT - FOR /F with other delims is ok if usebackq not used

EDIT: This bug seems fixed in Windows XP now.

set STRING=MaskOSLib Maker~1:dir:1
for /F "usebackq tokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l    # WRONG
goto :EOF
set STRING="MaskOSLib Maker~1:dir:1"
for /F "tokens=1-4 delims=:~" %%i in (%STRING%) do echo %%i-%%j-%%k-%%l               # CORRECT
goto :EOF


  • WRONG - Use FOR /F to enumerate items in a list
  • CORRECT - Use FOR to enumerate items in a list (space, comma and semi-colon are used as separators)
set STRING=value1 value2 value3
for /F %%i in ("%STRING%") do echo %%i   # WRONG
for %%i in (%STRING%) do echo %%i        # CORRECT
goto :EOF


  • WRONG - returning an ERRORLEVEL to VBS with an ending goto :EOF (or whatever)
  • CORRECT - Set ERRORLEVEL as the very last operation executed
:: following set ERRORLEVEL to 1, and then exit
VERIFY OTHER 2>/NUL
goto :EOF                               # WRONG - Calling script will see an ERRORLEVEL 0

::-----------------------------

setlocal
:: ...
if ... goto :FINISHFAIL
:: Do some stuffs and finish...
endlocal
goto :EOF

:FINISHFAIL
:: close locals and set ERRORLEVEL
endlocal
VERIFY OTHER 2>/NUL                     # CORRECT - Calling script will see an ERRORLEVEL > 0

Hints and Tips

Miscellaneous

Bypass pause

To bypass all pause commands, or to answer next set /P command

echo Y| batchpgm.bat

Enumerate list

Enumerate elements in a list

set SOURCEFILEEXT=.c .h
set TEXTFILEEXT=%SOURCEFILEEXT% .txt .ddf
for %i in (%TEXTFILEEXT%) do echo %i

Alternate rem

:: This is also a remark

Inlined rem

echo Hello world      &rem ampersand must be used for inlined rem (all space on the left are part of echo command!)

Persistent errorlevel

Make errorlevel persistent - env. var override ERRORLEVEL if defined

if ERRORLEVEL 1 set ERRORLEVEL=%ERRORLEVEL%
echo %ERRORLEVEL%

Change dir and drive

cd /D "%TEMP%"

Parsing command result

for /F "usebackq tokens=*" %%i in (`isText /sv %1`) do echo ... invalid ASCII: "%%i"

Echo an empty line

echo.

Scan directory and subdir

Scan directory and subdir - %%~pnxi is used to remove trailing dot

for /R "%~1\" %%i in (.) do echo  "%%~pnxi"

Ask user input

Ask user input (note that these scripts required Delayed Expansion to be enabled)

setlocal enabledelayedexpansion
set /P CONFIRM=... Empty files found [might crash GNU/Indent]... Delete [Y/N/Abort]?
if /I "!CONFIRM:~0,1!" EQU "Y" (
  echo user says YES
) else (
  echo user says NO
)

Ask user input with default value

setlocal enabledelayedexpansion
set USER=Y
set /P USER=Do you want to DISABLE it (Y/n^)
if /I "!USER:~0,1!" EQU "y" (
  echo user said YES
) else (
  echo user said NO
)

Set errorlevel in subroutine

Call :MySub "1"
goto :Continue

:MySub
if "%1" NEQ "1" exit /b 1
goto :EOF

:Continue

Remove surrounding quotes in argument

echo with or without quote: %1
echo always without quote: %~1
echo always with quote: "%~1"

Use `echo %ENV_VAR%` in FOR loops

Use `echo %ENV_VAR%` instead of '%ENV_VAR%' in FOR loops

rem   Assume ENV_VAR is set to a very long list of items delimited by ;
rem   The following fails when ENV_VAR is too long
for /F "usebackq delims=;" %%i in ('%ENV_VAR%') do echo %%i
rem   The following works when ENV_VAR is too long
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do echo %%i

Enumerate items in a list separated by a blank

Enumerate items in a list separated by a blank - !!! The list may not contain joker like * or & !!!

set TEXTFILEEXT=.txt .ddf .c .h
for %%i in (%TEXTFILEEXT%) do echo *%%i

Enumerate items in a list separated by another token

Enumerate items in a list separated by an another token (list can be quite long)

set ENV_VAR="item1";"item2";"item3"
for /F "usebackq delims=;" %%i in (`echo %ENV_VAR%`) do call :enumerate %%i
rem ... For loop is only to translate ; into blanks
goto :EOF
:enumerate
if "%~1" equ "" goto :EOF
echo "%~1"
SHIFT
goto :enumerate

Yet another solution To enumerate items in a list separated by an another token (list can be quite long)

set ENV_VAR="item1";"item2";"item3"; my item
call :enumerate %ENV_VAR:;= %
goto :EOF
:enumerate
if "%~1" equ "" goto :EOF
echo "%~1"
SHIFT
goto :enumerate

Enable Command Processor Extensions / Delayed Expansion

SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL DISABLEDELAYEDEXPANSION
rem ... setlocal set errorlevel to 0 if successful - can be used to detect if extensions are supported

Know whether a name refers to an existing file or to an existing dir

if exist "%~1" goto :isFile
pushd "%~1"
if errorlevel 1 goto :doesntexist
popd
:isDir
rem %1 is a directory
:isFile
rem %1 is a file
:doesntexist
rem %1 does not exist

String length subroutine

An optimized sub-routine that returns the length of a string (requires Delayed Expansion to be enabled)

::------ GetLength ----------------------------------------------------------------------------------------------------------------
:: %1     = string whose length must be returned
:: RESULT = length
:GetLength
set _STR=_%~1
set RESULT=0
set _STEP=128
:nextchar
if "%_STEP%" EQU "0" goto :EOF
set /A _I=RESULT + _STEP
if "!_STR:~%_I%!" EQU ""  (
  set /A "_STEP>>=1"
) ELSE (
  set /A RESULT=RESULT + _STEP
  if "%_STEP%" NEQ "128" set /A "_STEP>>=1"
)
goto :nextchar

Escaping characters

Use ^) to escape parenthesis in a command block

for %%i in (*.*) do (
        echo first one: %%i (hello^)
        echo second one: %%i (hello again^)
)

Test whether a task or service is running

Here an example to test whether a given task or process is currently running:

rem Test whether a service is running....
net start | find /i "automatic updates" > \nul
if ERRORLEVEL 1 (
  echo No, it is not running
)

rem Test whether a task is running....
tasklist | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
  echo No, it is not running
)

rem Test whether a task is running (more reliable)....
tasklist /FI "IMAGENAME eq task" 2>\nul | find /i "task" >\nul 2>\nul
if ERRORLEVEL 1 (
  echo No, it is not running
)

Parsing tasklist output

It seems difficult to parse the output generated by the command tasklist. For instance the following command doesn't work as foreseen.

> tasklist /svc | find /i "exe"

We can circumvent this using a for loop. Type in a batch file:

@echo off
setlocal enabledelayedexpansion

for /F "usebackq tokens=1,2,3" %%i in (`tasklist /svc`) do (
	if /I "%%k" EQU "wudfsvc" set UDF_PID=%%j
)
echo PID is %UDF_PID%
goto :EOF

Redirections

Info from http://www.robvanderwoude.com/index.html

command > file          Write standard output of command to file
command 1> file         Write standard output of command to file (same as previous)
command 2> file         Write standard error of command to file (OS/2 and NT)
command > file 2>&1     Write both standard output and standard error of command to file (OS/2 and NT)
command >> file         Append standard output of command to file
command 1>> file        Append standard output of command to file (same as previous)
command 2>> file        Append standard error of command to file (OS/2 and NT)
command >> file 2>&1    Append both standard output and standard error of command to file (OS/2 and NT)
commandA ¦ commandB     Redirect standard output of commandA to standard input of commandB
command < file          Command gets standard input from file
command 2>&1            Command's standard error is redirected to standard output (OS/2 and NT)
command 1>&2            Command's standard output is redirected to standard error (OS/2 and NT)

Location of 2>&1 is critical. It must be placed at the end of the line, or right before the next pipe.

To redirect both standard output and standard error to a file, use 2>&1 at the end:

echo Everything from stdout and stderr to a single file > file 2>&1

Redirection can be placed at the beginning of the line to increase readability, but beware of side effects:

ECHO Directory of all files on C: >> LOG1.LOG
DIR C:\ /S >> LOG1.LOG
rem is the same as the more readable version:
>> LOG1.LOG   ECHO Directory of all files on C:
>> LOG1.LOG   DIR C:\ /S
rem But the following
VER ¦ TIME > LOG1.LOG
rem is not the same as the line below (where it is VER that is redirected to LOG1.LOG !!!)
> LOG1.LOG VER ¦ TIME

Each redirection device exists in every directory on every drive so redirection to a device like NUL, AUX, LPTn, COMn, PRN COST 1 file handle per device per directory where the redirection is done !!! To avoid this, avoid redirect to NUL, but redirect to \NUL or better %TEMP%\NUL. Also use PRINT instead of redirecting to LPTn.

echo A way to trash output>NUL
echo This is a better way >\NUL

Using SED in a CMD batch file

Main problem of using SED is escaping the quote " in sed command.

  • In general, use \" to escape "
sed "s!\"Micro!\"Macro!g;" "temp.txt"               ::Change '"Micro' into '"Macro'
  • ... but it doesn't work anymore if query contains \"[^\"]*
sed "s!\"[^\"]*soft!hard!g;" "temp.txt"             ::DOESN'T WORK
  • Solution: Don't quote the sed command (Ok if no space in it)!
sed s!\"[^\"]*soft!hard!g; "temp.txt"               ::WORKS !
  • Solution (cont'd): if there is space, escape them using \x20 or \t.
  • Note that [^\"]*\" works even if quoted:
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt"           ::WORKS !
  • Escaping " and redirecting output
    • \" is not recognised by cmd.exe, but considered as a backslash followed by an opening/closing quote
    • → so redirection will work only if not enclosed in quotes according to cmd.exe:
sed "s![^\"]*\"Micro!Macro!g;" "temp.txt" > "result.txt"        ::WORKS !           (even number of \")
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt"      ::DOESN'T WORK !    (odd number of \")
  • Solution: Use a sub-routine:
call :DOIT > "result.txt"
rem ...

:DOIT
sed "s![^\"]*\"Micro!\"Macro!g;" "temp.txt" > "result.txt"