How to output exclamation mark with EnabledDelayedExapnsion? - shell

I am working on editing an XML file, and approximately the first 10 lines are comments. And for xml comments are in the form
<!-- COMMENT HERE -->
But when using my code it does not output that ! mark, which screws up the comments in the xml. I understand that the ENABLEDELAYEDEXPANSION is doing this because it thinks the exclamation mark is expanding a variable. How could I get this to work?
Here is my code below
setlocal ENABLEDELAYEDEXPANSION
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line = !line!+1
if !line!==39 (echo REPLACED TEXT39>>%tempfile%
)else if(!line!==45 (echo REPLACED TEXT45>>%tempfile%
)else (echo %%a>>%tempfile%
))
EDIT1 Basically what it is supposed to do is output each line as it is, unless it is line 39 or 45. It works, except the ! marks in the comments don't get outputted and they are not comments anymore.
EDIT2
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
setlocal ENABLEDELAYEDEXPANSION
set /a !line! +=1
echo !line!
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
endlocal
)else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
endlocal
)else (
endlocal
setlocal DISABLEDELAYEDEXPANSION
echo %%a>>%tempfile%
endlocal
))
Here is the latest code I have been using. It works the best, but now the problem is that the variable "line" is not getting updated. I have a feeling that it is because of the "endlocal". The only problem is that I need the "endlocal" there otherwise I get an error
Maximum setlocal recursion level reached.
The problem is, I need to alternate between enableddelayedexpansion and disabledelayedexpansion so that my exclamation marks show up properly. But to do that I need to keep up with the "endlocal" calls which i think is messing up my line variable. Any thoughts?

You can't output the exclamation mark this way.
The exclamation mark is part of the content of %%a but while delayed expansion is enabled you can't access it, as it will be parsed after the %%a is epanded.
So you need to disable delayed expansion at all or temporary.
A sample for temporary disabling it
setlocal ENABLEDELAYEDEXPANSION
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line = !line!+1
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
) else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
) else (
setlocal DisableDelayedExpansion
echo %%a>>%tempfile%
endlocal
)
)
Or you don't use it at all, then you only need to get the if line=42 part working.
This uses the fact that modulo by 0, will produce an error (which is suppressed by 2>nul) and the variable stays unchanged, in this case they stay undefined.
setlocal DisableDelayedExpansion
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line+=1
set "notLine39="
set /a "notLine39=1%%(line-39)" 2>nul
set "notLine45="
set /a "notLine45=1%%(line-45)" 2>nul
if not defined line39 (
echo REPLACED TEXT39>>%tempfile%
) else if not defined line45 (
echo REPLACED TEXT45>>%tempfile%
) else (
setlocal DisableDelayedExpansion
echo %%a>>%tempfile%
endlocal
)
)
Edit: Added explanation to your changed question
This uses the toggling delayed expansion technic, described in SO: Batch files: How to read a file?
The trick is to be in disabledDelayedExpansion when transfering %%a to text, then switching to enabledDE and be able to use the extended syntax.
But don't forget the endlocal before the next loop starts.
setlocal DisableDelayedExpansion
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line+=1
set "text=%%a"
setlocal EnableDelayedExpansion
if !line!==39 (
echo REPLACED TEXT39>>%tempfile%
) else if !line!==45 (
echo REPLACED TEXT45>>%tempfile%
) else (
echo %%a>>%tempfile%
)
endlocal
)

Related

Batch String +=? [duplicate]

