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...)
 
(→‎Hints and Tips: Reformating)
Line 243: Line 243:


== 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...
* 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>


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>


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

<source lang="winbatch">
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" (
Line 288: Line 311:
echo user says NO
echo user says NO
)
)
</source>


rem to set errorlevel in subroutine
* Set errorlevel in subroutine

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


: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> 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 342:
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 - '''!!! 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 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 363:
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 376:
SHIFT
SHIFT
goto :enumerate
goto :enumerate
</source>


rem To enable/disable Command Processor Extension or Delayed Extension
* Enable/disable Command Processor Extension or Delayed Extension

<source lang="winbatch">
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
SETLOCAL DISABLEEXTENSIONS
Line 343: Line 386:
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 401:
:doesntexist
:doesntexist
rem %1 does not exist
rem %1 does not exist
</source>


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 423:
)
)
goto :nextchar
goto :nextchar
</source>


=== Redirections ===
rem ------------------------------------------------------------------------------------------------
Info from http://www.robvanderwoude.com/index.html
rem Redirection
rem info from http://www.robvanderwoude.com/index.html
rem ------------------------------------------------------------------------------------------------


<source lang="winbatch">
rem command > file Write standard output of command to file
rem command 1> file Write standard output of command to file (same as previous)
command > file Write standard output of command to file
rem command 2> file Write standard error of command to file (OS/2 and NT)
command 1> file Write standard output of command to file (same as previous)
rem command > file 2>&1 Write both standard output and standard error of command to file (OS/2 and NT)
command 2> file Write standard error of command to file (OS/2 and NT)
rem command >> file Append standard output of command to file
command > file 2>&1 Write both standard output and standard error of command to file (OS/2 and NT)
rem command 1>> file Append standard output of command to file (same as previous)
command >> file Append standard output of command to file
rem command 2>> file Append standard error of command to file (OS/2 and NT)
command 1>> file Append standard output of command to file (same as previous)
rem command >> file 2>&1 Append both standard output and standard error of command to file (OS/2 and NT)
command 2>> file Append standard error of command to file (OS/2 and NT)
rem commandA ¦ commandB Redirect standard output of commandA to standard input of commandB
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
rem command < file Command gets standard input from file
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)
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>


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


rem To redirect both standard output and standard error to a file, use 2>&1 at the end:
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 463:
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>

Revision as of 10:31, 19 January 2009

Frequent Mistakes

rem ===============================================================================================================================
rem WIN2000 BATCH FILES FREQUENT MISTAKES
rem ===============================================================================================================================

goto :EOF& rem to avoid execution of this example file...

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

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

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

rem BETTER - 'EQU' is less confusing and exposed to mistake
if "0" EQU "0" (
 echo Hello
)

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

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

rem GOOD - Use [] instead
if "0" EQU "0" (
  rem do something
  echo I'm doing something [and something]...
)


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

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

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

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

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

rem Some Variable Expansion
set MYVAR=My Var
echo %MYVAR     &rem EXPAND TO MYVAR
echo %%MYVAR    &rem EXPAND TO %MyVar
echo %MYVAR%    &rem EXPAND TO My Var
echo "%MYVAR"   &rem EXPAND TO "MYVAR"
echo "%%MYVAR"  &rem EXPAND TO "%MYVAR"
echo "%MYVAR%"  &rem EXPAND TO "My Var"

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

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

rem WRONG: %VAR% are expanded when read not when executed (early expansion)!
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: ""
)

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
set MYVAR=%1
echo MYVAR: "%MYVAR%"      &rem EXPAND TO TEST: "TEST"
goto :EOF

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"

rem GOOD: Use Delayed Expansion (activated by CMD /V)!
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"
)

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

rem WRONG: Possible syntax error if MYVAR is empty in IF ... EQU expression
set MYVAR=
if MYVAR EQU YES echo failed

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

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

rem WRONG: SET command includes TRAILING BLANKS
set MYDIR=C:\TEMP\

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

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

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"
rem ... will output 'FILE'

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

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

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

rem WRONG: %* not modified by SHIFT
echo %*
SHIFT
echo %*
rem ... will output the same string

rem GOOD: Use a loop instead
if defined FILELIST set FILELIST=
:getFileList
if "%~1" EQU "" goto :noMoreParam
set FILELIST=%FILELIST% "%~1"
SHIFT
goto :getFileList
:noMoreParam

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

rem WRONG: Never use "%1" or %1
type "%1"

rem GOOD: Always use "%~1"
type "%~1"

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

rem WRONG: in W2K, for /r skip SYSTEM files/directories (in 4NT, SYSTEM & HIDDEN files are skipped).
for /r "." %%i in (*.*) do @echo %%i

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

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

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

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

rem GOOD: Always surround with quotes and always use flag /S to force quote removal
set MYBAT="C:\Program Files\MyBat\MyBat.bat"
set MYPARAM="parameter 1" "parameter 2"
cmd /V:F /S /C "%MYBAT% %MYPARAM%"

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

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
for /F "usebackqtokens=1-4 delims=:~" %%i in ('%STRING%') do echo %%i-%%j-%%k-%%l
goto :EOF

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

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)
set STRING=value1 value2 value3
for %%i in (%STRING%) do echo %%i
goto :EOF


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

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

:: following set ERRORLEVEL to 1, and then exit
VERIFY OTHER 2
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
:: Do some stuffs...
:: We must fail for some condition
if %COND% equ "YES" goto :FINISHFAIL
:: Do some stuffs and finish...
endlocal
goto :EOF

:FINISHFAIL
:: close locals and set ERRORLEVEL
endlocal
VERIFY OTHER 2>NUL

Hints and Tips

Miscellaneous

  • To bypass all pause commands, or to answer next set /P command
echo Y| batchpgm.bat
  • 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!)
  • 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 - %%~pnxi is used to remove trailing dot
for /R "%~1\" %%i in (.) do echo  "%%~pnxi"
  • Ask user input
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
)
  • 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%` 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 - !!! 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 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/disable Command Processor Extension or Delayed Extension
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
  • 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

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"