CMD Shell Tips and Pitfalls: Difference between revisions
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> |
|||
* 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> |
|||
* 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> |
|||
* 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> |
|||
* Change dir and drive |
|||
<source lang="winbatch"> |
|||
cd /D "%TEMP%" |
cd /D "%TEMP%" |
||
</source> |
|||
* 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> |
|||
* Echo an empty line |
|||
<source lang="winbatch"> |
|||
echo. |
echo. |
||
</source> |
|||
* 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> |
|||
* 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> |
|||
* Set errorlevel in subroutine |
|||
<source lang="winbatch"> |
|||
Call :MySub "1" |
Call :MySub "1" |
||
goto :Continue |
goto :Continue |
||
Line 298: | Line 324: | ||
:Continue |
:Continue |
||
</source> |
|||
* 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> |
|||
* 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> |
|||
* 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> |
|||
* '''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> |
|||
* 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> |
|||
* 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> |
|||
* 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 |
|||
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 |
|||
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) |
|||
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> |
|||
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> |
|||
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 ------------------------------------------------------------------------------------------------ |
|||
Main problem of using SED is escaping the '''quote''' <tt>"</tt> in sed command. |
|||
* 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> |
|||
* ... 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> |
|||
* <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> |
|||
* <u>Solution (cont'd)</u>: if there is space, escape them using <tt>\x20</tt> or <tt>\t</tt>. |
|||
* 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> |
|||
* 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 |
|||
** → 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 |
|||
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"