I made this code
dir /B /S %RepToRead% > %FileName%
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
echo %%a is working fine but echo %z% returns "echo disabled".
I need to set a %z% because I want to split the variable like %z:~7%
Any ideas?
There are two methods to setting and using variables within for loops and parentheses scope.
setlocal enabledelayedexpansion see setlocal /? for help. This only works on XP/2000 or newer versions of Windows.
then use !variable! instead of %variable% inside the loop...
Create a batch function using batch goto labels :Label.
Example:
for /F "tokens=*" %%a in ('type %FileName%') do call :Foo %%a
goto End
:Foo
set z=%1
echo %z%
echo %1
goto :eof
:End
Batch functions are very useful mechanism.
You probably want SETLOCAL ENABLEDELAYEDEXPANSION. See https://devblogs.microsoft.com/oldnewthing/20060823-00/?p=29993 for details.
Basically: Normal %variables% are expanded right aftercmd.exe reads the command. In your case the "command" is the whole
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo %z%
echo %%a
)
loop. At that point z has no value yet, so echo %z% turns into echo. Then the loop is executed and z is set, but its value isn't used anymore.
SETLOCAL ENABLEDELAYEDEXPANSION enables an additional syntax, !variable!. This also expands variables but it only does so right before each (sub-)command is executed.
SETLOCAL ENABLEDELAYEDEXPANSION
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
This gives you the current value of z each time the echo runs.
I struggeld for many hours on this.
This is my loop to register command line vars.
Example : Register.bat /param1:value1 /param2:value2
What is does, is loop all the commandline params,
and that set the variable with the proper name to the value.
After that, you can just use
set value=!param1!
set value2=!param2!
regardless the sequence the params are given. (so called named parameters).
Note the !<>!, instead of the %<>%.
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%P IN (%*) DO (
call :processParam %%P
)
goto:End
:processParam [%1 - param]
#echo "processparam : %1"
FOR /F "tokens=1,2 delims=:" %%G IN ("%1") DO (
#echo a,b %%G %%H
set nameWithSlash=%%G
set name=!nameWithSlash:~1!
#echo n=!name!
set value=%%H
set !name!=!value!
)
goto :eof
:End
Simple example of batch code using %var%, !var!, and %%.
In this example code, focus here is that we want to capture a start time using the built in variable TIME (using time because it always changes automatically):
Code:
#echo off
setlocal enabledelayedexpansion
SET "SERVICES_LIST=MMS ARSM MMS2"
SET START=%TIME%
SET "LAST_SERVICE="
for %%A in (%SERVICES_LIST%) do (
SET START=!TIME!
CALL :SOME_FUNCTION %%A
SET "LAST_SERVICE=%%A"
ping -n 5 127.0.0.1 > NUL
SET OTHER=!START!
if !OTHER! EQU !START! (
echo !OTHER! is equal to !START! as expected
) ELSE (
echo NOTHING
)
)
ECHO Last service run was %LAST_SERVICE%
:: Function declared like this
:SOME_FUNCTION
echo Running: %1
EXIT /B 0
Comments on code:
Use enabledelayedexpansion
The first three SET lines are typical
uses of the SET command, use this most of the time.
The next line is a for loop, must use %%A for iteration, then %%B if a loop inside it
etc.. You can not use long variable names.
To access a changed variable such as the time variable, you must use !! or set with !! (have enableddelayexpansion enabled).
When looping in for loop each iteration is accessed as the %%A variable.
The code in the for loop is point out the various ways to set a variable. Looking at 'SET OTHER=!START!', if you were to change to SET OTHER=%START% you will see why !! is needed. (hint: you will see NOTHING) output.
In short !! is more likely needed inside of loops, %var% in general, %% always a for loop.
Further reading
Use the following links to determine why in more detail:
Difference between %variable% and !variable! in batch file
Variable usage in batch file
To expand on the answer I came here to get a better understanding so I wrote this that can explain it and helped me too.
It has the setlocal DisableDelayedExpansion in there so you can locally set this as you wish between the setlocal EnableDelayedExpansion and it.
#echo off
title %~nx0
for /f "tokens=*" %%A in ("Some Thing") do (
setlocal EnableDelayedExpansion
set z=%%A
echo !z! Echoing the assigned variable in setlocal scope.
echo %%A Echoing the variable in local scope.
setlocal DisableDelayedExpansion
echo !z! &rem !z! Neither of these now work, which makes sense.
echo %z% &rem ECHO is off. Neither of these now work, which makes sense.
echo %%A Echoing the variable in its local scope, will always work.
)
set list = a1-2019 a3-2018 a4-2017
setlocal enabledelayedexpansion
set backup=
set bb1=
for /d %%d in (%list%) do (
set td=%%d
set x=!td!
set y=!td!
set y=!y:~-4!
if !y! gtr !bb1! (
set bb1=!y!
set backup=!x!
)
)
rem: backup will be 2019
echo %backup%
Try this:
setlocal EnableDelayedExpansion
...
for /F "tokens=*" %%a in ('type %FileName%') do (
set z=%%a
echo !z!
echo %%a
)
You can use a macro if you access a variable outside the scope
#echo off
::Define macro
set "sset=set"
for /l %%a in (1,1,4) do (
::set in loop
%sset% /a "x[%%a]=%%a*%%a"
if %%a equ 4 (
:: set in condition
%sset% "x[%%a]=x Condition"
%sset% "y=y Condition"
)
)
echo x1=%x[1]% x2=%x[2]% x3=%x[3]% x4=%x[4]% y=%y%
:: Bonus. enableDelayedExpansion used to access massive from the loop
setlocal enableDelayedExpansion
echo Echo from the loop
for /l %%a in (1,1,4) do (
::echo in one line - echo|set /p =
echo|set /p "=x%%a=!x[%%a]! "
if %%a equ 4 echo y=%y%
)
pause
I know this isn't what's asked but I benefited from this method, when trying to set a variable within a "loop". Uses an array. Alternative implementation option.
SETLOCAL ENABLEDELAYEDEXPANSION
...
set Services[0]=SERVICE1
set Services[1]=SERVICE2
set Services[2]=SERVICE3
set "i=0"
:ServicesLoop
if defined Services[%i%] (
set SERVICE=!Services[%i%]!
echo CurrentService: !SERVICE!
set /a "i+=1"
GOTO :ServicesLoop
)
The following should work:
setlocal EnableDelayedExpansion
for /F "tokens=*" %%a in ('type %FileName%') do (
set "z=%%a"
echo %z%
echo %%a
)

