Issue With Delayed Expansion In Batch Code - batch-file

I am running into an issue where I have a complete directory listing of a computer (located in the text file shown below).
The issue appears in cases where the directory listing contains special characters such as "! or &". When that issue occurrs, the filename is parsed such that those special characters are omitted (thus causing issues with leveraging those variables to compute other sub-tasks). Below is a snapshot of the code. Please advise as to how I might proceed.
Note that within the text file might be paths such as:
C:\Windows!temp!\file.txt
C:\Windows\file.txt
This will parse as:
C:\Windows\temp\file.txt (without quotes)
C:\Windows\file.txt
code snip
for /f "delims=?" %%a in (dir.txt) do (
setlocal enabledelayedexpansion
set filepath=%%a
)
call :subproc1
)
goto :proc2
:subproc1
echo !filepath!
endlocal
goto :eof
:proc2
continue with script here

You are toggling delayed expansion on too early, and there is no need for the subroutine.
for /f "delims=?" %%a in (dir.txt) do (
set filepath=%%a
setlocal enabledelayedexpansion
echo !filepath!
endlocal
)

#echo off
setlocal enableDelayedExpansion
for /f "delims=?" %%a in (dir.txt) do (
setlocal disableDelayedExpansion
(echo(%%a)
endlocal
)
endlocal
goto :proc2
:proc2
echo -------end
the Jeb's trick...

Related

Error in batch variable expansion with replacement containing parentheses

I have the following Batch script which replaces occurrences of {path} in a text file with a path:
#echo off
setlocal EnableDelayedExpansion
set myPath=C:\Program Files (x86)\foo
for /F "tokens=*" %%A in (template.txt) do (
set line=%%A
set line=!line:{path}=%myPath%!
echo !line!
)
When I run this script I get the error message
\foo! was unexpected at this time.
If I remove the parentheses in the path it works as expected. How can I solve this problem? I cannot have quotes around the path in the text file so putting quotes around the path in the set statement is not an option.
#echo off
setlocal EnableDelayedExpansion
set "mypath=C:\Program Files (x86)\foo"
for /F "tokens=*" %%A in (q71593680.txt) do (
set "line=%%A"
CALL :changeline
echo !line!
)
FOR %%c IN ("%mypath%") DO for /F "tokens=*" %%A in (q71593680.txt) do (
set "line=%%A"
set "line=!line:{path}=%%~c!"
echo !line!
)
GOTO :EOF
:changeline
set "line=!line:{path}=%mypath%!"
GOTO :eof
The problem is that the ) in the substitution-string is being taken as the closing parenthesis of the do.
Solution : make an internal subroutine as shown.
Use set "var1=data" for setting string values - this avoids problems caused by trailing spaces.
Don't use path as a variablename - it's a reserved word in batch, meaning the sequence in which the directories are searched to find an executable that isn't in the current directory.
Use set path=%ProgramFiles(x86)%\foo first (this folder may be elsewhere, this variable is here to find it anyway), and surround the replacement set with quotes (see below).
Corrected program:
#echo off
setlocal EnableDelayedExpansion
set pathval=%ProgramFiles(x86)%\foo
for /F "tokens=*" %%A in (template.txt) do (
set line=%%A
REM Please note the quotes around the affectation. It avoids an interpretation.
set "line=!line:{path}=%pathval%!"
echo !line!
)
Side note: even with setlocal, avoid to name a variable path...

Can't escape semicolon inside quotes in batch file using enableDelayedExpansion

