Batch string variables with variables in the string name - string

I have a batch file where I have a variable named t%num% and num is an integer and t%num%=#.
I need to set another variable called q equal to the contents of t%num%.
I tried set q=t%num% so that q would contain a #, but it did not work.

#ECHO OFF
SETLOCAL
SET num=5
SET t%num%=36
CALL SET q=%%t%num%%%
ECHO %q% %t5%
GOTO :EOF
Uses the idea that % escapes % so that the parser substitutes-in the value of num and the result is set q=%t5%

You can (ab)use delayed environment variable expansion for this purpose.
Let's say you have an environment variable t5 defined. The following batch script will assign the content of t5 to the variable q:
setlocal EnableDelayedExpansion
echo t5: %t5%
set num=5
set q=!t%num%!
echo q: %q%
However, be aware that enabling delayed environment variable expansion will make ! a special character not only in the batch script, but also inside the content of environment variables. Try setting the following content for t5 before executing the little script above:
set t5=Hello World!
You will notice that the exclamation mark at the end of "Hello World" simply disappears.
Or try this cheeky bit:
set t5=Guess what: !num!
Setting t5 like this and then executing the script, well... i leave it to you to find out what will happen :)

Related

What is the difference between "..." and x"..." in an IF condition in a Windows batch file?

I recently found the post Find if substring is in string (not in a file) where it is stated that considering
#setlocal enableextensions enabledelayedexpansion
#echo off
set str1=%1
if not x%str1:bcd=%==x%str1% echo It contains bcd
endlocal
then
the x before the two sides of the equality is to ensure that the string bcd works okay. It also protects against certain "improper" starting characters.
However, I haven't found any explanation about the actual effect of this x. So what is the difference between x"%string%" and "%string%"?
That is simply a very bad coded string comparison. The x on both sides makes it possible to compare the two strings even if %str1:bcd=% or %str1% is substituted by Windows command processor on parsing entire command line by an empty string before execution of command IF.
But the batch file execution is nevertheless exited immediately by cmd.exe because of a syntax error in case of value of environment variable str1 contains a space character or "&<>|.
Enclosing an argument string in double quotes results in getting all characters except percent sign and with enabled delayed environment variable expansion also the exclamation mark interpreted as literal character including space which is outside a double quoted string interpreted as argument string separator.
So much better is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
if "%~1" == "" goto EndBatch
set "str1=%~1"
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
The first argument of the batch file is compared first without surrounding double quotes with an empty string. So if the batch file is started without any argument or with just "" as first argument string, Windows command processor executes the command GOTO resulting in restoring previous environment pushed on stack with command SETLOCAL and exits the batch file.
Otherwise the batch file is called really with an argument string. This argument string is assigned to environment variable str1 with removing surrounding double quotes if there are one. So on calling batch file with argument test the value test is assigned to environment variable str1 and on calling it with "another test" the value another test without the double quotes is assigned to str1. And even on calling the batch file with wrong coded argument string "bcd test (missing second ") just bcd test is assigned to the environment variable str1.
The IF condition compares the value of environment variable str1 with all occurrences of bcd removed with the unmodified variable value. The double quotes around the two strings make it possible to compare the two strings even on containing space or ampersand or the redirection operators <>|. The command IF includes the double quotes on comparing the two strings.
So is this code now safe?
No, it is not in case of somebody calls the batch file invalid with test_bcd" as argument string on which first double quote is missing. In this case the first IF command line executed by cmd.exe is:
if "test_bcd"" == "" goto EndBatch
The trailing " of the wrong specified argument string is not removed by cmd.exe and cause a syntax error on this command line on execution as it can be seen on running the batch file from within a command prompt window with first line modified to #echo on.
One solution without using delayed environment variable expansion is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
if not defined str1 goto EndBatch
set "str1=%str1:"=%"
if not defined str1 goto EndBatch
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
This code makes sure that str1 does not contain any double quote before executing the IF command comparing the strings.
Another solution is using delayed environment variable expansion:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "str1=%~1"
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
That looks better as the above code without usage of delayed environment variable expansion. But it does not work as expected if the the argument string is for example "!Hello!" because in this case the if not condition is also true and output is therefore the message It contains bcd although the string !Hello! does not contain bcd.
The solution is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
setlocal EnableDelayedExpansion
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
endlocal
Delayed expansion is not enabled on assigning the argument string to environment variable str1 which results in getting the exclamation marks in string "!Hello!" interpreted as literal characters. Then delayed expansion is enabled for making the string comparison with using delayed environment variable expansion which avoids that the command line itself is modified by cmd.exe before execution of IF command.
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 /? ... explains %~1, not so good as done here.
echo /?
endlocal /?
goto /?
if /?
set /?
setlocal /?
See also:
How does the Windows Command Interpreter (CMD.EXE) parse scripts? ... a long answer every batch file writer should read carefully from top to bottom.
forfiles - FALSE vs. false (doesn't work with lower case?) ... this answer is about argument processing of command IF.
Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files ... this answer explains in detail how string comparisons are done by command IF.
Why is no string output with 'echo %var%' after using 'set var = text' on command line? ... this answer explains why set "variable=value" should be used in general and not other variants.
Single line with multiple commands using Windows batch file ... explains how & and || outside a double quoted argument string are interpreted by cmd.exe.
Microsoft article about Using command redirection operators explains how <>|& outside a double quoted argument string are interpreted by cmd.exe.
The addition of x (or any other alphabetic character) in front of a string ensures that the relational statement is syntactically valid even when/if the string is empty.
Suppose str1 is an empty string. Then the comparison %str1:bcd=%==%str1% after the substitution degenerates to ==, which is syntactically invalid.
However, with an x in front, the comparison becomes x==x and can be evaluated. Naturally, adding the same prefix to each of the two strings does not affect their (in)equality.

Slicing Strings Using Variables In Batch

So I want a program like this:
Enter number:
5
Giving first 5 characters of string "apple pie"...
apple
I have tried something like this
set characters=5
set "string=apple pie"
set string=%string:~0, %characters%%
But it's not working, any idea why?
either rewrite
set string=%string:~0, %characters%%
as
call set "string=%%string:~0, %characters%%%"
or use delayed expansion
#echo off
set characters=5
set "string=apple pie"
setlocal enabledelayedexpansion
set "string=!string:~0, %characters%!"
echo %string%
see Variables in batch not behaving as expected

Split string by special characters '*' in batch file

I need to delete the substring after *. with a batch file
Example :
The value of string is : TEST_SINISTRE*.csv
rem My code :
SET mystring="TEST_SINISTRE*.csv"
rem Do the split
SET ext_test=%mystring:*.="& rem %"
SET ext_test=%ext_test%
rem what i get
echo %ext_test% ===> "& rem csv"
rem What i want to see
===> TEST_SINISTRE
Can you help me :-)
If, and only if, the *. pattern can only occur once in the string, and the part after *. is not contained in the part before *., the following could be used:
rem this is the original string containing one `*.`:
set "STRING=TEST_SINISTRE*.csv"
rem now get everything after `*.`:
rem (if `*` is the first character in substring substitution, it means everything up to
rem and including the search string is to be replaced, by nothing here in this case)
set "SUBSTR=%STRING:**.=%"
rem get everything before `*.`, including the `*`:
setlocal EnableDelayedExpansion
set "SUBSTL=!STRING:.%SUBSTR%=!"
rem truncate the `*` from the string:
endlocal & set "SUBSTL=%SUBSTL:~,-1%"
Since the substitution syntax for variable expansion is used, this is done in a case-insensitive manner.
To make it more secure, you could append something that will most probably never occur to your original string temporarily and remove it afterwards. To accomplish this, replace the set command line between the setlocal/endlocal block with the following (using appendix ### here for instance):
set "STRING=!STRING!###"
set "SUBSTL=!STRING:.%SUBSTR%###=!"
The * character is a wildcard in batch variable substring substitution. When you do *.=something in an inline substitution, you're really saying "replace everything up to and including the dot". You should use a for /F loop so you can specify the asterisk as a delimiter.
set "str=TEST_SINISTRE*.csv"
for /f "tokens=1* delims=*" %%I in ("%str%") do set "ext_test=%%I%%J"
echo %ext_test%
I'm not sure what your ultimate goal is, but here's a hacksy possible alternative. You could actually create a file called TEST_SINISTRE.csv and then capture the filename into a variable as a wildcard match.
set "str=TEST_SINISTRE*.csv"
type NUL > TEST_SINSTRE.CSV
for %%I in (%str%) do set "ext_test=%%I"
echo %ext_test%
I'm sure that's not exactly you have in mind, but it does demonstrate that maybe you don't need to strip the asterisk if you are going to be performing filename matching.

Batch String Manipulation

I am trying to write a batch program that writes more code based on what the user inputs. I am stuck on the string manipulation, as "!", ">", "&", and "%" all need to be escapeded before they are to be outputted to other files. This is what I have so far:
#echo off
set /p code=
set newcode=%code:^%=^%%,^>=^^>,!=^^!,^&=^^&%
echo %newcode%>>file.bat
All this escapinging escaped stuff is making my brain hurt, so can you please help me?
Thanks!
Since you haven't explained clearly what you are trying to do with the input, I am assuming you are trying to get the user to type something and then for that to be entered into a file. If that is the case te utilise copy in combination with con
#echo off
Echo Enter ^^Z (Ctrl + Z) and return it to end input
copy con file.txt
And that will allow the user to type WHATEVER they want, and it to be put in a file to be examined. Otherwise you're gonna have a lot of fun (And trouble) dealing with all the escaping you're gonna have to do (Rather ironic wasn't that?).
Mona.
Simply use delayed expansion in your case, as delayed expansion needs no further escaping for the content.
#echo off
setlocal EnableDelayedExpansion
set /p code=
echo(!code!
The echo( is for safe text output, even if the text is empty or is something like /?.
To the comment of #foxidrive:
When delayed expansion is enabled, then exclamation marks ! are altered, when they are visible before the delayed expansion is done, but the expanded content of delayed expanded variables will never be altered.
#echo off
set "var=Hello!"
setlocal EnableDelayedExpansion
echo "!var!" "Hello!"
This will result to: "Hello!" "Hello"

Weird scope issue in .bat file

I'm writing a simple .bat file and I've run into some weird behavior. There are a couple places where I have to do a simple if/else, but the code inside the blocks don't seem to be working correctly.
Here's a simple case that demonstrates the error:
#echo off
set MODE=FOOBAR
if "%~1"=="" (
set MODE=all
echo mode: %MODE%
) else (
set MODE=%~1
echo mode: %MODE%
)
echo mode: %MODE%
The output I'm getting is:
C:\>test.bat test
mode: FOOBAR
mode: test
Why is the echo inside the code block not getting the new value of the variable? In the actual code I'm writing I need to build a few variables and reference them within the scope of the if/else. I could switch this to use labels and gotos instead of an if/else, but that doesn't seem nearly as clean.
What causes this behavior? Is there some kind of limit on variables within code blocks?
You are running into the problem of cmd's static variable expansion. The MODE variable is only evaluated once. You can see this if you omit the #echo off line.
From the set /? documentation:
Finally, support for delayed environment variable expansion has
been added. This support is always
disabled by default, but may be
enabled/disabled via the /V command
line switch to CMD.EXE. See CMD /?
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%
setlocal EnableDelayedExpansion
will enable the /v flag
Additionally to already anwered here.
Long answer article:
https://rsdn.org/article/winshell/NTCommandProcessor.xml
The google translated variant is pretty broken, but you can at least read the raw text:
https://translate.google.com/?sl=ru&tl=en&text=https%3A%2F%2Frsdn.org%2F%3Farticle%2Fwinshell%2FNTCommandProcessor.xml&op=translate
Start read from Conditional block section.
Short answer:
The block operator (...) blocks a %-variable expansion until the top most scope. You have to exit the scope out to be able to use %-variable as is:
#echo off
set "MODE="
(
(
set MODE=all
echo MODE=%MODE%
)
echo MODE=%MODE%
)
echo MODE=%MODE%
Or use call prefix to reevaluate it in place:
#echo off
set "MODE="
(
(
set MODE=all
call echo MODE=%%MODE%%
)
)
Looks like the read and write use different scoping rules.
If you eliminate this line
set MODE=FOOBAR
it will work as expected. So you'll probably need to have a complex series if if/elses to get the variables populated as you'd like.

Resources