Edit text after dot with batch script

I want to remove three digits after dot in last 3 columns with batch file (windows). Note that dots can be present in other columns.
This is a sample of my data:
4216118,'0806010709','ljubičasti ','Hita kirška ambnta',1,'Eriiti (vk, kk)','X','Uđaj za heološke prege','Celyn1800 ','Hni Sak','Hemlogja','2016-06-08 11:42:05.040','2016-06-08 11:41:42.122','2016-06-08 11:49:49.370'
4216081,'0806010387','ljubičasti ','Oća doven.amb. - VANJA',1,'Erii (vk, kk)',,'Urj za heoške prage','Adia 120 R','Reni','Hlogija','2016-06-08 08:52:13.962','2016-06-08 08:51:57.067','2016-06-08 11:08:26.504'
4216667,'1506010909','ljčasti ','tna ambuta kke za invne bolesti',1,'Erciti (vk, kk)',,'Uj za hemloške prge','Cell-Dyn 1800 R','Hi','Hemagija','2016-06-15 21:24:14.646','2016-06-15 21:24:03.523','2016-06-15 21:26:58.871'
4213710,'0905010991','ljubičasti ','Hna kira amnta',1,'Eociti (vk, kk)','X','Uđaj za hemloške prage','Cel1800 ','Hi Sak','Hemlogja','2016-05-09 17:52:32.231','2016-05-09 17:52:26.319','2016-05-09 18:31:33.643'
Example:
Before:
'2016-06-08 11:49:49.370'
After:
'2016-06-08 11:49:49'
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q47642335.txt"
SET "outfile=%destdir%\outfile.txt"
(
FOR /f "delims=" %%a IN ('more %filename1%') DO (
SET "line=%%a"
ECHO !line:~0,-57!!line:~-53,-31!!line:~-27,-5!!line:~-1!
)
)>"%outfile%"
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q47642335.txt containing your data for my testing.
Produces the file defined as %outfile%
Sadly, cmd doesn't play nicely with unicode files, so there will be some modification to the data. Essentially, read each line and pick the line-portions to be concatenated, using - substringing values to select from the end of the line, which is of a consistent structure.
Try this, without any guaranties. Regarding your example, expect you want remove last three digits AND dot, unlike your describe to remove three digits AFTER dot.
#echo off & setlocal ENABLEDELAYEDEXPANSION > out.txt
for /f "tokens=1-15 delims=," %%a in (data.txt) do (
set "string="
call :process "%%a" "%%b" "%%c" "%%d" "%%e" "%%f" "%%g" "%%h" "%%i" "%%j" "%%k" "%%l" "%%m" "%%n" "%%o"
)
exit /B
:process
echo %~1| findstr /R /C:"[0-9]*-[0-9]*-[0-9]* [0-9]*:[0-9]*:[0-9]*\.[0-9][0-9][0-9]" >NUL
if not errorlevel 1 (
set "str=%~1"
set "str=!str:~0,-5!"
if defined string (set "string=!string!,!str!'") else (set "string='!str!'")
) else (
set "str=%~1"
if defined string (set "string=!string!,!str!") else (set "string='!str!'")
)
shift
if not "%~1"=="" (goto :process) else (echo !string!>>out.txt)
GOTO:EOF

Windows batch file to find duplicates in a tree

