This question already has answers here:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
(8 answers)
Closed 3 years ago.
I ran across an issue using the SET command inside a FOR /F loop to echo the values of the loop parameters (e.g., %%g, %%h, etc.). I used the SET command because I wanted to see what values get assigned to each FOR /F loop parameter when the FOR /F loop iterates. I am specifically looking to see how many caret escape characters (^) are stripped by the CMD parser as it processes the line of test code that is the last element in the $code_test[00] array record:
#echo off
setlocal disabledelayedexpansion
set $code_test[00]=Record [00],2,green,blue,if not exist ^^!$m^^!^^!$n^^! (set /a $result+=3)
echo Value using ECHO command:
echo $code_test[00] = %$code_test[00]%
echo.
echo Value using SET command with Delayed Expansion DISABLED:
set $code_test[
echo.
setlocal enabledelayedexpansion
echo Value using SET command with Delayed Expansion ENABLED:
set $code_test[
echo.
for /f "tokens=1-10 delims=," %%g in ('set $code_test[') do (
echo For /f loop values using ECHO command ...
echo g = %%g
echo h = %%h
echo i = %%i
echo j = %%j
echo k = %%k
echo.
echo For /f loop values using SET command ...
set %%g
set %%h
set %%i
set %%j
set %%k
)
The code populates one record for the $code_test[xx] array with various elements, the last of which is a Windows CMD test code statement. I want to see the value of the FOR /F loop parameter that corresponds to this test code statement, which is %%k. So, I used both the echo and set commands to echo the value of %%k to see if %%k retained the caret (^) escape characters that were present in the original test code definition for $code_test[00]. The echo command worked as expected and stripped the caret escape characters (^) from the code. But the set command completely failed. I've read the post about how the CMD interpreter parses code here and I didn't see an answer to my question.
I realize that dealing with FOR /F loop parameters can be tricky. Should I just assume that I can't deal with FOR /F loop parameters directly (e.g., set %%k) and make it a policy to always assign the value of a FOR /F parameter to an environment variable then deal with the environment variable instead of the FOR /F parameter?
SET "blue62=108"
SET "%%%%k=hello"
ECHO after SET %%%%%%%% k errorlevel=!errorlevel!
set|FIND "k="
echo For /f loop values using SET command ...
ECHO ++++++++++++++++!errorlevel!
set %%g
set %%h
set %%i
set %%j
ECHO :::::::::::::::::::::!errorlevel!
SET if not exist !$m!!$n! (set /a $result+=3)
ECHO -----------!errorlevel!-------------------
ECHO :::::::::::::::::::::!errorlevel!
SET "if not exist !$m!!$n! (set /a $result+=3)"
ECHO -----------!errorlevel!-------------------
set %%k
)
I expanded your reporting to include the above code with these results:
For /f loop values using ECHO command ...
g = $code_test[00]=Record [00]
h = 2
i = green
j = blue
k = if not exist !$m!!$n! (set /a $result+=3)
after SET %%%% k errorlevel=0
For /f loop values using SET command ...
++++++++++++++++1
Environment variable 2 not defined
Environment variable green not defined
blue62=108
:::::::::::::::::::::1
-----------1-------------------
:::::::::::::::::::::1
-----------1-------------------
Environment variable %k not defined
I believe this demonstrates that set simply acts strangely if confronted with an argument that is not a simple space-absent string with optional = for assignment.
What was really curious was that the Environment variable %k not defined did not appear with the original code.
I believe you're really expecting a 1970's vintage design to not only be bullet-proof but act in a consistent manner when supplied with unanticipated data. Just treat it like your grandma. Very capable, but easily confused.
Related
The problem is echo !out! show all lines but echo %data% which assigned from !out! only show first line
#echo off
call :f
echo %data%
:f
setlocal EnableDelayedExpansion
set out=
set NL=^
for /f "delims=" %%i in ('dir /b') do (
if defined out set "out=!out!!NL!"
set "out=!out!%%i"
)
echo !out!
set data=!out!
exit /b 0
What is the correct way to pass the value entirely to other variable which i need to use freely after endlocal?
Thank you #aschipfl's for your answer that clarify that it's impossible to do it without end up in for-in loop again. Actually all that I want is to simplify my code without have to write for-in loop many times by turn it into a subroutine. I've solved it by passing the subroutine to the subroutine instead.
As derived from this thread, you cannot echo a multi-line string using immediate (%-)expansion, because everything after the first line-break is ignored.
To make your script working you need to correct two issues:
before the line endlocal & set data=%out% you must replace every new-line in variable out by an escaped new-line, that is the sequence ^ plus new-line plus new-line, which is exactly the same that you are using for defining the variable NL;
echo %data% truncates the displayed string at the first occurrence of a new-line in the value of variable data, so you need to use set data to show the actual content of the variable (or more precisely said, of all variables whose names begin with data);
Both of these items are commented (rem) in the following code:
#echo off
setlocal EnableDelayedExpansion
set out=
set NL=^
for /F "delims= eol=|" %%i in ('dir /b') do (
if defined out set "out=!out!!NL!"
set "out=!out!%%i"
)
echo * Original variable content:
set out
rem // Replace every new-line by an escaped new-line:
set out=!out:^%NL%%NL%=^^^%NL%%NL%^%NL%%NL%!
echo * Modified variable content:
set out
endlocal & set data=%out%
rem // Do not use `echo` to show true content of variable:
echo * Returned variable content:
set data
echo * Mistaken variable content:
echo %data%
exit /B 0
Although the variable value is now correctly passed over the endlocal barrier, this approach is not exactly brilliant, because it does not allow you to use variable %data% (again because everything after the first line-break is ignored as initially mentioned), unless you have got delayed expansion enabled in the hosting cmd instance, which would permit to use !data!.
Another remaining problem is that special characters in the multi-line string (like ^, &, (, ) and ", <, >, |) may cause syntax errors or other unexpected issues. However, this can be avoided by using a for meta-variable rather than a normal environment variable for passing the variable value beyond the endlocal barrier, because the former are expanded after special character recognition, in contrast to the latter, which are expanded before:
#echo off
setlocal EnableDelayedExpansion
set out=
set NL=^
for /F "delims=" %%i in ('dir /b') do (
if defined out set "out=!out!!NL!"
set "out=!out!%%i"
)
echo # Original variable content:
set out
rem /* Use a `for` meta-variable rather than a normal environment variable to
rem pass the variable value beyond the `endlocal` barrier;
rem a standard `for` loop can be used here, because there are not going to be
rem wildcards `?` and `*` in the variable value since they have already been
rem resolved by `dir`; `for /F` cannot be used here due to the new-lines: */
for %%j in ("!out!") do endlocal & set "data=%%~j"
rem // Do not use `echo` to show true content of variable:
echo # Returned variable content:
set data
echo # Mistaken variable content:
echo %data%
exit /B 0
The problem not being able to use variable %data% remains though.
To be able to use variable %data% with immediate expansion you could however simply store escaped new-lines rather than literal ones in the variable, because upon expansion you will have the intended literal new-line:
#echo off
setlocal EnableDelayedExpansion
set out=
set NL=^
for /F "delims=" %%i in ('dir /b') do (
if defined out set "out=!out!!NL!"
set "out=!out!%%i"
)
echo # Original variable content:
set out
rem // Replace every new-line by an escaped new-line:
set out=!out:^%NL%%NL%=^^^%NL%%NL%^%NL%%NL%!
echo # Modified variable content:
set out
rem /* Use a `for` meta-variable rather than a normal environment variable to
rem pass the variable value beyond the `endlocal` barrier;
rem a standard `for` loop can be used here, because there are not going to be
rem wildcards `?` and `*` in the variable value since they have already been
rem resolved by `dir`; `for /F` cannot be used here due to the new-lines: */
for %%j in ("!out!") do endlocal & set "data=%%~j"
rem // Do not use `echo` to show true content of variable:
echo # Actual variable content:
set data
echo # Parsed variable content:
echo %data%
exit /B 0
But regard that this is only going to work when %data% does not appear within quoted ("") strings.
This question already has answers here:
Make an environment variable survive ENDLOCAL
(8 answers)
Closed 3 years ago.
I'm trying to use a FOR loop to read the lines in a text file, but I also need to keep track of some variables and evaluate them. The easiest way to do that is by enabling DelyaedExpansion. Actually, it seems to be the ONLY way as everything else I've tried in relation to variables fails miserably if I don't use it. Unfortunately, this means that if any of the lines of text in the file contain exclamation points, they will be stripped out.
I thought I had found a solution by reading a line of text and putting it into a variable, THEN enabling DelayedExpansion, doing the variable operations, and finally using ENDLOCAL & SET VARIABLE=%VARIABLE% to preserve the value. Unfortunately that doesn't seem to work if the ENDLOCAL statement is inside a loop.
For example;
echo off
for /F "delims=" %%F in (test.txt) do (
set Line=%%F
setlocal enabledelayedexpansion
set /a Count=Count+1
echo !Count! - !Line!
endlocal & set Count=%Count%
)
echo Total: %Count%
Each time the loop repeats, the value of "Count" is reset to zero.
If I move the SETLOCAL command before the FOR command, it will strip any "!" from the text, which is unacceptable.
Please note: The example above is only a small part of a much larger script that does many things with the variables inside the loop. I have boiled the problem down to the bare minimum to make it easy to understand. I need to preserve "!" in text read from a file while also being able to perform multiple variable operations within each loop.
So I either need a way to read text from a file, one line at a time, with DeleyedExpansion enabled AND preserve any "!" in the text, or preserve the value of variables that are defined within the SETLOCAL/ENDLOCAL commands within a loop.
With Help from dbenham and his answer here, There is a Solution that exists for this Scenario.
The key, as Dave has Shown, is in Setting the variables PRIOR to using SetlocalEnableDelayedExpansion so that ! is preserved.
#echo off
Set "count=0"
For /F "delims=" %%F in (test.txt) do (
Call :LineParse "%%~F"
)
REM The Below Loop demonstrates Preservation of the Values
Setlocal EnableDelayedExpansion
For /L %%a in (1,1,!count!) DO (
ECHO(!line[%%a]!
)
Endlocal
pause
exit
:LineParse
Set /a count+=1
Set "Line[%count%]=%~1"
Setlocal EnableDelayedExpansion
ECHO(!Line[%count%]!
ECHO(Total: !count!
(
ENDLOCAL
)
GOTO :EOF
There are still a few characters that will not be parsed as desired with this Method, noted in test.txt:
test.txt
Safe Characters: ! > * & ` ' . ] [ ~ # # : , ; ~ } { ) ( / \ ? > < = - _ + $ |
problem Characters: ^ "" %%
problem examples:
line disappears " from single doublequote
but not "" from escaped doublequote
%% will not display unless escaped. % unescaped Percent Symbols will attempt to expand %
caret doubles ^ ^^ ^^^
Don't need to complicate...
Just replace:
echo/ to set /p
setlocal enabledelayedexpansion to cmd /v /c
#echo off
for /F "delims=" %%F in ('type test.txt')do set /a "Count+=1+0" && (
(echo/ & cmd /v/s/c "set/p "'=!Count! - %%F"<nul")>>".\newfile.txt")
cmd /v /c echo/ Total: !Count! && call set "Count="<nul && goto :EOF
USING BATCH/BASH On Windows:
I'm wondering how to search a text file (.txt) line by line as each line is its own string, look for the location directory (e.g, C:...) which is part of this string. and just take out that specific part and store in a variable.
This is to be done for each line though.
Any help would be appreciated.
TestRecID TestUserID 5 2017-04-20 TestTAtRec No 2.560 No C:\Test1\Test3 Tester
TestRecID TestUserID 5 2017-04-20 TestTAtRec No 2.560 No C:\Test2\Test2 Tester
TestRecID TestUserID 5 2017-04-20 TestTAtRec No 2.560 No C:\Test3\Test1 Tester
Presume that the above are each row in the text file.
At each space in the line that is where the different columns would have been in the DB.
The Expected Result would be to have:
C:\Test1\Test3 --> Variable 1
C:\Test2\Test2 --> Variable 2
C:\Test3\Test1 --> Variable n
Stored in some variable.
I really can't overstate enough how much of a newb I am with Batch/Bash for Windows
Follow Up Question:
for /f "tokens=9 delims= " %%a in (%findfile%) do echo %%a
I want to then store the %%a in a variable_name
I thought it would be a case of:
for /f "tokens=9 delims= " %%a in (%findfile%) do echo %%a
set variable_name = %%a
But it is not working??
Was Answered but I got given out to for asking a question in an answer,
The Answer given:
for /f "tokens=9 delims= " %%b in (%findfile%) do set "location=%%b"
EDIT
Working Loop for taking unique variables (Thanks to #Stephen):
REM Get Locations from textfile:
for /f "skip=1 tokens=9 delims= " %%a in (%findfile%) do set "_%%a=yes"
REM Translate into proper variables:
set count = 0
for /f "tokens=1* delims==#" %%a in ('set _') do (
set /a count+=1
set _location[%count%]=%%a
)
set _location
This is the FFMPEG function I'm using but I now need it to take in each variable separately:
for %%i in (%_location%\*.mp4) do (if not exist "%%~ni\" MD "%%~ni"
ffmpeg -i "%%i" -vframes 1 -f image2 -start_number 0 "%%~ni\%%~ni_Summary_%%3d.jpeg"
)
The %_location% part in the function above is where my issue is? of it not taking multiple variables at once
As it makes no sense to store the whole output into one variable, I assume, you want one variable for each of the lines.
#echo off
setlocal EnableDelayedExpansion
set n=0
for /f "tokens=9" %%a in (file.txt) do (
set /a n+=1
set _Var[!n!]=%%a
)
set _Var[
Note the parantheses after do. This is called a code block and is parsed and executed like a single command. (this is why you need delayed expansion).
EDIT to remove duplicates, you can either use a lot of code to compare the (maybe) new data with each of the already existing variables or use a strange trick: use the data as variable names (and some dummy string as value), so each duplicate will just "overwrite" an already existing variable and in a second step put the data (now variable names) as values into variables.
#echo off
SetLocal EnableDelayedExpansion
REM clear variables:
for /f "delims==" %%a in ('set _') do set "%%a="
REM get locations from textfile:
for /f "tokens=9" %%a in (file.txt) do set "_%%a=yes"
REM translate into proper variables:
set count=0
echo on
for /f "tokens=1* delims==#" %%a in ('set _') do (
set /a count+=1
set x=%%a
set _var[!count!]=!x:~1!
)
set _var
Note: your data shouldn't contain exclamation marks (!) due to delayed expansion.
need some quick help. This is a university program, everything is working fine except when I call my :forLoop method to iterate through 100 numbers (1,1,100) starting at 1 going by 1 til 100 and doing the iteration % 5 (i%%5). for some reason I cannot get this to work. appreciate any help or direction.
When I echo %%A it is iterating through all the number perfect. When I echo %result% I get a blank "" (nothing inside)
:forLoop
FOR /L %%A IN (1,1,100) DO (
set /A result=%%A %% 2
echo "%%A"
echo "%result%"
)
Correct code is
:forLoop
setlocal ENABLEDELAYEDEXPANSION
FOR /L %%A IN (1,1,100) DO (
set /A result=%%A %% 5
echo !result! >> results.txt
set /A total=!total!+!result!
echo !total!
)
The problem is that the %result% is substituted when the for is read, meaning that it is no longer a variable when the loop is executed. What you need is delayed variable expansion to be enabled and then use ! instead of %:
setlocal ENABLEDELAYEDEXPANSION
:forLoop
FOR /L %%A IN (1,1,100) DO (
set /A result=%%A %% 5
echo "%%A"
echo !result!
)
This is all explained in the help message you get when you run SET /?:
Delayed environment variable expansion is useful for getting around
the limitations of the current expansion which happens when a line
of text is read, not when it is executed. The following example
demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements
is substituted when the first IF statement is read, since it logically
includes the body of the IF, which is a compound statement. So the
IF inside the compound statement is really comparing "before" with
"after" which will never be equal. Similarly, the following example
will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory,
but instead will just set the LIST variable to the last file found.
Again, this is because the %LIST% is expanded just once when the
FOR statement is read, and at that time the LIST variable is empty.
So the actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different
character (the exclamation mark) to expand environment variables at
execution time. If delayed variable expansion is enabled, the above
examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
for BAT file when I write following script I don't get the name of the .jpg files in the folder. What's the error and how it can be achieved?
for /f "tokens=*" %%f in ('dir /b *.jpg') do (
SET newname=%%f
SET front=%newname:~0,6%
echo %front%
)
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
This is one option - but ! characters in the filenames will cause issues.
#echo off
setlocal enabledelayedexpansion
for /f "delims=" %%f in ('dir /b *.jpg') do (
SET "newname=%%f"
SET "front=!newname:~0,6!"
echo !front!
)
pause