I'm manipulating some HTML via Batch. I'm using delayed expansion but when a semicolon is encountered inside double quotes, the script fails to copy anything after and including the semicolon.
It's probably due to the fact that I use double quotes when I pass the variable to the putLineInHTMLFile label (I need to keep things separated in labels).
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=" %%x in (file.html) do call :putLineInHTMLFile "%%x"
goto :EOF
:putLineInHTMLFile
set "line=%~1"
echo !line!>> output.html
file.html contains:
<tag1>
<tag"bla;2">
After running the script, output.html contains:
<tag1>
<tag"bla
I've tried escaping the semicolon with ^ or ^^. Didn't work. I've tried escaping the double quotes too. That didn't work either.
I can change the contents of file.html anyway I please just as long as I can include that semicolon in the output file.
This seems to work for the test-case given; no guarantees for wider use:
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=# tokens=*" %%x in (file.html) do (
set "safe=%%x"
set "safe=!safe:"=""!"
call :putLineInHTMLFile "!safe!"
)
goto :eof
:putLineInHTMLFile
set "line=%~1"
set "line=%line:""="%"
echo !line!>> output.html
:eof
Within the "body" of the for command, the %%x has not been split, it's only when processed by the call command that this happens. To protect that, I've used safe to double-up all double-quotes in the string, and then added a line in the subroutine to strip them out again.
This doesn't work properly if the double-quotes aren't matched, but in those cases, neither does the echoing of the trailing >, even when there are no semi-colons present.
This method works in all cases, as long as the quotes be matched (even number) in the input lines:
#echo off
setlocal enableDelayedExpansion
del output.html
for /f "delims=" %%x in (file.html) do call :putLineInHTMLFile "%%x"
goto :EOF
:putLineInHTMLFile
set line=%*
echo !line:~1,-1!>> output.html
PS - Please, do not include additional parts that not appears in the original code, like the :eof label... The label is written in uppercase letters in goto :EOF command to indicate that it is special. Type goto /? for further details.
Certainly. The problem is that the subroutine receives
"<tag"bla;2">"
for that line. The parser sees that as
Token1 : "<tag"bla
Token2 : 2">"
because ; like Space is a separator.
Sadly, without knowing what you intend to do within the subroutine, it's difficult to advise on how to circumvent the problem.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43391363.txt"
SET "outfile=%destdir%\outfile.txt"
SET "outfile2=%destdir%\outfile2.txt"
del "%outfile%"
del "%outfile2%"
(for /f "delims=" %%x in (%filename1%) do set "line=%%x"&call :putLineInHTMLFile "%%x")>"%outfile2%"
goto :eof
:putLineInHTMLFile
ECHO %*
echo !line!>>"%outfile%"
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q43391363.txt containing your data for my testing.
Produces files defined as %outfile% and %outfile2%
So - here's two different ways, one using conventional output direct to outfile1 and the other using redirection from a subroutine into outfile2.

Issue With Special Characters In File Path

I posted a question earlier today but the answers received did not solve the issue (probably an error on my part by not providing enough sample code...not to mention a few typos). In this case, everything works just fine until I get to a filepath that has "!" or "&" in the path itself. When there is one of these characters the characters are stripped from the path (note the rest of the path appears normal) but when the hash function is called the modified path is not found (as one would expect). I need to prevent these characters from being stripped but also function in the case where the path has spaces or no spaces.
I know I need to use expansion (or some form thereof), just not sure as to the exact placement to get the desired effect.
Code Snip
#echo off
for /f "delims=?" %%A in (dir-selected_tmp.txt) do (
set filepath=%%A
call :filepathparse
call :md5hashchk
call :echohash
)
goto :eof
:filepathparse
for %%B in ("%filepath%") do (
set filename=%%~nxB
)
goto :eof
:md5hashchk
for /f "delims= " %%b in ('md5deep64.exe "!filepath!"') do set hashvalue=%%b
goto :eof
:echohash
echo !hashvalue!
goto :eof
Add setLocal enableDelayedExpansion to the start of your script.
You cannot wrap variables with exclamation marks unless you have that set. See this page for more information about variable expansion in batch.
Your code, as it was written, don't need the delayed expansion !variables!; just replace exclamation-marks by percents:
:md5hashchk
for /f "delims= " %%b in ('md5deep64.exe "%filepath%"') do set hashvalue=%%b
goto :eof
:echohash
echo %hashvalue%
goto :eof
EDIT: Response to comment
Try this:
#echo off
for /f "delims=?" %%A in (dir-selected_tmp.txt) do (
set "filepath=%%A"
call :filepathparse
call :md5hashchk
call :echohash
)
goto :eof
:filepathparse
for %%B in ("%filepath%") do (
set "filename=%%~nxB"
)
goto :eof
:md5hashchk
for /f "delims= " %%b in ('md5deep64.exe "%filepath%"') do set "hashvalue=%%b"
goto :eof
:echohash
set /P "=%hashvalue%" < NUL
echo/
goto :eof

