I wrote the following command line.
C:\Users\Administrator> echo TOMATO:50EA| (set /p z=& call set v1=APPLE:30EA %z%& for /F "tokens=1,2 delims= " %K in ('echo %v1%') do (echo Item 1: %K , Item 2: %L))
Result:
C:\Users\Administrator>(echo Item 1: APPLE:30EA , Item 2: TOMATO:50EA )
Item 1: APPLE:30EA , Item 2: TOMATO:50EA
Why has it also printed the echo command line?
The default behaviour in command line is to echo to console the command that will be executed. You can turn echo off or precede the commands you want to hide with #
echo TOMATO:50EA|(set /p z=& for /F "tokens=1,2 delims= " %K in ('echo APPLE:30EA %z%') do #echo Item 1: %K , Item 2: %L)
But this code (and your code) has a point of failure. It will only work while z has no value before starting to execute the command.
You can try to use delayed expansion
echo TOMATO:50EA|cmd /q /v /c"(set /p z=& for /F "tokens=1,2" %K in ("APPLE:30EA !z!") do echo Item 1: %K , Item 2: %L)"
and while the default configuration (with delayed expansion disabled) is used, then this code will solve the problem. But if you have delayed expansion enabled at command line and z has an initial value it will also fail.
Note:
The code in the question is written using %K. This means command line. Remember that in batch files the for replaceable parameters need to have the percent sign escaped (%%K).
Related
CMD is misinterpreting code on the false side of an if statement, resulting in a crash.
Here is some test code, which fails should the end user enter y or Y:
#Echo Off
Set "var="
Set "input="
:YorN
Set /P "input=Leave var empty? [Y(crash)|N]"
(Set input) 2>NUL | %SystemRoot%\System32\findstr.exe /I /L /X "input=Y input=N" 1>NUL
If ErrorLevel 1 GoTo YorN
If /I "%input%" == "n" Set "var=content1;content2;"
If Not "%var%" == "" (
For /F "Tokens=1,2 Delims=;" %%G In ("%var:~0,-1%") Do If Not "%%G" == "" Echo "%%G" "%%H"
) Else (
Echo As per your choosing, var is empty. Because of the if statement the "for" command didn't get interpreted and CMD didn't crash. You will not see this message.
)
Pause
Exit /B
This version however, with only one minor line break change works as intended.
#Echo Off
Set "var="
Set "input="
:YorN
Set /P "input=Leave var empty? [Y(crash)|N]"
(Set input) 2>NUL | %SystemRoot%\System32\findstr.exe /I /L /X "input=Y input=N" 1>NUL
If ErrorLevel 1 GoTo YorN
If /I "%input%" == "n" Set "var=content1;content2;"
If Not "%var%" == "" (
For /F "Tokens=1,2 Delims=;" %%G In ("%var:~0,-1%"
) Do If Not "%%G" == "" Echo "%%G" "%%H"
) Else (
Echo As per your choosing, var is empty. Because of the if statement the "for" command didn't get interpreted and CMD didn't crash. You will see this message.
)
Pause
Exit /B
Could somebody please explain to me what is causing this issue, or confirm that this is a bug in cmd.exe?
There is no bug, but the behavior is not obvious.
A minimal example shows the problem.
#echo off
set "var="
set "other=content"
echo First char of var is "%var:~0,1%" my other var=%other%
You get:
First char of var is "~0,1other
If you add any text to var it works as expected.
The variable var is undefined, not empty!
The problem is the expansion rule of undefined variables, the parser stops the variable expansion part, if it finds a double colon in an expression, but the variable is undefined.
In this case the parser ignores (and removes) the variable expansion after reading %var:.
But the the parser looks at the remaining line of ~0,1%" my other var=%other%.
It splits the line to
~0,1 -- Normal text
%" my other var=% -- This is a percent expansion of the variable with the name " my other var=, this variable is undefined, cmd.exe removes the complete part
other -- Normal text
% -- The trailing opening percent is removed, because there is no other percent sign
In your complicated example the line feed seems to solve the situation, because the part If Not "%%G" ... is on a separate line and the first percent of %%G is not used as the closing percent of the expression %") Do If Not "%.
In the failure case, it gets worse, because the closing parenthesis of the FOR block is removed and then the FOR command scans the rest of the file for a closing parenthesis and that ends in total rubbish.
For better understanding you could read:
SO: Percent Expansion Rules from #dbenham
What you have found (referring to revision 13 of your question) is something that I consider a bug (or at least a terrible design flaw) – but the problem is neither the if nor the for statement, it is the sub-string syntax:
If you follow the percent expansion rules very carefully, you may notice, that sub-string expansion (%VAR:~[integer][,[integer]]%) or sub-string substitution (%VAR:[*]search=[replace]%) behaves odd in case variable VAR is not defined. Here is an excerpt of that post with the most relevant sections highlighted:
Phase 1) Percent Expansion Starting from left, scan each character for % or <LF>. If found then
1.05 (truncate line at <LF>)
If the character is <LF> then
Drop (ignore) the remainder of the line from the <LF> onward
Goto Phase 2.0
Else the character must be %, so proceed to 1.1
1.1 (escape %) skipped if command line mode
[…]
1.2 (expand argument) skipped if command line mode
[…]
1.3 (expand variable)
[…]
Else if command extensions are enabled then[…]
If next character is % then[…]
Else if next character is : then
If VAR is undefined then
If batch mode thenRemove %VAR: and continue scan.
[…]
[…]
1.4 (strip %)
[…]
Applying this to your code portions, we can conclude the following:
First code portion:
If var is not defined, %var:~0,-1% becomes parsed to ~0,-1%, because of Remove %VAR: and continue scan in the above excerpt of Phase 1, leaving behind the remaining command line ~0,-1%") Do If Not "%%G" == "" Echo "%%G" "%%H", which is interpreted as:
the literal string ~0,1,
the (undefined) variable %") Do If Not "% (which becomes stripped),
the (undefined) variable %G" == "" Echo "% (which becomes stripped),
the (undefined) variable %G" "% (which becomes stripped),
and the remainder %H" (which the single %-sign becomes removed from),
constituting the command line For /F "Tokens=1,2 Delims=;" %G In ("~0,-1H" after Phase 1. The next line ) Else ( provides an expected closing parenthesis, but there is Do expected rather than Else. That is why the specific error message Else was unexpected at this time. arises.
Hence the %-sign in the fragment ~0,-1% is considered as an opening one for another (yet undefined) variable, impairing the whole remainder of the command line.
Second code portion:
If var is not defined, %var:~0,-1% also becomes parsed to ~0,-1%, because of Remove %VAR: and continue scan in the above excerpt of Phase 1, but leaving behind the remaining command line ~0,-1%", resulting in just the literal string ~0,-1" (with the %-sign stripped).
In this situation however, there is a line-break (<LF>) following, so Phase 1 is ended because of Goto Phase 2.0 and so, parsing newly begins with Phase 1 in the next line.
The key to all this is the statement and continue scan, meaning that from that point on, the next detected %-sign is recognised as an opening one.
How can I circumvent this bug?
To circumvent the described issue, you have got the following options:
To avoid command blocks in the problematic code section If Not "%var%" == "" ( … ) Else ( … ) by using the goto command and labels:
If Not Defined var GoTo UnDef
For /F "Tokens=1,2 Delims=;" %%G In ("%var:~0,-1%") Do If Not "%%G" == "" Echo "%%G" "%%H"
GoTo :Next
:UnDef
Echo As per your choosing, var is empty. Because of the "if" statement the "for" command didn't get interpreted and CMD didn't crash. You will see this message.
:Next
To utilise delayed variable expansion, which is applied to individual tokens rather than whole command lines, rendering them independent from each other; the section behind in in a for-loop constitutes such a token, according to the delayed expansion rules:
If Not "%var%" == "" (
SetLocal EnableDelayedExpansion
For /F "Tokens=1,2 Delims=;" %%G In ("!var:~0,-1!") Do EndLocal & If Not "%%G" == "" Echo "%%G" "%%H"
) Else (
Echo As per your choosing, var is empty. Because of the "if" statement the "for" command didn't get interpreted and CMD didn't crash. You will see this message.
)
Therein, delayed expansion is only active until parsing of the in clause because of the endlocal behind do (which is only executed once because there is only one loop iteration).
I want to run a command (in this example echo) for each line of a variable (in cmd, i.e. batch). In this case, the lines are supposed to be separated by \n, but others delimiters should work as well.
Therefore I set a variable:
> set var="foo\nbar"
I then want to run my command (echo) on each line, i.e. on "foo" and "bar". I tried to use for for this:
> for /f "tokens=* delims=\n" %s in (%var%) do (echo %s)
foo\nbar
Obviously this isn't what I wanted - I expected something like
foo
bar
How do I achieve this?
You can use a real line feed character to get your desired effect.
setlocal EnableDelayedExpansion
(set \n=^
%=empty, do not modify this line=%
)
set "var=foo!\n!bar"
for /f "delims=" %%L in ("!var!") do (
echo ## %%L
)
Other delimiters in the variable
can be used, but have to be replaced later with a linefeed.
setlocal EnableDelayedExpansion
(set \n=^
%=empty, do not modify this line=%
)
set "var=my,list,delimited,by,commas"
for %%L in ("!\n!") do (
for /f "delims=" %%k in ("!var:,=%%~L!") do (
echo ## %%k
)
)
About the delims option in FOR /F
The delims option is for splitting a line into tokens, that will not create more loop iterations - NEVER.
FOR /F "tokens=1,2,3 delims=;" %%A in ("123;456;789") do echo ... %%A, %%B, %%C
Output
... 123, 456, 789
not
... 123
... 456
... 789
%var% is used as the input set to FOR-parcer.
But before running FOR command we should replace \n with space (+outer quotes):
Syntax: %variable:StrToFind=NewStr%
Theese spaces become delimiters in our FOR-parser.
To remove quotes automatically from a a single letter variable use symbol ~
Syntax: %~1
So we have next code:
set var="foo\nbar"
for %%A IN (%var:\n=" "%) DO #ECHO=%%~A
P.S. As jeb truly said the code won't work correctly with special symbols: *, ?, %% in variable content.
Tool zur Imageverwaltung für die Bereitstellung
Version: 10.0.17763.1
Abbildversion: 10.0.17763.253
Funktionsliste:
Funktionsidentität : Browser.InternetExplorer~~~~0.0.11.0
Status : Nicht vorhanden
Funktionsidentität : Hello.Face.17658~~~~0.0.1.0
Status : Installiert
Funktionsidentität : Hello.Face.Migration.17658~~~~0.0.1.0
Status : Installiert
Funktionsidentität : Language.Basic~~~af-ZA~0.0.1.0
Status : Nicht vorhanden
I want to output the lines where the next line contains Status: Installiert.
I know how to find the lines containing the string Installiert, but don't know how to include the whole line before the match.
This command line written into a batch file can be used for this task:
#for /F "usebackq delims= eol=" %%I in ("TextFile.txt") do #for /F "tokens=2 eol= delims=: " %%J in ("%%~I") do #if "%%~J" == "Installiert" (call echo(%%Line%%) else set "Line=%%I"
The outer FOR interprets " as end of line character. So lines starting with " would be ignored by outer FOR.
The inner FOR interprets a space character as end of line character which does not matter here because the space character is also a delimiter. The line splitting is done first by FOR resulting in removing all spaces and colons from beginning of line and so space as end of line character is no problem here.
Thanks goes to aschipfl for these additional information on how the two FOR above with the specified options process the lines in specified text file.
Better would be:
#for /F usebackq^ delims^=^ eol^= %%I in ("TextFile.txt") do #for /F "tokens=2 delims=: eol=" %%J in ("%%~I") do #if "%%~J" == " Installiert" (call echo(%%Line%%) else set "Line=%%I"
The outer FOR is run with an empty list of string delimiters and no end of line character. The inner FOR is run also with no end of line character, but with just colon as string delimiter which is the reason for the space character at beginning of the string to compare.
Both command lines output on execution with file TextFile.txt in current directory containing the posted lines:
Funktionsidentität : Hello.Face.17658~~~~0.0.1.0
Funktionsidentität : Hello.Face.Migration.17658~~~~0.0.1.0
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
echo /?
for /?
if /?
set /?
I want create a batch file to find the total number of commas in the first line of text file.
Sample Text File
input.txt
12345,Bhavik
12323,Bhavik,Sanghvi
Output
1
I tried to surf net for this but couldnt find a solution, please help
Here's another simple solution to this question.
#echo off
setlocal enabledelayedexpansion
set LF=^
::Above 2 blank lines are critical - do not remove
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
set /p var=<input.txt
echo "%var:,="!cr!!lf!"..***..%">temp.file
find /c "..***.." <temp.file
del temp.file
#echo off
setlocal EnableDelayedExpansion
rem Read the first line
set /P "line=" < input.txt
rem Store it in a text file
> before.txt echo !line!
rem Store the line without commas in a second file
> after.txt echo !line:,=!
rem Get the difference in sizes between both files
set "diff="
for %%a in (before.txt after.txt) do (
if not defined diff (
set "diff=%%~Za"
) else (
set /A "diff-=%%~Za"
)
)
del before.txt after.txt
echo %diff%
If, rather than being hampered by the awful Windows BATCH tools, you install awk from the Unix tools for Windows here, you can do this:
awk -F, 'NR==1{print NF-1;exit}' input.txt
That says... "Run awk and use commas as the separator to divide fields. On line 1, print the number of fields on this line minus 1, then exit. Do that for file input.txt."
gawk is just a slightly different version of awk if you get that one in the Unix Utils package. You may need to replace the single quotes with double ones to accommodate Windows' lack of abilities.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q35826440.txt"
:: first method
SET /a count=0
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO SET "line=%%a"&GOTO got1
:got1
SET "line=%line:"=%"
IF NOT DEFINED line ECHO method 1: %count% found&GOTO method2
IF "%line:~-1%"=="," SET /a count+=1
SET "line=%line:~0,-1%"
GOTO got1
:: second method
:method2
SET /a count=-1
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO SET "line=%%a"&GOTO got2
:got2
SET "line=%line:"=%"
SET "line=%line:;=%"
SET "line=%line: =%"
SET "line=%line:,=x,x%"
FOR %%a IN (%line%) DO SET /a count+=1
ECHO method 2: %count% found
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q35826440.txt containing your data for my testing.
Two methods - both read the first line to line, then removes any " characters.
The first then mechanically loops, checking whether the last character is a comma, counting if it is and removing the last character until the string found is empty.
The second replaces all ; and Space characters (for good measure, Tab could be removed too) and then replacing commas with x,x.
The result is that the only separators left are commas, and there will be 1 more item in the list so formed than there are commas.
Hence, start the counter at -1 and increment for each element found in the list.
Next solution (similar to Magoo's second method) seems to treat even ˙cmd˙ and .bat poisonous characters supposed in input file:
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "infile=D:\bat\SO\files\35826440input.txt" change to suit your circumstances
set /A "commacount=-1"
for /F "usebackq delims=" %%G in ("%infile%") do (
set "line=%%G"
call :parseline
if /I not "%~1"=="/all" goto :continue
)
:continue
echo script continues here
ENDLOCAL
exit /B
:parseline
rem treat unbalanced doublequote in next line
set "lineToParse=%line:"=§%"
set "lineToParse=%lineToParse:,=","%"
set /A "commacount=-1"
for %%g in ("%lineToParse%") do (
set /A "commacount+=1"
rem echo %line%, !commacount!, %%g
)
echo %commacount% "%line%"
goto :eof
Output (with input file listing):
==> D:\bat\SO\35826440.bat
1 "12345,Bhavik"
script continues here
==> D:\bat\SO\35826440.bat /all
1 "12345,Bhavik"
2 "12323,Bhavik,Sanghvi"
3 "12323,Bhavik,Sanghvi,three"
0 "zero"
1 ",1 leading"
2 ",,2 leading"
1 "trailing,"
2 "2 trailing,,"
2 "2 middle,,mid"
4 "!OS!,!,!!,!!!,exclamations"
4 "%OS%,%,%%%,%%,percents"
8 "&,|,>,<,",",;,=,miscelaneous"
0 "unbalanced"doublequote"
script continues here
==> type D:\bat\SO\files\35826440input.txt
12345,Bhavik
12323,Bhavik,Sanghvi
12323,Bhavik,Sanghvi,three
zero
,1 leading
,,2 leading
trailing,
2 trailing,,
2 middle,,mid
!OS!,!,!!,!!!,exclamations
%OS%,%,%%%,%%,percents
&,|,>,<,",",;,=,miscelaneous
unbalanced"doublequote
==>
I have this block of code:
#ECHO OFF
SET "SRCFOLDER=C:\Users\MyUserName\Desktop\PhotoTests"
SET "TEMPCODE=Hi"
ECHO %TEMPCODE%
ECHO.
FOR /F "tokens=*" %%G IN ('DIR /B %SRCFOLDER%') DO (
ECHO %%G
CALL tooltipInfo.bat %%G 19 | FIND /C "Star" > test.txt
SET /P TEMPCODE=<test.txt
ECHO %TEMPCODE%
ECHO.
)
SET /P TEMPCODE=<test.txt
ECHO %TEMPCODE%
ECHO.
PAUSE
I'm confused by the output as I noticed that the variable in the FOR loop is not overwritten by what I think should be the content of "test.txt", which will vary each time the FOR loop runs.
For the purpose of this example, the file tooltipInfo.bat will echo a text string like "1 Star" or "3 Stars" based on the rating recorded in the file's properties. The FIND statement should result in a "0" or "1" being saved into the test.txt file.
The output is:
Hi
Canon_Locked.JPG
Hi
Nikon_Locked.JPG
Hi
0
Press any key to continue . . .
May I know why the TEMPCODE variable isn't being overwritten in the loop and retains the original value of "Hi". However in the final block of code, it was able to read and echo out the actual content of the file.
This is a common mistake when using FOR and parentheses. The problem is that by default every variable between ( and ) is evaluated when the line is first read, and then re-used for each iteration of the FOR.
If the variable changes during the loop (via SET), then you will need to change % to ! for variables inside the parentheses, and also turn on delayed expansion using setlocal
SETLOCAL enabledelayedexpansion
FOR /F "tokens=*" %%G IN ('DIR /B %SRCFOLDER%') DO (
ECHO %%G
CALL tooltipInfo.bat %%G 19 | FIND /C "Star" > test.txt
SET /P TEMPCODE=<test.txt
ECHO !TEMPCODE!
ECHO.
)