I have two bat files and i need to pass three parameter from one bat file to the other. However if the parameter ends in an "!" the parameter is not receieved
caller.bat
impl.bat param1 param2! param3
impl.bat
echo %1
echo %2
echo %3
Expected Result after running caller.bat:
param1
param2!
param3
Actual Result after running caller.bat:
param1
param2
param3
Can anyone help on how i can achieve the actual result?
As the other answers said, it's possible to disable delayed expansion, but it isn't necessary to display an exclamation mark.
It's important that you get the exlamation mark (and other special chaacters) into a variable.
Then you can use it perfectly with delayed expansion
Sample
#echo off
set "param1=%~1"
set "param2=%~2"
setlocal EnableDelayedExpansion
echo Works: !param1! !param2!
echo FAILS: %param1% %param2%
Calling this with
myBatch.bat arg1! "arg2!<>&"
The advantage of delayed expansion is that any content can be handled without side effects, as the expanded content will not be parsed anymore.
Percent expansion will fail in many cases, as the content will be parsed also for special characters
In fact, the explanation mark is received by the destination batch file, but is discarded when the echo command is processed. You can see this if you have echoing turned on. (However, if you use the call command to run the batch file, the explanation mark is not passed to it.)
The explanation mark is only discarded if delayed expansion is enabled. You can correct it by turning delayed expansion off temporarily:
setlocal disabledelayedexpansion
impl.bat param1 param2! param3
You can also work around it by quoting the special character with the caret, though since the argument goes through several levels of processing you have to quote it multiple times:
impl.bat param1 param2^^^^^! param3
or
call impl.bat param1 param2^^^^^^^^^^! param3
This approach may not be reliable, as it depends on what the child is doing with the argument. It is preferable to turn delayed expansion off, although that won't work if the child turns it back on again.
One of the few cases where the solution is to disable delayed expansion. I assume you are enabling it either when launching your cmd.exe, or as a setlocal line in your batch file. Either way, the shortest answer is to stop turning that on - by default cmd parameters do exactly what you want.
If you rely on delayed expansion elsewhere and can't turn it off globally, then you can use setlocal to disable it for a chunk of code.
echo %1
setlocal disabledelayedexpansion
echo %2
endlocal
echo %3
Just note that anywhere you expand a variable with the bang symbol, delayed expansion must be turned off. This includes if you set it to a variable and use that variable later.
Related
This question is originally coming from Escape percent signs in given variables. I do not want to disarrange the good answer over there. But my issue changed a little bit...
Let's assume there is a given string variable enclosed by double quotes which may include one or more percent signes. It is not possible to switch to enabled delayed expansion permanently (other code is already usable). Calling a function including the string variable as a parameter is necessary. This is what I determined so far:
#echo off & setlocal ENABLEEXTENSIONS
SET AlbumArtist=%1
CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN
echo %AlbumArtist_VDN%
CALL :EscapePoisonChars %%AlbumArtist%% AlbumArtist_VDN
echo %AlbumArtist_VDN%
endlocal &GOTO:EOF
:EscapePoisonChars
#echo off & setlocal ENABLEEXTENSIONS
SET TmpString="%~1"
SET TmpString=%TmpString:&=^^^&%
SET TmpString=%TmpString:(=^^^(%
SET TmpString=%TmpString:)=^^^)%
endlocal&SET %2=%TmpString:~1,-1%&GOTO :EOF
I know that this is probably not a "clean solution". But I would like to understand why when the routine is invoked by CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN the percent sign disappears. When called with the string variable %%AlbumArtist%% enclosed by doubled percent signs it gives the wanted output:
D:\Batch>PercentTwins.bat "100% Rock & Roll"
100 Rock & Roll
100% Rock & Roll
D:\Batch>
Why there is a different result if %AlbumArtist% is expanded in- or outside the function :EscapePoisonChars? With echo on I see that the percent sign just disappears with SET TmpString="~1". Any explanations will help me to improve my further cmd techniques. Thanks!
Anyone correct me if I am wrong, but I think when single percent signs in one command are passed on to another command, they will disappear. If it is just used within the same command, it normally will not disappear.
(There is probably a more 'correct' way of saying this, or this idea might be wrong altogether)
(some info on percent signs at Microsoft Support website)
The call command initiates the % parsing phase a second time (reference the accepted answer to the question post How does the Windows Command Interpreter (CMD.EXE) parse scripts?); this lets the % signs disappear. You could double the % signs in advance, but you would need to enable delayed expansion for that, like this:
#echo off & setlocal ENABLEEXTENSIONS EnableDelayedExpansion
SET AlbumArtist=%1
set AlbumArtist=!AlbumArtist:%%=%%%%!
CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN
echo %AlbumArtist_VDN%
endlocal &GOTO:EOF
In your second sub-routine call, the two parsing phases do not ever see the % signs of the original string; the first phase expands %%AlbumArtist%% to %AlbumArtist% literally, the second phase expands it to 100% Rock & Roll.
But:
There is no need for all that, you do not need the sub-routine at all, when you stick to the only safe set syntax and ensure to always have the string quoted properly:
set "AlbumArtist=%~1"
%1 would return the string as given; %~1 removes potential surrounding quotation marks. Enclosing the entire assignment expression within quotation marks makes the syntax robust against poisonous characters by not considering the "" as part of the assigned string itself.
When using/returning the string (by echo as in the example here), you need to enclose the string within quotes again to maintain safety, in case you are using normal (immediate) % expansion:
set "AlbumArtist=%~1"
echo "%AlbumArtist%"
Alternatively, delayed expansion makes reading variables safe even without quotation marks:
set "AlbumArtist=%~1"
setlocal EnableDelayedExpansion
echo !AlbumArtist!
endlocal
I am writing a batch file where I need to output a string containing '!' to another file. But when I echo that string to another file, it removes "!" from the output.
Eg:
Input:
set LINE=Hi this is! output
echo !LINE!>>new_file.txt
Output in new_file.txt is:
Hi this is output
Also, if input is
set LINE=Hello!! this is output!!
echo !LINE!>>new_file.txt
Output in new_file.txt:
Hello
Hence, it skips the ! (Exclamation mark) from the output to the new_file.
If I use %LINE%, then it simply displays "echo is on" to the output file.
Please suggest a way to overcome this problem.
If you have delayed expansion enabled and want to output an exclamation mark, you need to escape it.
Escaping of exclamation marks needs none, one or two carets, depending on the placement.
#echo off
REM No escaping required, if delayed expansion is disabled
set test1=Test1!
setlocal EnableDelayedExpansion
REM One caret required
REM Delayed expansion uses carets independent of quotes to escape the exclamation mark
set "test2=Test2^!"
REM Two carets required
REM The first caret escapes the second caret in phase2 of the parser
REM Later in the delayed expansion phase, the remaining caret escapes the exclamation mark
set test3=Test3^^!
echo !test1!
echo !test2!
echo !test3!
The difference between !var! and %var% in blocks is explained at DOS batch: Why are my set commands resulting in nothing getting stored?
An explanation of the batch parser can be found at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
It seems you have called SETLOCAL EnableDelayedExpansion somewhere higher in the code. Take a look here to see what the effects from that are.
Contents of test.bat are:
setlocal EnableExtensions EnableDelayedExpansion
set param1=%~1
echo %param1%
Can someone explain why test.bat "^^!^^^&^^^^" makes the cmd window crash but test.bat "^^^&^^^^" has an expected result of setting &^ to variable param1?
I can do test.bat "pass^^!word" and I get the expected result of pass!word.
Update: test.bat "^^!^^^^^&^^^^^^^^" works. But I'm not completely sure why. This gets interpreted to set param1=^!^^&^^^^. Why does ^ need ^^^ in front of it?
You got many problems, as the special characters will be evaluated mulitple times in your case.
First in the set command, the special character phase will reduce your string "^^!^^^&^^^^" to
^!^&^^
But as delayed expansion is enabled and your string contains an exclamation mark,
another phase will be enabled to reduce the carets again to.
!&^
At this point param1 contains !&^, you can test it with set param1
But as you try to echo the value with echo %param1% another expansion will be executed.
And now you get a problem, as %param1% will expand to !&^,
The exclamation mark will be removed, as the second exlamation mark is missing for expanding a variable,
the ampersand will be treated as new command separator and
the ending caret will be treated as multiline character.
echo ! & ^<next line>
It's much safer to use the delayed expansion here, as this never change the content, as this phase is the last one of the parser.
setlocal EnableDelayedExpansion
set param1=%~1
set param1
echo !param1!
And all these explanations can be found at How does CMD.EXE parse scripts?
It is because the escape character for the Windows shell is ^, so:
"^^!^^^^^&^^^^^^^^"
Each ^^ becomes ^
Each ^& becomes &
So finally you will get:
"^!^^&^^^^"
I am trying to create a batch string like this: >abcd_
I have a variable called soeid, with value as abcd. So this is what i am doing, but it does not work.
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo %str%
I'm sure it is working just fine. To prove it, add SET STR after you define the value, and you will see the correct value.
The problem you are having is when you try to echo the value, the line that is executing becomes: echo >abcd_. The > is not quoted or escaped, so it is simply taking the ouput of ECHO with no arguments and redirecting it to a file named "abcd_"
If you don't mind seeing quotes, then change your line to echo "%str%" and it will work.
The other option is to enable and use delayed expansion (I'm assuming this is a batch script code, and not executing on the command line)
setlocal enableDelayedExpansion
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo !str!
Normal %var% expansion occurs early on while the interpreter is parsing the line. Delayed !var! expansion occurs at the end just before it is executed. The redirection is detected somewhere in the middle. That is why the normal expansion doesn't work - the interpreter sees the expanded > character and interprets it as the output redirection operator. The delayed expansion hides the > character from the interpreter until after redirection is parsed.
For more info about delayed expansion, type SET /? from the command line and read starting with the paragraph that starts with "Finally, support for delayed environment variable expansion...".
This code
#echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:!myvar!
set myvar=!myvar!
echo myvar:!myvar!
gives
myvar:first
myvar:first
on Windows Vista SP2.
The output I had expected is
myvar:first
myvar:second
Why the difference and how to obtain desired effect?
The problem is that set myvar=!myvar! expands to set myvar=first,
you set it with the same content, and then you ask echo myvar:!myvar! to show the content of myvar.
I will try to add some more explanations, even if Aacini and shf301 already answered the question.
Both showed the double expansion with the !%var%! construct, and Aacini explained why it can work, and why the reversed version %!var!% can't work.
IMHO there are four different expansions.
Delayed Expansion:
As Aacini explained the delayed expansion is safe against any special characters in the content (it can handle ALL characters from 0x01 to 0xFF).
Percent Expansion:
The percent expansion can't handle or removes some characters (even with escaping).
It can be useful for simple content, as it can expand variables after an endlocal barrier.
setlocal
set "myVar=simple content"
(
endlocal
set result=%myVar%
)
FOR-Loop-Parameters expansion:
It is safe, if the delayed expansion is disabled, else the delayed expansion phase is executed after the expansion of the %%a variables.
It can be useful, as it can expand variables after an endlocal barrier
setlocal EnableDelayedExpansion
set "var=complex content &<>!"
for /F "delims=" %%A in ("!var!") DO (
endlocal
set "result=%%A"
)
SET Expansion:
set var expands also a variable, and it is always safe and works independent of the delayed expansion mode.
Aacini just explained how the call %%%var%%% construct work, I only want to give some additional remarks.
call is stackable, you can use many of them and each restarts the parser.
set "var=%%var%%#"
call call call call call echo %var%
results to %var%######
But call have many disadvantages/side effects!
Each call double all carets ^
You can say: "Hey I've tested it and I can't see any doubling"
call call call call echo ^^
result ^
Nevertheless it's true, but it's mostly hidden, as each restart also have a special character phase where carets escapes the next character, but you can see the doubling effect with
call call call call echo "^^"^^
result "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"^
Even if a call expansion restarts the parser, you can never use the delayed expansion in any phase (only in the first one).
call stops working if it detects unescaped special characters.
echo you ^& me
call echo you & me
call echo you ^& me
call echo you ^^& me
call echo you ^^^& me
Only the first results to the output you & me, all the others fails.
Another problem is that call is extremly slow, a call set var=content is ~50times slower than set var=content, the cause is that call try to start an external program.
#echo off
setlocal
(
echo echo *** External batch, parameters are '%%*'
) > set.bat
set "var="
call set var=hello
set var
I hope it was interesting a bit ...
And if you want to go more in depth you can read CALL me, or better avoid call
and How does the Windows Command Interpreter (CMD.EXE) parse scripts?
This problem is not directly related to Delayed variable Expansion, but to the fact that two value expansions are required: the first one give the variable name and the second one must replace this name by its value. The direct way to do that is via two expansions in the same line as shown in the previous answer: set myvar=!%myvar%! that works because %var% expansion is done before the command-line is analyzed for execution whereas !var! expansion is done later, just before the command is executed (hence the "delayed" name). This mean that %var% expansion may provide parts of the command and may cause syntax errors, but !var! not. For example if %var%==value ... cause an error if var is empty or have spaces, but if !var!==value ... never cause a syntax error.
The double expansion of values may be achieved in other ways that does not involve Delayed variable Expansion. For example, we may create an auxiliary Batch file that do the second expansion:
echo myvar:%myvar%
echo set myvar=%%%myvar%%%> auxiliary.bat
call auxiliary
echo myvar:%myvar%
Previous method may be used to do a third or even deeper level expansions, and even be combined with Delayed Expansions to create very complex value managements. This matter is not just a curiosity, but the key to access array elements or linked lists. For example:
set month[1]=January
set month[2]=February
. . .
set month[12]=December
for /f "tokens=1-3 delims=/" %%a in ("%date%") do echo Today is !month[%%a]! %%b, %%c
What you're trying to do won't work - delayed expansion only changes the variable expansion behavior of a variable inside of a block. It doesn't allow you the aliasing/nesting (for a lack of a better word) that you are attempting.
set myvar=first sets the variable myvar to the text "first". set first=second sets the variable first to the text "second. There is no link between those two lines. myvar will never evaluate to something that it wasn't explicitly set to.
I don't believe there is anyway to accomplish what you are trying to do here.
* Edit *
OK after taking a look at your answer I seeing how that works, you can get your desired output with this:
#echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:%myvar%
set myvar=!%myvar%!
echo myvar:%myvar%
So the magic seems to happen because of the way that standard and delayed expansion occur. The line set myvar=!%myvar%! is seems be expanded first by the standard expander to set myvar=!first! (you'll see this if you run the script with echo on). Then the delayed expander runs and expands !first to "second" and set's myvar to that.
I have no idea if this is documented behavior as to how standard and delayed expansion should work or just an implementation detail (which means it could break in the future)