Batch file to handle random % character in FOR LOOP

I am trying to pass file names as FOR loop parameters to a separate batch file. The problem is, if a file name contains special characters (especially %), the parameter doesnt go to the called script. EG -
The FIRST_SCRIPT.bat is as follows -
cd "C:\theFolder"
for /R %%a in (*.*) do call SECOND_SCRIPT "%%~a"
The SECOND_SCRIPT.bat is as follows -
ECHO %1
If a file name contains % eg. "% of STATS.txt", the output ends up being
of STATS.txt
Which is wrong. I have tried using Setlocal DisableDelayedExpansion but with little success
Setlocal DisableDelayedExpansion
for /R %%a in (*.*) do (
SET "var=%%~a"
Setlocal EnableDelayedExpansion
call TEST_UPGRADE "%var%" "%%~a"
)
There are other stackoverflow answers, but they all need the % character to be known before hand. Since the file names are not in our control, these solutions won't work for us. Is there any way of handling this?
Thanks!
platform: cmd.exe for Windows XP
Aacini shows a solution that would work with % and also ! but it fails with carets ^.
But the solution is simple.
First it's necessary to disable the delayed expansion to handle the exclamation marks.
The filename is now exactly in the var variable.
The problems with carets and percents are caused by the CALL.
This can be solved with the CALL itself by use only the second percent expansion phase of the CALL by using %%var%%.
Setlocal DisableDelayedExpansion
for /R %%a in (*.*) do (
SET "var=%%~a"
call TEST_UPGRADE "%%var%%"
)
The next problem is in the second.bat to display the filename.
This should be done with delayed expansion enabled to avoid problems with special characters, or you need always quotes.
set "var=%~1"
setlocal EnableDelayedExpansion
echo Filename: !var!
solution with a temp file:
first.bat
#ECHO OFF &SETLOCAL
REM to escape the '%' use a second '%'
SET "var=40%% &off!.txt"
REM get a random temp file name
:loop
SET "tname=%temp%%random%%random%"
IF EXIST "%tname%" GOTO :loop
SETLOCAL ENABLEDELAYEDEXPANSION
REM save the variable in the file
>"%tname%" (ECHO(!var!)
CALL "second.bat" "%tname%"
ENDLOCAL
second.bat
#ECHO OFF &SETLOCAL
SET "tname=%~1"
<"%tname%" set/p"var="
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !var!
DEL "%tname%" /F /Q
..output is:
40% &off!.txt
EDIT: I added the enable/disable delayed expansion technique to avoid problems with exclamation-mark character.
#echo off
setlocal DisableDelayedExpansion
for /F "delims=" %%a in ('dir /B *.txt') do echo %%a
echo/
for %%a in (*.txt) do (
SET "var=%%a"
setlocal EnableDelayedExpansion
call :TEST_UPGRADE "!var:%%=%%%%!" "%%~a"
endlocal
)
goto :EOF
:TEST_UPGRADE
ECHO First: %1 Second: %2
exit /B
Output example:
% of STATS.txt
Discount of 10% in all.txt
Normal file.txt
First: "% of STATS.txt" Second: " of STATS.txt"
First: "Discount of 10% in all.txt" Second: "Discount of 10 in all.txt"
First: "Normal file.txt" Second: "Normal file.txt"

How to replace substrings in windows batch file

Can anyone tell me using batch file in windows ...how to read from a file and replace string=bath from file containing=bath Abath Bbath XYZbathABC with string hello so that the output is like hello Ahello Bhello XYZhelloABC
Expanding from Andriy M, and yes you can do this from a file, even one with multiple lines
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "INTEXTFILE=test.txt"
set "OUTTEXTFILE=test_out.txt"
set "SEARCHTEXT=bath"
set "REPLACETEXT=hello"
for /f "delims=" %%A in ('type "%INTEXTFILE%"') do (
set "string=%%A"
set "modified=!string:%SEARCHTEXT%=%REPLACETEXT%!"
echo !modified!>>"%OUTTEXTFILE%"
)
del "%INTEXTFILE%"
rename "%OUTTEXTFILE%" "%INTEXTFILE%"
endlocal
EDIT
Thanks David Nelson, I have updated the script so it doesn't have the hard coded values anymore.
SET string=bath Abath Bbath XYZbathABC
SET modified=%string:bath=hello%
ECHO %string%
ECHO %modified%
EDIT
Didn't see at first that you wanted the replacement to be preceded by reading the string from a file.
Well, with a batch file you don't have much facility of working on files. In this particular case, you'd have to read a line, perform the replacement, then output the modified line, and then... What then? If you need to replace all the ocurrences of 'bath' in all the file, then you'll have to use a loop:
#ECHO OFF
SETLOCAL DISABLEDELAYEDEXPANSION
FOR /F %%L IN (file.txt) DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !line:bath=hello!
ENDLOCAL
)
ENDLOCAL
You can add a redirection to a file:
ECHO !line:bath=hello!>>file2.txt
Or you can apply the redirection to the batch file. It must be a different file.
EDIT 2
Added proper toggling of delayed expansion for correct processing of some characters that have special meaning with batch script syntax, like !, ^ et al. (Thanks, jeb!)
To avoid blank line skipping (give readability in conf file) I combine aflat and jeb answer (here) to something like this:
#echo off
setlocal enabledelayedexpansion
set INTEXTFILE=test.txt
set OUTTEXTFILE=test_out.txt
set SEARCHTEXT=bath
set REPLACETEXT=hello
set OUTPUTLINE=
for /f "tokens=1,* delims=¶" %%A in ( '"findstr /n ^^ %INTEXTFILE%"') do (
SET string=%%A
for /f "delims=: tokens=1,*" %%a in ("!string!") do set "string=%%b"
if "!string!" == "" (
echo.>>%OUTTEXTFILE%
) else (
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
echo !modified! >> %OUTTEXTFILE%
)
)
del %INTEXTFILE%
rename %OUTTEXTFILE% %INTEXTFILE%
To avoid problems with the batch parser (e.g. exclamation point), look at Problem with search and replace batch file.
Following modification of aflat's script will include special characters like exclamation points.
#echo off
setlocal DisableDelayedExpansion
set INTEXTFILE=test.txt
set OUTTEXTFILE=test_out.txt
set SEARCHTEXT=bath
set REPLACETEXT=hello
set OUTPUTLINE=
for /f "tokens=1,* delims=¶" %%A in ( '"type %INTEXTFILE%"') do (
SET string=%%A
setlocal EnableDelayedExpansion
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
>> %OUTTEXTFILE% echo(!modified!
endlocal
)
del %INTEXTFILE%
rename %OUTTEXTFILE% %INTEXTFILE%
I have made a function for that, you only call it in a batch program within needing to code more.
The working is basically the same as the others, as it's the best way to do it.
Here's the link where I have that function
To avoid blank line skipping just replace this:
echo !modified! >> %OUTTEXTFILE%
with this:
echo.!modified! >> %OUTTEXTFILE%
If you have Ruby for Windows,
C:\>more file
bath Abath Bbath XYZbathABC
C:\>ruby -pne "$_.gsub!(/bath/,\"hello\")" file
hello Ahello Bhello XYZhelloABC

Resources