Reputation: 11
I need to escape "!" (and other special chars) in a for-loop where delayed variable expansion is enabled
I've tried manually escaping the loop-varible with string substitution ^^! in the loop-variable, %%a but with no luck at all. Is it already too late after the for has read them? If so, how the heck can I even accomplish this?
Below is a short function. The only relevant part here is the for-loop and the echo statement. That is printing out whole lines from a file every X'th line, and those lines are file-paths. They (sometimes) contain characters like "!" and other troublesome special characters. I just want echo here to pass it without interpreting it at all - but instead it ends up deleting my "!" chars. For my use they need to be exactly correct or they are useless as they must correlate to actual files later on in what I use them for.
setlocal EnableDelayedExpansion
:SplitList
if [%3] == [] goto :SplitListUsage
set inputfile=%~1
set outputfile=%~2
set splitnumber=%~3
set skipLines=0
set skipLines=%~4
if %skipLines% GTR 0 (
set skip=skip=%skipLines%
) else (
set skip=
)
@echo off > %outputfile%
set lineNumber=0
for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
set /a modulo="!lineNumber! %% !splitnumber!"
if "!modulo!" equ "0" (
echo %%a >> !outputfile!
)
set /a lineNumber+=1
)
exit /B 0
Upvotes: 1
Views: 269
Reputation: 1463
Quick solution:
if !modulo! equ 0 (
setlocal DisableDelayedExpansion
(echo %%a)>> "%outputfile%"
endlocal
)
Better solution:
Based on your code sample, there is no need to have delayed expansion enabled for the entire code,
in fact you should keep it disabled to not mess with the file names or input strings which may contain !
, and enabled it when necessary:
setlocal DisableDelayedExpansion
REM Rest of the code...
for /f "tokens=* %skip% delims= " %%a in (%inputfile%) do (
set /a "modulo=lineNumber %% splitnumber"
setlocal EnableDelayedExpansion
for %%m in (!modulo!) do (
endlocal
REM %%m is now have the value of modulo
if %%m equ 0 (
(echo %%a)>> "%outputfile%"
)
)
set /a lineNumber+=1
)
Side notes:
There are some other issues with your code which as you might have noticed, some of them are corrected in the above solutions. But to not distract you from main issue you had, I covered them here separately.
There is also room for improving the performance of the code when writing to the outputfile
.
Here is the re-written code which covers the rest:
@echo off
setlocal DisableDelayedExpansion
:SplitList
if "%~3"=="" goto :SplitListUsage
set "inputfile=%~1"
set "outputfile=%~2"
set "splitnumber=%~3"
set "skipLines=0"
set /a "skipLines=%~4 + 0" 2>nul
if %skipLines% GTR 0 (
set "skip=skip=%skipLines%"
) else (
set "skip="
)
set "lineNumber=0"
(
for /f "usebackq tokens=* %skip% delims= " %%a in ("%inputfile%") do (
set /a "modulo=lineNumber %% splitnumber"
setlocal EnableDelayedExpansion
for %%m in (!modulo!) do (
endlocal
REM %%m is now have the value of modulo
if %%m equ 0 echo(%%a
)
set /a lineNumber+=1
)
)>"%outputfile%"
"
e.g. set inputfile=%~1
. If the now naked batch parameter %~1
contains spaces or special characters like &
your batch files fails, either fatally with a syntax error or at execution time with incorrect data. The recommended syntax is to use set "var=value"
which does not assign the quotes to the variable value but provides protection against special characters. It also protects the assignment against the accidental trailing spaces.%inputfile%
may contain special characters or spaces, so it should be protected by double quoting it when using in the FOR /F
's IN clause. When double quoting the file name in FOR /F
the usebackq
parameter must also be used.SET /A
there is no need to expand the variable values: set /a modulo="!lineNumber! %% !splitnumber!"
. The variable names can be used directly, and it will work correctly with or without delayed expansion.(echo %%a) >> "%outputfile%"
inside a FOR
loop introduces a severe performance penalty specially with a large number of iterations, because at each iteration the output file will be opened, written to and then closed. To improve the performance The whole FOR
loop can redirected once. Any new data will be appended to the already opened file.echo(
is to protect against the empty variable values, or when the variable value is /?
. Using echo %%a
may print the `ECHO is on/off' message if the variable is empty or may print the echo usage help.(echo %%a)>> "%outputfile%"
instead of echo %%a >> "%outputfile%"
is to prevent outputting the extra space between %%a
and >>
. Now you know the reason for using echo(
, it is easy to understand the safer alternative: (echo(%%a)>> "%outputfile%"
Upvotes: 2