I'm trying to make a self-building chatbot that learn questions by inputing question and three possible answers, then, it randomize that three answers and give it as output. The learned question goes to the final line of the same batch file.
Here's the sample:
#echo off&&setlocal enabledelayedexpansion
echo/teach me a question to answer.
set/p q=
echo(>>%~f0
echo/:%q: =%>>%~f0
cls
echo/teach me three ways to answer it.
set/p a1=
set/p a2=
set/p a3=
echo/set a[0]=%a1%>>%~f0
echo/set a[1]=%a2%>>%~f0
echo/set a[2]=%a3%>>%~f0
echo/"set/a ax=%random% %%3&&set aa=!a[%ax%]!">>%~f0
echo/"echo %aa%">>%~f0
But I get the following:
:howareyou?
a[0]=good
a[1]=fine
a[2]=well
"set/a ax=24793 %3&&set aa="
"echo "
It's possible to get
set/a ax=%random% %%3&&set aa=!a[%ax%]!
echo %aa%
exactly as it is?
There are two problems in this code.
First of all, to answer your question:
How to echo exactly what I type in?
In batch, the answer is always more complicated than you think. In case of echo, quotes don't really change the way % and ! interpreted, but they are echoed as well, which is bad now.
Instead, escape % by duplicating, & by putting a ^, and ! by putting two ^s before it [ref]:
echo set/a ax=%%random%% %%%% 3^&^&set aa=^^!a[%%ax%%]^^! >>%~f0
echo echo %%aa%% >>%~f0
Now you can see, that everything's echoed correctly, but the echoed code still won't work.
Why?
The second problem is the &&: percent-variables (%var%) will be substituted before the evaluation of the line or the command group (commands enclosed by parentheses).
That is, since the set /a and set are on the same line, %ax%'s value will be substituted before set /a sets it. That could be avoided by using the power of the Delayed Expansion, and its !ax! syntax, but in this case, it isn't possible because it is accessed inside another variable access.
So, instead do what && would do otherwise:
set/a ax=%random% %% 3
if %errorlevel% GTR 0 set aa=^^!a[%%ax%%]^^!
Finally, you end up with something like:
echo set/a ax=%%random%% %%%% 3 >>%~f0
echo if %%errorlevel%% GTR 0 set aa=^^!a[%%ax%%]^^! >>%~f0
echo echo %%aa%% >>%~f0
Take a look Here for a functional example of escaping exclamation marks used in delayed variables for this type of situation.
#echo off & setlocal DisableDelayedExpansion
Set "/Ex=^!"
and to utilize:
Echo(Set "Answer=%/Ex%Answer[%%RandAnswer%%]%/Ex%">>Bot.bat
Highly recommend reading here to understand the way the command line is parsed and why this works.
Related
Can someone give me an example of where a batch script would act differently with or without delayed expansion? Are there any situations where you would NOT want to use delayed expansion? Thanks.
Look at the following examples...
Example 1: The following code DOESN'T use delayed expansion, so the variables in the for loop are expanded only one time. This means that %Count% will always expand to 0 in each iteration of the loop, no matter what we do to it with the set command:
#echo off
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=%COUNT% + 1
echo Count = %COUNT%
)
pause
So this script will output:
Count = 0
Count = 0
Count = 0
Count = 0
This is not how this loop is supposed to work.
Example 2: On the other hand, if we use delayed expansion, we have the following script, which will run as expected.
setlocal ENABLEDELAYEDEXPANSION
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=!COUNT! + 1
echo Count = !COUNT!
)
pause
and, as expected, it will output:
Count = 1
Count = 2
Count = 3
Count = 4
When you use the ENABLEDELAYEDEXPANSION, and expand a variable using ! instead of %, the variable is re-expanded each time, and everything works as it's supposed to.
I wanted to add a great example on how "EnableDelayedExpansion" (EDE) can be useful outside of the ubiquitous FOR loop examples.
Here is a line of earthquake data that I wish to parse (I call it it 1line.txt)
ak_11574812 2015.04.29.193822 62.9525 -148.8849 1.0 9.5 1 49km S of Cantwell, Alaska
The problem I ran into was that last segment of this line does not always start at the same column number. So I needed to create a flexible SET command that will accurately pluck out the last segment of this line.
ECHO OFF
setlocal enableDelayedExpansion
set where=72
set /p line=<1line.txt
set locate=!line:~%where%,28!
echo %locate%
EDE allows me to place a variable (where) inside another variable (line). EDE will translate the variable bracketed by % first, then process the variable bracketed by ! and (in this case) push out the results into the "locate" variable.
Max's answer gives an example of where a batch script would act differently with or without delayed expansion.
For the sake of completeness, let's answer another part of the question and show a situation where you would NOT want to use delayed expansion when your data contain an exclamation mark ! (and show two ways of processing such data):
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_auxFile=%temp%\%~n0.txt"
rem create multiline sample file
>"%_auxFile%" ( for /L %%G in (1,1,3) do echo line %%G is 100%% valid! Sure! Hurrah!)
rem create one-line sample file
>"%_auxFile%" echo this line is 100%% valid! Sure! Hurrah!
echo(
echo --- file content
type "%_auxFile%"
echo(
SETLOCAL EnableDelayedExpansion
echo --- enabled delayed expansion chokes down unescaped exclamation marks^^^! "^!"
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%~G"
echo loop var=%%~G
echo _auxLine=!_auxLine!
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- toggled delayed expansion works although might be laborious!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
SETLOCAL EnableDelayedExpansion
echo _auxLine=!_auxLine!
ENDLOCAL
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- keep delayed expansion DISABLED: use CALL command!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
call :ProcessVar
)
ENDLOCAL
rem delete the sample file
del "%_auxFile%"
ENDLOCAL
goto :eof
:ProcessVar
echo _auxLine=%_auxLine%
echo WARNING: neither !_auxLine! nor %%G loop variable is available here!
goto :eof
Note that above script shows proper ways of escaping
% percent sign by %% doubling it (delayed expansion does not matter), and
! exclamation mark if delayed expansion is enabled:
"^!" if enclosed in a pair of double quotes, then use the cmd and batch-script general escape character ^ caret;
^^^! otherwise, use three ^ carets.
Output:
==> D:\bat\SO\10558316.bat
--- file content
this line is 100% valid! Sure! Hurrah!
--- enabled delayed expansion chokes down unescaped exclamation marks! "!"
loop var=this line is 100% valid Hurrah
_auxLine=this line is 100% valid Hurrah
--- toggled delayed expansion works although might be laborious!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
--- keep delayed expansion DISABLED: use CALL command!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
WARNING: !_auxLine! as well as %G loop variables are not available here!
==>
As pointed in the answer the main usage of the delayed expansion is the setting and accessing variables in brackets context.
Though it can be useful in another situations too.
Parametrizing substring and string substitution:
#echo off
setlocal enableDelayedExpansion
set "string=test string value"
set start=5
set get_next=6
echo #!string:~%start%,%get_next%!#
set "search_for=string"
set "replace_with=text"
echo #!string:%search_for%=%replace_with%!#
the output will be:
#string#
#test text value#
though this can be achieved with additional call this way is more performant
Using shift command within brackets parameterized argument access
#echo off
echo first attempt:
(
echo "%~1"
shift
echo "%~1"
)
::now the shift command will take effect
setlocal enableDelayedExpansion
echo second attempt:
(
set /a argument=1
call echo %%!argument!
shift
call echo %%!argument!
)
the output will be:
first attempt:
"first argument"
"first argument"
second attempt:
"second argument"
"third argument"
As you can see parameterized argument access can be done only with delayed expansion.
Using for tokens (or function arguments) for parameterization
One more approach for mixing !s and %s this could be useful for nested loops:
#echo off
setlocal enabledelayedexpansion
set begin=2
set end=2
set string=12345
for /f "tokens=1,2" %%A in ("!begin! !end!") do set "string2=!string:~%%A,%%B!"
echo !string2!
endlocal
as you can see now the for command tokens are used as parameters.
Several answers here answer the "How to use delayed expansion?" question or what happen if you don't use delayed expansion. However, the second question is "Are there any situations where you would NOT want to use delayed expansion?" and a couple answers take this question as "how to avoid the problems caused by using delayed expansion?"
My answer answers the question as I understand it: "In which situations is better to NOT use delayed expansion (instead of use it)?"
If you want to exchange the contents of two variables, the simplest way to perform this is using the %standard% variable expansion:
set "var1=%var2%" & set "var2=%var1%"
The way that the %standard% expansion works makes possible to achieve this replacemenmt without using any auxiliary variable. As far as I know, the Batch-file "programming language" is the only one that allows to perform this exchange in this way, that is, making good use of a language "feature" (not via a specialized "exchange" instruction/statement).
I'm trying to set 2 variables in 3 categories, 6 variables total, copying out the categories three times seems like a poor option especially because my real code is much larger than this with almost 10 categories with 30 variables each.
First I ask which category to set variable (constant) and then asked to set the two variables in that category.
Which is fine, until I want to do something with the combined variable.
#echo off
cls
:start
cls
echo which variable do you want to set?
echo (1),(2),(3)
choice /c 123 /n
if ERRORLEVEL 3 goto :3
if ERRORLEVEL 2 goto :2
if ERRORLEVEL 1 goto :1
:1
set const=one
goto :wizard
:2
set const=two
goto :wizard
:3
set const=three
goto :wizard
:wizard
set /p %const%_varA= set %const% variableA:
set /p %const%_varB= set %const% variableB:
:: this line is the problem
echo %%const%_varA%
echo %%const%_varB%
::
echo.
pause
goto :filewrite
echo.
:filewrite
echo one varA %one_varA%
echo one varB %one_varB%
echo two varA %two_varA%
echo two varB %two_varB%
echo three varA %three_varA%
echo three varB %three_varB%
pause
goto :start
I have played around a bit and the problem you have is the fact that in batch-files the way to escape a % is another one. So your code will eventually look like echo %const%_varA% and as %var_A% is empty/does not exist, the only thing you should be getting is %const as output.
Luckily there is a way to bring another character into the game to prevent this from happening. Adding setlocal EnableDelayedExpansion under the first line will make variables accessable using exclamation marks. This is usually used to access variables in closed sets of parenthesis but comes in handy for this one:
#echo off
setlocal EnableDelayedExpansion
set const=three
set %const%_varA=foo
echo Without exclamationmarks: %%const%_varA%
echo With exclamationmarks : !%const%_varA!
pause
Is a tiny example that demonstrates the problem.
The upper line is what you currently have and not working. The lower one however uses above explaned delayed expansion.
Meaning: First (%) the value of %const% is calculated, changing the line to echo [...] !three_varA! and after (!) comes the whole thing!
Feel free to ask questions :)
I would like to know if its possible to use spaces in variables in the same batch file and compare it with another variable even though they both have spaces in them.
Example:
#echo off
CLS
set var1=variable 1
set var2=variable 2
IF %var1%==%var2% (
goto matches
) else (
goto doesnt
)
:matches
echo.
echo Both the Variables Match!
echo.
pause
exit
:doesnt
echo.
echo Both the Variables DO NOT Match!
echo.
pause
exit
just adding that code as an example since a simple way of learning it i could use it as a reference to help me remember.
Showing me how to make the example above to work, would help me greatly if its possible to match variables while still containing its spaces. I know a good bit about Batch, just don't exactly know how to get spaces to work in variables in that way. Thanks for your time, hopefully there's a Solution.
Enclosing the values in quotes, as per the malexander answer, will typically work. But it can fail if the value already contains quotes.
If you want to successfully compare values in variables no matter what the content, then you need delayed expansion:
setlocal enableDelayedExpansion
if !var! == !var2!
Put quotes around the variables.
IF "%var1%"=="%var2%"
I'm trying to d a simple batch replace of doublequotes to singlequotes.
The teststring must containg special characters, at most: "<LF>"
I cannot replace the double quotes there, as the batch just exists with Syntaxerror. Do you know why, or how to overcome this?
SET TEST="<LF>","<HT>"
SET modified=%TEST:"='% <-- Syntaxerror
ECHO %modified%
Use delayed expansion:
setlocal enabledelayedexpansion
SET TEST="<LF>","<HT>"
SET modified=!TEST:"='! <-- Syntaxerror
ECHO !modified!
As Mr Fuzzy Button notes, the problem is that the shell interprets < and > as redirection. Delayed expansion (using ! instead of %) expands variables after parsing and thus does not affect redirection.
You can solve the SET without delayed expansion by enclosing the argument in quotes:
SET "modified=!TEST:"='!"
But the ECHO would still be problematic, then.
While delayed expansion is a good thing, it's not necessary to answer this question. There are also times when setlocal isn't available. I ran into one.
In the windows shell, string matching for quotes works on outermost match pairs (in most cases, but not all), not innermost.
SET TEST="<LF>","<HT>"
echo %TEST%
SET "modified=%TEST:"='%"
echo.|set /p "___=%modified%"
Workaround: If echo can't print something, set can. Go figure.
This works for me...
SET TEST="LF","HT"
echo %TEST%
SET modified=%TEST:"='%
ECHO %modified%
The problem lies in your <s and >s being interpreted as input/output pipes in the Echo
echo off
set /a a=0
:start
if %a% LEQ 21(
echo test
set /a a=%a%+1
goto start
)
I don't get it...
I only get a Syntax-error when the loop starts.
The code you posted isn't a valid bash script it's a batch script to echo test 20 times in a bash script using a for loop like this:
#/bin/bash
for i in {1..20}; do
echo test
done
Much clearer and readable than batch scripts, welcome to bash!
Besides the error already explained by Bali C, you should note that your code have a potential problem. In this command: set /a a=%a%+1 the %a% value is expanded just one time when the if command is executed. In this case your code run by chance because the goto command cause the if be executed again in each loop, but if your program would be larger and more complex, and this set command would be executed several times inside parentheses, the code would fail because %a% will be expanded just one time to the value that a variable had before enter the parentheses.
The way to solve this problem is easy in this case: just eliminate the percent signs, because set /a a=a+1 command can directly take the values of the variables. However, there is much more involved here! For further details, type set /? and pay attention to "delayed variable expansion" description.
You are missing a space between 21 and ( which will make it compare the number in the loop to 21(, also breaking the if statement, the latter being why you are getting a syntax error.
Add a space and it works fine.
echo off
set /a a=0
:start
if %a% LEQ 21 (
echo test
set /a a=%a%+1
goto start
)