I need a batch file ( Windows CMD is the interpreter, a .bat ) to do this type of task:
1) Search through a folder and its subfolders
2) Find files with the same filename and extension ( aka duplicates )
3) Check if they have the same size
4) If same name + same size, echo all the files except the first one ( practically I need to delete all except one copy )
Thanks for any type of help
This is only an initial script, just for check the files, in a folder and its subfolders, and their size:
#Echo off
Setlocal EnableDelayedExpansion
Set Dir=C:\NewFolder
For /r "%Dir%" %%i in (*) do (
Set FileName=%%~nxi
Set FullPath=%%i
Set Size=%%~zi
Echo "!FullPath!" - SIZE: !Size!
)
Echo.
Pause
This script does what you ask. Just set the ROOT variable at the top to point to the root of your tree.
#echo off
setlocal disableDelayedExpansion
set root="c:\test"
set "prevTest=none"
set "prevFile=none"
for /f "tokens=1-3 delims=:" %%A in (
'"(for /r "%root%" %%F in (*) do #echo %%~znxF:%%~fF:)|sort"'
) do (
set "currTest=%%A"
set "currFile=%%B:%%C"
setlocal enableDelayedExpansion
if !currTest! equ !prevTest! echo "!currFile!"
endlocal
set "prevTest=%%A"
)
But you can make the test more precise by using FC to compare the contents of the files. Also, you can incorporate the DEL command directly in the script. The script below prints out the commands that would delete the duplicate files. Remove the ECHO before the DEL command when you are ready to actually delete the files.
#echo off
setlocal disableDelayedExpansion
set root="c:\test"
set "prevTest=none"
set "prevFile=none"
for /f "tokens=1-3 delims=:" %%A in (
'"(for /r "%root%" %%F in (*) do #echo %%~znxF:%%~fF:)|sort"'
) do (
set "currTest=%%A"
set "currFile=%%B:%%C"
setlocal enableDelayedExpansion
set "match="
if !currTest! equ !prevTest! fc /b "!prevFile!" "!currFile!" >nul && set match=1
if defined match (
echo del "!currFile!"
endlocal
) else (
endlocal
set "prevTest=%%A"
set "prevFile=%%B:%%C"
)
)
Both sets of code may seem overly complicated, but it is only because I have structured the code to be robust and avoid problems that can plague simple solutions. For example, ! in file names can cause problems with FOR variables if delayed expansion is enabled, and = in file name causes a problem with npocmoka's solution.
#echo off
setlocal
for /f "tokens=1 delims==" %%# in ('set _') do (
set "%%#="
)
for /r %%a in (*.*) do (
if not defined _%%~nxa%%~za (
set "_%%~nxa%%~za=%%~fa"
) else (
echo %%~fa
)
)
endlocal

Batch how to change a single line?

I have some code, that I am trying to use to take this file, and wait until it gets to line 39. And then at line 39 I want it to print something else. But i can't get it to ever get through that IF condition.
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line+=1
if !line!==39 (
echo REPLACED TEXT>>%tempfile%
) else (
echo %%a>>%tempfile%
) )
After the
set /a line+=1
I tried
ECHO line
to see what my line variable was after I set it to line+=1. I received back all 0s. Does anyone see why my code is not incrementing the line like its supposed to? But when it outputs the file, it outputs perfectly except that line 39 that does not change. It remains the same.
I think you're missing setlocal ENABLEDELAYEDEXPANSION and you need to use echo !line! if you want to output inside the block:
#echo off
setlocal ENABLEDELAYEDEXPANSION
set line=0
FOR /f "usebackqdelims=" %%a in ("%filename2%") do (
set /a line = !line!+1
ECHO !line!
if !line!==39 (
echo REPLACED TEXT>>%tempfile%
) else (
echo %%a>>%tempfile%
) )

Loop through CSV file with batch - line break issue

