How to safely echo FOR variable %%~p followed by a string literal - batch-file

I have a variable %%p created from for /f command
When i try to use it with some additional references like: %%~dp and then write some text afterwards it accesses a different variable
set var="%%~dpabc.txt"
Code outputs
%%~dpa instead of %%~dp

So you must be using FOR /F with multiple tokens, like
for /f "tokens=1-16" %%a in (file) do echo %%~dpabc.txt
Or your code could have nested FOR loops. Something like
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dpabc.txt
)
)
Or even something like
for %%a in (something) do call :sub
exit /b
:sub
for %%p in (somethingelse) do echo %%~dpabc.txt
exit /b
All three code examples above will print out the drive and path of %%~dpa, followed by "bc.txt". As per the documentation, the FOR variables are global, so the DO clause of the subroutine FOR loop has access to both %%a and %%p.
Aschipfl does a good job documenting the rules for how modifiers and variable letters are parsed.
Whenever you use a FOR variable before a string literal, you must be extremely careful that the string literal cannot be interpreted as part of the FOR variable expansion. As can be seen with your example, this can be difficult. Make the literal dynamic, and the problem is even worse.
set /p "myFile=Enter a file name: "
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp%myFile%
)
)
If the user enters "abc.txt" then we are right back where we started. But looking at the code it is not obvious that you have a potential problem.
As Gerhard and Mofi say, you are safe if you use a character that cannot be interpreted as a modifier. But that is not always easy, especially if you are using FOR /F returning multiple tokens.
There are solutions!
1) Stop the FOR variable parsing with !! and delayed expansion
If you look at the rules for how cmd.exe parses scripts, you will see that FOR variables are expanded in phase 4 before delayed expansion occurs in phase 5. This provides the opportunity to use !! as a hard stop for the FOR expansion, provided that delayed expansion is enabled.
setlocal enableDelayedExpansion
for %%a in (something) do (
for %%p in (somethingelse) do (
echo %%~dp!!abc.txt
)
)
The %%~dp is expanded properly in phase 4, and then in phase 5 !! is expanded to nothing, yielding your desired result of the drive letter followed by "abc.txt".
But this does not solve all situations. It is possible for ! to be used as a FOR variable, but that should be easy to avoid except under extreme situations.
More troubling is the fact that delayed expansion must be enabled. This is not an issue here, but if the FOR variable expands to a string containing ! then that character will be parsed by delayed expansion, and the results will most likely be messed up.
So the !! delayed expansion hack is safe to use only if you know that your FOR variable value does not contain !.
2) Use intermediate environment variables
The only simple foolproof method to avoid problems in all situations is to transfer the value of the FOR variable to an intermediate environment variable, and then toggle delayed expansion and work with the entire desired string.
for %%a in (something) do (
for %%p in (somethingelse) do (
set "drive=%%~dp"
setlocal enableDelayedExpansion
echo !drive!abc.txt
endlocal
)
)
3) Use Unicode characters via environment variables
There is a complex bullet proof solution, but it takes a good bit of background information before you can understand how it works.
The cmd.exe command processor represents all strings internally as Unicode, as are environment variables - Any Unicode code point other than 0x00 can be used. This also applies to FOR variable characters. The sequence of FOR variable characters is based on the numeric value of the Unicode code point.
But cmd.exe code, either from a batch script, or else typed into the command prompt, is restricted to characters supported by the active code page. That might seem like a dead end - what good are Unicode characters if you cannot access them with your code?
Well there is a simple, though non-intuitive solution: cmd.exe can work with predefined environment variable values that contain Unicode values outside the active code page!
All FOR variable modifiers are ASCII characters that are within the first 128 Unicode code points. So if you define variables named $1 through $n to contain a contiguous range of Unicode characters starting with say code point 256 (0x100), then you are guaranteed that your FOR variable can never be confused with a modifier.
So if $1 contains code point 0x100, then you would refer to the FOR variable as %%%$1%. And you can freely use modifiers like `%%~dp%$1%.
This strategy has an added benefit in that it is relatively easy to keep track of FOR variables when parsing a range of tokens with something like "tokens=1-30" because the variable names are inherently sequential. The active code page character sequencing usually does not match the sequence of the Unicode code points, which makes it difficult to access all 30 tokens unless you use the Unicode variable hack.
Now defining the $n variables with Unicode code points is not a trivial development effort. Thankfully it has already been done :-) Below is some code that demonstrates how to define and use the $n variables.
#echo off
setlocal disableDelayedExpansion
call :defineForChars 1
for /f "tokens=1-16" %%%$1% in (file) do echo %%~d%$16%abc.txt
exit /b
:defineForChars Count
::
:: Defines variables to be used as FOR /F tokens, from $1 to $n, where n = Count*256
:: Also defines $max = Count*256.
:: No other variables are defined or tampered with.
::
:: Once defined, the variables are very useful for parsing lines with many tokens, as
:: the values are guaranteed to be contiguous within the FOR /F mapping scheme.
::
:: For example, you can use $1 as a FOR variable by using %%%$1%.
::
:: FOR /F "TOKENS=1-31" %%%$1% IN (....) DO ...
::
:: %%%$1% = token 1, %%%$2% = token 2, ... %%%$31% = token 31
::
:: This routine never uses SETLOCAL, and works regardless whether delayed expansion
:: is enabled or disabled.
::
:: Three temporary files are created and deleted in the %TEMP% folder, and the active
:: code page is temporarily set to 65001, and then restored to the starting value
:: before returning. Once defined, the $n variables can be used with any code page.
::
for /f "tokens=2 delims=:." %%P in ('chcp') do call :DefineForCharsInternal %1
exit /b
:defineForCharsInternal
set /a $max=%1*256
>"%temp%\forVariables.%~1.hex.txt" (
echo FF FE
for %%H in (
"0 1 2 3 4 5 6 7 8 9 A B C D E F"
) do for /l %%N in (1 1 %~1) do for %%A in (%%~H) do for %%B in (%%~H) do (
echo %%A%%B 0%%N 0D 00 0A 00
)
)
>nul certutil.exe -decodehex -f "%temp%\forVariables.%~1.hex.txt" "%temp%\forVariables.%~1.utf-16le.bom.txt"
>nul chcp 65001
>"%temp%\forVariables.%~1.utf8.txt" type "%temp%\forVariables.%~1.utf-16le.bom.txt"
<"%temp%\forVariables.%~1.utf8.txt" (for /l %%N in (1 1 %$max%) do set /p "$%%N=")
for %%. in (dummy) do >nul chcp %%P
del "%temp%\forVariables.%~1.*.txt"
exit /b
The :defineForChars routine was developed at DosTips as part of a larger group effort to easily access many tokens with a FOR /F statement.
The :defineForChars routine and variants are introduced in the following posts within that thread:
Work with a fixed number of columns
Work with varying numbers of columns
Dynamically choose which tokens to expand within the DO clause

This behaviour is caused by the kind of greedy nature of the parsing of for variable references and its ~-modifiers. Basically it follows these rules, given the preceding %/%%-signs have already been detected:
if Command Extensions are enabled (default), check if next character is ~; if yes, then:
take as many as possible of the following characters in the case-insensitive set fdpnxsatz (even multiple times each) that are preceding a character that defines a for variable reference or a $-sign; if such a $-sign is encountered, then:
scan for a :1; if found, then:
if there is a character other than % following the :, use it as a for variable reference and expand as expected, unless it is not defined, then do not expand;
if the : is the last character, cmd.exe will crash!
else (no : is found) do not expand anything;
else (if no $-sign is encountered) expand the for variable using all the modifiers;
else (if no ~ is found or Command Extensions are disabled) use the next character as a for variable reference, except it is a %-sign2, and expand, unless such is not defined, or there is not even a character following, then do not expand;
1) The string between $ and : is considered as the name of an environment variable, which may even be empty; since an environment variable cannot have an empty name, the behaviour is just the same as for an undefined environment variable.
2) A %-sign can be the name of a for meta-variable, but it cannot be expanded without a ~-modifier.
This answer has meanwhile been posted in an augmented manner as a community answer to the thread How does the Windows Command Interpreter (CMD.EXE) parse scripts?.

As already explained in the for meta-variable parsing rules, the ~-modifier detection happens in a greedy manner. But you can stop parsing by another for meta-variable, which eventually expands to nothing, or by the ~$-modifier as suggested by jeb in a comment, which does not even require another for meta-variable, so any existing one can be used:
rem // Using `%%~#` will expand to an empty string (note that `#` is not a valid `~`-modifier):
for %%# in ("") do (
rem // Establish a `for`-loop that defines meta-variables `%%a` to `%%p`:
for /F "tokens=1-16" %%a in ("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16") do (
rem /* Since `d`, `p` and `a` are all valid `~`-modifiers and a `for` meta-variable
rem `%%b` exists while `b` is not a valid `~`-modifier, `%%~dpab` is expanded: */
echo(%%~dpabc.txt
rem /* `for` meta-variable parsing is stopped after `%%~dp`, because the following `%`
rem is not a valid `~`-modifier, and neither exists a `for` meta-variable named `%`;
rem `%%~#` is expanded to an empty sting then (parsing surely stops at `#`): */
echo(%%~dp%%~#abc.txt
rem /* The following does not even require a particular `for` meta-variable like `%%#`,
rem it just uses the existing one `%%p` with the `~$`-modifier that specifies an
rem environment variable; since there is no variable name in between `$` and `:`,
rem there is certainly no such variable (since they must have names), hence `$~%:p`
rem expands to an empty string; note that `~$` is always the very last modifier: */
echo(%%~dp%%~$:pabc.txt
)
)
Note that this approach fails in case there is a for meta-variable named % (which is not quite common but possible).

Related

Prepare a string in a block of parenthesis variable expansion is disabled to return it to a calling scope that enables expansion (no call allowed)

i have a request for theorical knowledge purpose only in Windows Batch scripting.
Today my question is about preparing the value of a local string loc_str INSIDE A BLOCK OF PARENTHESIS and WITHOUT ENABLING DELAYED EXPANSION, in order to return it to a local scope that enables variable expasion (or eventually to prepare it for an incoming local for loop with in("%loc_str%") if nasty characters in loc_str have been properly escaped before).
We assume that loc_str contains at least one ^ and one ! and do not contain double quotes. Lets consider the following code:
#echo off
setlocal disabledelayedexpansion
set "flag_dde_prev=%flag_dde%" & set "flag_dde=!"
(
set "loc_str=Hello^^^ planet!!!! ^^Earth^"
if not defined flag_dde_prev (
call set "out_str=%%loc_str:^=^^%%"
set out_str
call set "out_str=%%out_str:^=^^%%"
set out_str
)
)
endlocal & set "str=%out_str%"
set str
As you know the local flags flag_{dde, dde_prev} are used to test the type of the calling and current scopes. Typically they're both defined at the beginning of a block setlocal..endlocal. The flag_dde is equal to ! if the current scope disables variable expansion, or is undefined
if the current scope enables it. The local value of flag_dde_prev is the inherited value of flag_dde in the calling scope.
What we must do here is to escape all ^ and ! to prepare the return of out_str with %..% to the calling scope when this latter enable variable expansion (ie when flag_dde_prev is defined). Two substitutions ^=^^ then !=^! with a simple set would be enough, but being inside a block (..) forces us to use the call set statement. Unfortunatly the caret ^ is not replaced in the same way they are in a scope that enables variable expansion.
Precisely, the first substitution ^=^^ doubles even sequences of carets, but does not double odd ones (it doubles them minus one).
Then, the second substitution !=^! replace each ! with ^^!.
To sump up,
loc_str=Hello^^^ planet!!!! ^^Earth^ ;init
out_str=Hello^^^^^ planet!!!! ^^^^Earth^ ;1st substitution ^=^^
out_str=Hello^^^^^ planet^^!^^!^^!^^! ^^^^Earth^ :2nd substitution !=^!
^=^^ seems to be reversible with ^^=^ if done BEFORE !=^! only.
If ^^=^ is done after !=^! then sequences of ^ not preceeding ! are modified only.
If ^^=^ is done after !=^! i didn't find a way to replace the sequences of ^^ before each ".
It behaves like a sequence ^^ before ! is the atomic unreplacable one, longer caret sequences can be replaced but i couldn't obtain ^! whatever i tried.
The same problem is quite easy to solve when variable expansion is enabled, even with nasty strings containing quotes by replacing "" by " at first (jeb already talked about this in another thread). For example if locs_tr doesn't contain quotes, the substitutions would be the following:
set "out_str=!loc_str:^=^^^^!" ;1st substitution, multiplicates the number of ^ by 4 as expected
call set "out_str=%%out_str:^!=^^^!%%" ! ;2nd substitution, replaces each ! by ^^!
set "out_str=!out_str:^^=^!" ;3rd substitution required, replaces each ^^ by ^ to divide by 2 the total number of ^
So my question is simple:
Is there a way using substitution IN THE EXACT SAME CONTEXT (ie no call) to obtain the desired prepared output value for out_str, so it can be returned safely to a scope that enables delayed expansion ?
The prepared output value should double the number of ^ and add ^ before each ! ie:
out_str=Hello^^^^^^ planet^!^!^!^! ^^^^Earth^^
Note that's my question is for theorical knowledge purpose ONLY. Indeed calling a label to do the two substitutions with a simple set is the reasonable way to proceed.
Your assumptions are wrong!
What we must do here is to escape all ^ and ! to prepare the return ...
Two substitutions ^=^^ then !=^! with a simple set would be enough ...
The problem is here the fact that caret folding is different if there is an exclamation mark in the line or not!
setlocal EnableDelayedExpansion
echo "one caret^"
echo "no caret^ but a bang^!"
echo "caret ^^ and a bang^!"
echo one caret ^^
echo no caret^^ but a bang^^!
echo caret ^^^^ and a bang^^!
call and carets and exclamation marks are really funny.
Because call always double all carets before any other rule, but you don't see it with quotes, because in the next parser phase the doubled carets are folded to single carets again.
Parse flow for a single line like
Starting with:
call echo my caret^^^^ "caret^^" and a bang !
1. Caret escaping outside quotes
call echo my caret^^ "caret^^" and a bang !
2. delayed expansion caret escaping (outside and **inside** quotes), the bang itself is lost here
call echo my caret^ "caret^" and a bang
3. The `CALL` caret doubling
call echo my caret^^ "caret^^" and a bang
4. Caret escaping outside quotes
call echo my caret^ "caret^^" and a bang
The problem itself was solved by dbenham and me a few years ago.
See SO: Macro to preserve variables when leaving setlocal scope
or
SO: Make an environment variable survive ENDLOCAL.
In your case it's much simpler, because you don't support quotes nor line feeds in the variable.
And you know, that you leave the current scope and will return into a scope with delayed expansion enabled, this simplifies the problem, too.
#echo off
setlocal EnableDelayedExpansion
setlocal disabledelayedexpansion
(
set "loc_str=Hello^^^ planet!!!! ^^Earth^"
set "loc_str"
setlocal EnableDelayedExpansion
set "out_str=!loc_str:^=^^^^!" # 1st substitution, multiply all carets by four
set "out_str"
call set "out_str=%%out_str:^!=^^^!%%" ! # 2nd substitution, replace all exclamation marks to two carets with exclam
set "out_str"
set "out_str=!out_str:^^=^!" # 3rd substitution reduce all carets by factor of two
set "out_str"
for /F "delims=" %%V in ("!out_str!") do (
endlocal
endlocal
set "str=%%V" ! # The trailing exclamation mark is intentionally, but will not part of the result
)
)
echo str = '!str!'
Ok jeb i just executed your macro. The code below can be copied and run. If i understood properly, the general principle is the following (correct me if i didn't).
#echo off
setlocal enableextensions disabledelayedexpansion
set "_(echo=echo: & echo"
set "_(set=echo: & set"
:: Initialize the DDE flags of the current/previous scopes.
set "flag_dde_prev=" & set "flag_dde=!"
:: Base string ; one of your nasty strings defined in another post ;p
set "str_base=caret ^ bang ! and some (other ) <>&| %% \"
setlocal enabledelayedexpansion
set "flag_dde_prev=!flag_dde!" & set "flag_dde=!"
setlocal enabledelayedexpansion
set "flag_dde_prev=!flag_dde!" & set "flag_dde=!"
:: Nasty local string to be returned ; still one of yours !
set "loc_str_nasty=!str_base! and also quoted stuff "!str_base!""
set loc_str & echo:
if defined flag_dde_prev (
set "out_str=!loc_str_nasty!"
) else (
rem STEP1: Each " is replaced by ""q so everything is consider outside quotes
set "out_str=!loc_str_nasty:"=""q!"
set out_str
rem STEP2: Each ^ is replaced with ^^
set "out_str=!out_str:^=^^!"
set out_str
rem STEP3: Each ! is replaced by ""e!
rem WARN A simple `set` must be avoided because of special characters.
call set "out_str=%%out_str:^!=""e^!%%"
set out_str
rem STEP4/5: Initial " and ^! were marked differently with letters q and e so we can restore them by replacing ""q and ""e! by " and ! respectively
set "out_str=!out_str:""e=^!"
set out_str
set "out_str=!out_str:""q="!"
set out_str
)
rem Return the value of out_str to the calling DDE/EDE scope by using a safe `for`.
for /F "delims=" %%# in ("!out_str!") do endlocal & set "str_pro=%%#"
:: Echo safely the protected string str_pro in the current DDE/EDE scope
%_(set% str_pro
if defined flag_dde (
for /F "tokens=1* delims==" %%v in ('set str_pro') do echo %%str_pro%%=%%w
) else (
%_(echo% ^^^!str_pro^^^!=!str_pro!
)
endlocal
Thanks for your remarks, indeed i talked too fast there is no problem at all with ""q and ""e in the input value of loc_str. You're very right about avoiding special characters with rem, i totally forgot. I did typing mistake also by assigning flag_dde_prev, it must be done with !..! inside an EDE scope of course. I struggle with the code editor here, i'm not used to such stuffs for now (yes i know i sound like a cavern man lol).
I edited my previous post, the protection block (..) is skipped when returning to a DDE scope (i forgot also to assign out_str when flag_dde_prev when defined to !). The returned protected string to DDE contains nasty characters so can't be echoed with %..%, so i used a for to echo it.
Is there a way to deal with " and &|<> to protect a such nasty loc_str to echo it in the calling DDE scope with a brutal echo %..% or call set %..%, or at the contrary is an undirect echoing can't be avoided ?
By undirect i mean without a slow echo only ie with a for like above, or with a very fast set str_pro only by deleting the leading string "str_pro=" by adding a dynamic leading string of backspace in the out_str value.
OUTPUT
Run twice, first when the calling scope is DDE, second when it's EDE (written as EDE in the code above).

Difference between !var! and %var% in batch files [duplicate]

for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=a"') do ( set usemenu=a )
for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=b"') do ( set usemenu=b )
for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=c"') do ( set usemenu=c )
Right, in this code (which may not work, that what i'm trying to find out) we have this "%%a" in that 'for' command.
First, whats the difference between %variable% and %%a?
Second, can someone explain the 'for' command to me? I have Google'd it way too much and all the explanations seem way to complicated...
What I am trying to do is pull a variable from options.txt, so i can change the menu style of my game launcher. there are 3 styles (a, b and c), so if the options.txt reads "menu=a" how can i get it to set a variable like %usemenu% to the value of a?
Thanks for any help in advance!
%variable% are environment variables. They are set with set and can be accessed with %foo% or !foo! (with delayed expansion if enabled). %%a are special variables created by the for command to represent the current loop item or a token of a current line.
for is probably about the most complicated and powerful part of batch files. If you need loop, then in most cases for has you covered. help for has a summary.
You can
iterate over files: for %x in (*.txt) do ...
repeat something n times: for /l %x in (1, 1, 15) do... (the arguments are start, step and end)
iterate over a set of values: for %x in (a, b, c) do ...
iterate over the lines of a file: for /f %x in (foo.txt) do ...
tokenize lines of a file: for /f "tokens=2,3* delims=," %x in (foo.txt) do ...
iterate over the output of a command: for /f %x in ('somecommand.exe') do ...
That's just a short overview. It gets more complex but please read the help for that.
Variables of the form %%a (or %a if for is used outside of batch files) are very similar to arguments to batch files and subroutines (%1, %2, ...). Some kinds of expansions can be applied to them, for example to get just the file name and extension if the variable represents a file name with path you can use %%~nxa. A complete overview of those is given in help for.
On the other hand, environment variables have other kinds of special things. You can perform replacements in them via %foo:a=b% would result in %foo% except that every a is replaced by a b. Also you can take substrings: %foo:~4,2%. Descriptions of those things can be found in help set.
As to why %variables% and %%a are different things that's a bit hard to answer and probably just a historical oddity. As outlined above there is a third kind of variable, %1, etc. which are very similar to those used in for and have existed for longer, I guess. Since environment variables are a bit unwieldy to use in for due to blocks and thus heavy reliance on delayed expansion the decision probably was made to use the same mechanisms as for arguments instead of environment variables.
Also environment variables could be more expensive, given that the process has a special “environment” block of memory where they are stored in variable=value␀ pairs, so updating environment variables involves potentially copying around a bit of memory while the other kind of variables could be more lightweight. This is speculation, though.
As for your problem, you don't really need for here:
find /v ":" "%appdata%\gamelauncher\options.txt" | find "menu=a" && set usemenu=a
This will only run the set if the preceding command was successful, i.e. menu=a was found. This should be considerably easier than for. From what I read you're trying to look whether menu=a exists in a line that does not contain a colon and in that case usemenu should be set to a, right? (And likewise for b and c. You could try coaxing for into doing that by looping over the lines of the file or output and tokenizing appropriately to figure out the value of menu but depending on the format of the lines this can be tricky. If what you have there works in theory then you should simply stick to that. You can however use a loop around it to avoid having to repeat the same line three times for a, b and c:
for %%X in (a b c) do (
find /v ":" "%appdata%\gamelauncher\options.txt" | find "menu=%%X" && set usemenu=%%X
)
If the file you are parsing is simple, however, with just name=value pairs in each line where : foo would be a comment, then you could use for as well:
for /f "tokens=1,* eol=: delims==" %%A in (%appdata%\gamelauncher\options.txt) do (
if "%%A"=="menu" set usemenu=%%B
)
But that depends a little on the exact format of the file. Above snippet would now read the file line by line and for each line would discard everything after a colon (the eol=: option), use the equals sign as a token delimiter and capture two tokens: The part before the first = and everything after it. The tokens are named starting with %%A so the second one is implicitly %%B (again, this is explained in help for). Now, for each line we examine the first token and look whether it's menu and if so, assign its value to the usemenu variable. If you have a lot of possible options to support this is certainly easier to maintain :-).

What's difference in the batch command?

#echo off
cd %~dp0
md .\newfolder
for /f "usebackq delims=" %%f in ("list.txt") do (
call set /a add=%%add%%+1
call set addx=0000%%add%%
call set addx=%%addx:~-3%%
call copy "%%f" ".\newfolder\%%addx%%_%%f"
)
pause
I made simple namechange code. I usually use command without 'call' but here it makes error message . why is that? .. and when i use %variable% not %%variable%% , It doesn't work well..
plz tell me why it happens.. and last question.. environment variable's value is stored until exit cmd . I want to know how i can unset that.. thank you..
All code within a parenthesized block is parsed in one pass. Normal variable expansion using percents occurs at parse time. So if you set a variable within a block, you cannot access the value using normal expansion because the value will be the value that existed before you entered the block.
You have the above situation. There are two classic ways to resolve the problem.
1) You can use CALL and double the percents as you have done. The CALL solves the problem because normal expansion occurs twice for a called line - once for the entire block, and again before the line is executed, but after previous lines in the block have executed. The first expansion converts the double percents to single percents, and the second expansion actually expands the variable.
I do not like this solution because it is slow, and also because the CALL causes problems with quoted ^ characters - they are doubled.
You can use multiple CALLs on the same command. Each Call requires the percents to be doubled. So one CALL requires 2 percents, two CALLs requires 4 perecents, three CALLs 8 percents, etc.
2) I think the preferred solution is to use delayed expansion. It is much faster, and also you never have to worry about escaping or quoting special characters like &, |, >, < etc. when you used delayed expansion. Delayed expansion does just what it says - the variable is not expanded until just before the line is executed. Delayed expansion must be enabled before it can be used. Within a batch file you can use setlocal enableDelayedExpansion.
The one problem that can occur with delayed expansion is FOR variables are corrupted if they contain ! and delayed expansion is enabled when they are expanded. That can usually be solved by toggling delayed expansion on and off within the loop.
If you type HELP SET from the command prompt, you will get a pretty good description of the problem with expanding variables within a block of code, and how delayed expansion can help. The description starts about half way down with the words Finally, support for delayed environment variable expansion....
Note - you do not need to expand variables when used within a SET /A computation. SET /A will automatically expand the value at execution time. Undefined variables are treated as zero.
In your code, you can simply use set /a add=add+1
But there is an even simpler shorthand way - you can use the += operator: set /a add+=1.
Here is another way your code could be written without using CALL. The code is untested, but I think I got it right.
#echo off
setlocal disableDelayedExpansion
cd "%~dp0"
md newfolder
set add=0
for /f "usebackq eol=: delims=" %%F in ("list.txt") do (
set /a add+=1
set "file=%%F"
setlocal enableDelayedExpansion
set "addx=00!add!"
copy "!file!" "newfolder\!addx:~-3!_!file!"
endlocal
)
pause
I explicitly initialize add to 0 because it might already be set to a value. If you know that it is undefined or already set to 0, then the initialization is not needed.
Your FOR loop is dealing with file names, and ! is valid within file names. That is the reason I toggle delayed expansion on and off within the loop - I don't want file names with ! to be corrupted when I expand %%F. File names can also start with ; (though highly unlikely). If it does, then FOR will skip that file because the default EOL character is ;. A file can never start with :, so I like to set EOL to : instead.
I put SETLOCAL near the top so that the environment variable definitions do not persist after the batch file completes.

How to find the value of a variable in batch without knowing the name of the variable

I'm making a simple batch game, and I'm trying to find out how to use the value of a variable with an unknown name. Example:
I dont know the name or the value of the var1 variable, because it is automatically generated at an earlier point in the game.
The only thing I know is the 3rd line: set var3=var2.
I need my game to use the value of "var1" in an if statement like shown in the last line.
How would I do this?
set var1=5
set var2=var1
set var3=var2
if ??? EQU something something...
It took a few edits, but I finally came up with a truly bullet proof option (method 5).
In method 5, if var3 is replaced by a variable name that contains " then you will have to escape some quotes. But if you are sophisticated enough to put a quote in a variable name then you already know how to do that :-)
#echo off
setlocal
set var1=5
set var2=var1
set var3=var2
::method 1 - Without using delayed expansion. (this is relatively slow and unsafe)
:: the initial expansion uses 1 % and no CALL
:: the 2nd expansion uses CALL and 2 %%
:: the 3rd expansion uses CALL and 4 %%%%
:: each additional expansion would require another CALL and doubled percents
call call set "test=%%%%%%%var3%%%%%%%
if "%test%"=="5" echo Method 1 works: Value of "unknown var"=%test%
::The remaining methods use delayed expansion. They are safer and faster.
setlocal enableDelayedExpansion
::method 2 - Using normal and delayed expansion and a temp variable.
:: Almost always works, but can fail in rare cases where variable
:: name contains "
set "test=!%var3%!"
if "!%test%!"=="5" echo Method 2 works: Value of "unknown var"=!%test%!
::method 3 - Using normal and delayed expansion without using any temp variable.
:: This works for sensible variable names, but fails if name contains
:: * or ?. Also can fail if variable name contains "
for %%A in ("!%var3%!") do if "!%%~A!"=="5" echo Method 3 works: Value of "unknown var"=!%%~A!
::method 4 - Using normal and delayed expansion without using any temp variable.
:: This almost always works, but can fail in rare cases where variable
:: name contains "
for /f "eol==delims=" %%A in ("!%var3%!") do if "!%%A!"=="5" echo Method 4 works: Value of "unknown var"=!%%A!
::method 5 - Using delayed expansion without using any temp variable.
:: This always works!
for /f "eol==delims=" %%A in ("!var3!") do for /f "eol==delims=" %%B in ("!%%A!") do if "!%%B!"=="5" echo Method 5 works: Value of "unknown var"=!%%B!

Dos Batch - For Loop

can anyone
tell me why in the example below the value of LIST is always blank
i would also like to only retrive the first 4 characters of %%i in variable LIST
cd E:\Department\Finance\edi\Japan_orders\
FOR /f %%i IN ('dir /b *.*') DO (
copy %%i E:\Department\Finance\Edi\commsfile.txt
set LIST=%%i
echo %LIST%
if %%i == FORD110509 CALL E:\Department\Finance\edi\EXTRACT.exe E:\Department\Finance\edi\COMMSFILE.TXT
)
pause
thanks in advance
You need delayed expansion. Add the following at the start of your program:
setlocal enabledelayedexpansion
and then use !LIST! instead of %LIST% inside of the loop.
For a thorough explanation please read help set.
Bracketed command blocks are parsed entirely, and it is done prior to their execution. Your %LIST% expression, therefore, is expanded at the beginning, while the LIST variable is still empty. When the time comes to execute echo %LIST%, there's not %LIST% any more there, only the empty string (read: 'nothing') instead. It's just how it works (don't ask me why).
In such cases the delayed expansion mechanism is used, and Joey has already told you that you need to use a special syntax of !LIST! instead of %LIST%, which must first be enabled (typically, by the command SETLOCAL EnableDelayedExpansion, which he has mentioned as well).
On your other point, you can extract a substring from a value, but the value must first be stored into a variable. Basically, the syntax for extracting substrings is one these:
%VARIABLE:~offset,charcount%
%VARIABLE:~offset%
That is, you are to specify the starting position (0-based) and, optionally, the number of characters to cut from the value. (If quantity is omitted then you are simply cutting the source string at the offset to the end.) You can read more about it by issuing HELP SET from the command line (wait, it's the same command that Joey has mentioned!).
One more thing: don't forget about the delayed expansion. You need to change the above % syntax to the ! one. In your case the correct expression for retrieving the first 4 characters would be:
!LIST:~0,4!
You can use it directly or you could first store it back to LIST and then use simply !LIST! wherever you need the substring.

Resources