This is extension to another question (Loop through CSV file with batch - Space issue)
I have csv file content like this
name,sex,age,description,date
venu,m,16,test mesg,2012-05-01
test,f,22,"He is good guy
and
brilliant",2012-05-01
I am looping this file using this command.
For /F "usebackq tokens=1-3 delims=" %%x in (test.csv) Do (
But since there is line break in second row, I am getting 3 records even though there are two records in the file.
How to fix this? Thanks in advance.
The main problem seems to be to count the quotes in a line.
If the count of quotes is odd then you need to append the next line and count again the quotes.
Counting of characters in a string is a bit tricky, if you won't iterate through all charachters.
I used here the delayed reduction technic, each quote will be effectivly replaced by a +1 and all other characters are removed.
To begin and terminate the line in a proper way there is always one extra +1 at the beginning, which will be compensated by a -1 in front.
The main trick is to replace the complete text from one quote to the next with exactly one +1 by replacing each quote with !!#:#=.
This works as !#:#=...<some text>...! will always be expanded to +1, as the content of the variable # is +1 and so the search pattern # can't be found.
The other replacements are only necessary to avoid problems with exclamation marks and carets int the text.
:::::::::::::::::::::::::::::::::::::::::::
:CountQuotes <stringVar> <result>
setlocal EnableDelayedExpansion
set "line=!%~1!"
set "#=+1"
rem DelayedExpansion: double all quotes
set "line=!line:"=""!"
rem DelayedExpansion: remove all carets ^
set "line=!line:^=!"
rem PercentExpansion: Remove all !
set "line=%line:!=%"
rem PercentExpansion: Replace double quotes to !!#:#=
set "line=-1^!#:#=%line:""=^!^!#:#=%"
for /F "delims=" %%X in ("!line!") do (
set /a count=%%X!
)
(
endlocal
set %~2=%count%
exit /b
)
And the logic for appending lines and inserting linefeeds
#echo off
setlocal DisableDelayedExpansion
set "lastLine="
set LF=^
rem Two empty lines
for /F "delims=" %%A in (test.csv) do (
set "line=%%A"
setlocal EnableDelayedExpansion
set "line=!line:\=\x!"
if defined lastLine (
set "line=!lastLine!\n!line!"
)
call :CountQuotes line quoteCnt
set /a rest=quoteCnt %% 2
if !rest! == 0 (
for %%L in ("!LF!") DO set "line=!line:\n=%%~L!"
set "line=!line:\\=\!"
echo Complete Row: !Line!
echo(
set "lastLine="
) ELSE (
set "lastLine=!line!"
)
for /F "delims=" %%X in (""!lastLine!"") DO (
endlocal
set "lastLine=%%~X"
)
)
exit /b
:::::::::::::::::::::::::::::::::::::::::::
:CountQuotes <stringVar> <result>
setlocal EnableDelayedExpansion
set "line=!%~1!"
set "#=+1"
rem DelayedExpansion: double all quotes
set "line=!line:"=""!"
rem DelayedExpansion: remove all carets ^
set "line=!line:^=!"
rem PercentExpansion: Remove all !
set "line=%line:!=%"
rem PercentExpansion: Replace double quotes to !!#:#=
set "line=-1^!#:#=%line:""=^!^!#:#=%"
for /F "delims=" %%X in ("!line!") do (
set /a count=%%X!
)
(
endlocal
set %~2=%count%
exit /b
)
The Batch file below do what you want:
#echo Off
setlocal EnableDelayedExpansion
call :processFile < test.csv
goto :EOF
:processFile
set line=
set /P line=
if not defined line exit /b
set "line=!line:,,=,#,!"
for %%a in (name sex age description mydate) do set %%a=
for %%a in (!line!) do (
if not defined name (
set "name=%%a"
) else if not defined sex (
set "sex=%%a"
) else if not defined age (
set "age=%%a"
) else if not defined description (
set "description=%%a"
) else if not defined mydate (
set "mydate=%%a"
)
)
:checkDate
if defined mydate goto show
set /P line=
for /F "tokens=1* delims=," %%a in ("!line!") do (
set "description=!description! %%a"
set "mydate=%%b"
)
goto checkDate
:show
for %%a in (name sex age description mydate) do set /P "=%%a=!%%a!, " < NUL
echo/
goto processFile
I added the requirements you requested in your previous topic, that is, the sex may be empty (and is changed by # character as I explained in my answer to that topic), and the name may include commas. I tested the program with this data file:
name,sex,age,description,date
venu,m,16,"test mesg",2012-05-01
test,,22,"He is good guy
and
brilliant",2012-05-01
"venu,gopal",m,16,"Another
multi-line
description",2012-05-02
And get these results:
name=name, sex=sex, age=age, description=description, mydate=date,
name=venu, sex=m, age=16, description="test mesg", mydate=2012-05-01,
name=test, sex=#, age=22, description="He is good guy and brilliant", mydate=2012-05-01,
name="venu,gopal", sex=m, age=16, description="Another multi-line description", mydate=2012-05-02,
Note that any field that contain commas or spaces must be enclosed in quotes.

Resources