How to pass escaped double quote to batch file - windows

I know there were similar questions, but there is one thing I can't find in the answers. I'm trying to pass the following string to batch file:
hello " world
This is a single argument. Here is my batch file:
#echo off
#echo %1
Now if I run it from command line, I'm getting the following results:
>C:\file.bat "hello "" world"
"hello "" world"
>C:\file.bat "hello \" world"
"hello \"
>C:\file.bat "hello """ world"
"hello """
In all cases I'm getting the wrong result, how do I escape a double quote and pass it correctly? Or should I do any additional conversion steps in the batch file itself?

It is impossible to pass a string literal value of hello " world as a batch parameter for two reasons:
Space cannot be escaped with regard to parameter tokenization. Parameters are always delimited by unquoted token delimiters, even if they are preceded by the ^ escape character. The token parameter token delimiters are {space}, {tab}, ,, ; =, and {0xFF}. The only way to include a token delimiter as part of a parameter is to make sure the delimiter is quoted.
It is impossible to escape a quote within a quoted string. Quotes are a state machine: The first encountered quote turns on quote semantics, and the subsequent one turns it off. The next quote turns it back on again, etc. If quoting is off, then a quote literal can be escaped as ^" to keep quoting off. But once quoting has begun, then there is no way to escape a closing
quote: The next quote always turns quoting off.
So no matter how you add quotes and/or escaped quotes, there will always be an unquoted space, thus the string will be treated as two parameters.
You could adopt the strategy recommended by Hapax - Quote the entire string, and double any quote literals within the string: "Hello "" world".
However I would make a slight change and use %~1 instead of %1 so as to remove the outer quotes. Then the doubled quotes should be converted back to a single quote:
#echo off
set "arg1=%~1"
set "arg1=%arg1:""="%"
echo %arg1%
But there are other potential issues with passing string literals as batch parameters. The most insidious is caret doubling. It is impossible to pass a quoted caret as a parameter if the caller uses CALL. I won't get into the mechanism behind the problem. See https://stackoverflow.com/a/4095133/1012053 if you want more information. But the following illustrates the problem:
test.bat
#echo %1
-- Sample cmd session --
D:\test>test "^"
"^"
D:\test>call test "^"
"^^"
Because of the many complications with passing string literals as batch parameters, the most effective strategy used in advanced batch scripting is to store the value in an environment variable, and then pass the variable name as the parameter. The batch script can then use delayed expansion to get the correct value:
test2.bat
#echo off
setlocal enableDelayedExpansion
set "arg1=!%1!"
echo !arg1!
-- Sample cmd session --
D:\test>set "myVar=Hello world! ^&|<>" How are you doing? !^^^&^|^<^>^"
D:\test>set myVar
myVar=Hello world! ^&|<>" How are you doing? !^&|<>
D:\test>test2 myVar
Hello world! ^&|<>" How are you doing? !^&|<>
D:\test>call test2 myVar
Hello world! ^&|<>" How are you doing? !^&|<>

The way to keep quotes is to pass them doubled. However, this passes both of them.
You can then process the input and remove the doubles.
Executing using file.bat "hello "" world", we use:
#echo off
set param=%1
set param=%param:""="%
echo %param%
(result: "hello " world")
A shorter version:
#echo off
set param=%1
echo %param:""="%

Related

Special Characters in Batch File

Special characters in batch files are a pain, but I haven't found the right workaround for properly escaping the first two characters of this particular string I'm trying to pass the application.
SET pass=^&AntiBatchfileString
A_Program.exe /pass=%pass%
Things I have tried:
:: Escaping the escape twice, first for ^, second for &.
SET pass=^^^^&AntiBatchfileString
echo %pass%
:: Combining escapes.
SET first=^^
SET second=^^&AntiBatchfileString
SET pass=%first%%second%
echo %pass%
:: Preventing expansion
SET first=^^
SET second=^^&AntiBatchfileString
SET pass=!first!%second%
echo %pass%
:: I got this to print correctly
SET "pass=^&AntiBatchfileString"
echo ^^%pass%
Still when passing the last one it doesn't accept the login, I don't know what the final output is. That got me thinking maybe it was trying to do another expansion when passing the parameter to the application, so I quoted that as well.
SET "pass=^&AntiBatchfileString"
A_Program.exe "/pass=^^%pass%"
It's still not working, I'm not sure what I'm missing at this point.
Supposing you want the string ^&AntiBatchfileString literally, this is the best set syntax, as most special characters (^ & ( ) < > | and also the standard delimiters , ; = SPACE TAB) lose their particular meaning as soon as ther are placed in between "", and the "" themselves do not become part of the variable value:
set "pass=^&AntiBatchfileString"
This works only as long as the command extensions are on, which is the Windows default anyway (type cmd /? and see the /E option).
When expanding (reading) a variable like "%pass%" (with enclosing ""), special characters are still treated literally.
However, as soon as you expand it like %pass% (no ""), they get back their special meaning. So you have the following options:
Use set "pass=^^^&AntiBatchfileString", where ^^ escapes the literal ^ and ^& the literal & when reading like %pass%.
Enable delayed expansion (see set /? about how it works and setlocal /? or cmd /? about how to enable it), where the variable value is expanded (read) at a point of time where parsing of special characters has already been completed.
I prefer the latter approach, because no special escaping is necessary, and it can also deal with " appearing in the string value (even if unsymmetrically present).
By the way, " can also be escaped by ^", as long as this does not appear within unescaped "".
Nevertheless, % signs cannot be escaped like ^% in a batch file, because percent expansion happens before escaping, but you need to double them like %% to get one literal one each, independent whether or not the string is in between "".
Note that on the console, %% does not work.
Finally, literal ! are consumed by the delayed expansion feature when enabled, therefore you need to pay particular attention to those in case, by escaping them like ^!, or also by intelligently toggling delayed expansion (hence to enable it only when it is actually needed and to disable it otherwise, when a literal string is provided, like in a set command line, for instance, when expanding a standard variable like %pass% and when reading a for variable like %%I (batch file) or %I (console), for example). Of course this is also not the ultimate solution, because you need setlocal and endlocal to enable/disable delayed expansion, which are intended to localise environment changes, so any variable changes since the most recent setlocal command are lost as soon as endlocal is executed (there are some tricks for passing a variable value over the endlocal barrier though).
If you want to use % as a string without escaping in a batch file:
Like %20, you can use %%%20.
git clone "https:// abc.com /D%%%220an"

Batch scripting: Why does this print "= instead of the empty string?

I'm trying to remove quotes in some text using parameter expansion in Batch. Can anyone tell me why this:
#echo off
setlocal
set args=%*
echo %args:"=%
prints "= instead of nothing? As far as I can see %args:"=% should replace all quotes with nothing, so I don't get why this is happening.
Any help would be appreciated, thanks!
edit: To clarify, I'm not passing any parameters to the batch script.
That is the result you get when you do not pass any arguments to your script.
If args is not defined, then %args:"=% is expanded as follows:
%args: is treated as a non existent variable expansion, which becomes nothing
"= is treated as itself
% (a lone percent) is stripped
It is not intuitive, but that happens to be how cmd.exe works. See https://stackoverflow.com/a/7970912/1012053 for more info.
You can prevent the problem by using if defined
#echo off
setlocal
set args=%*
if defined args echo %args:"=%
It works here
C:\Windows\system32>"C:\Users\User\Desktop\Test2.bat" dog
dog
Removing echo off shows this
C:\Windows\system32>setlocal
C:\Windows\system32>set args=dog
C:\Windows\system32>echo dog
dog
You are not passing any command line arguments.
C:\Windows\system32>"C:\Users\User\Desktop\Test2.bat"
C:\Windows\system32>setlocal
C:\Windows\system32>set args=
C:\Windows\system32>echo "=
"=

& sign inside batch file parameter to be passed for another program

I have a batch file that I call with something like this
call do.cmd "one two"
In do.cmd I am launching a program and pass to it first parameter from above:
#echo off
some_program.exe -name='%1'
The value to some_program.exe for name variable must be passed inside single quotes without surrounding double quotes. To get rid of double quotes in passed parameter I make a temporary variable like this:
set v_tmp=%1
set v_tmp=%v_artist:"=%
And then launch my program by
some_programm.exe -name='%v_tmp%'
The problem start when with do.cmd some text having & sign is passed. If I leave batch file code as is, variaable setting will fail because & will act as a divider. If I escape & sign by
set v_tmp=%1
set v_tmp=%v_tmp:&"=^^^&%
set v_tmp=%v_tmp:"=%
then some_program will output text having ^&..
The question is how do I get from call do.cmd "one & two" line to the correct & sign escaping and double quote removal so that to have in result some_program.exe -name='one & two'?
You could try to use delayed expansion here.
#echo off
set "arg1=%~1"
setlocal EnableDelayedExpansion
some_program.exe -name='!arg1!'
This works, as %~1 removes enclosing quotes, if present.
And !arg1! always expands the variable in a safe manner.

Writing a text string that contains a backslash, issues with assignment of the variable

I want the bat file to take this text \Music\TheArchive\Aeph & Neonlight\Aeph & Neonlight - Space Truckers - 7b - 173.26.mp3 and write it into a new row in the existing text file baseplaylist.m3u
So far I have this:
set "texty=\Music\TheArchive\Aeph & Neonlight\Aeph & Neonlight - Space Truckers - 7b - 173.26.mp3"
echo %texty% >>C:\Music\Playlists\baseplaylist.m3u
but it does not work. I know the issue is due to the characters in the text that I am trying to copy, but I don't know how to overcome this.
Backslash has nothing to do with your problem. Nor is there a problem with your assignment of the variable. :-)
Your problem is the & character - it is the compound command operator that allows you to run multiple commands on the same line of input. You want the & to be a literal, but it is being treated as an operator when you try to ECHO it. For example, echo this&echo that prints 2 lines of output to the screen: "this" followed by "that".
A batch line must be parsed before it can be executed. The parser must identify commands and operators. The expansion of %texty% occurs before the commands and operators are parsed, so the parser thinks the next token after & is a command.
There are many special characters that can cause similar issues: | > < ) & ^.
There are two ways to force the batch parser to treat a special character as a literal: 1) enclose it in quotes, or 2) escape it with ^.
echo this&echo that
echo "this & that"
echo this ^& that
results:
this
that
"this & that"
this & that
In your SET statement you have the entire assignment enclosed in quotes, so it is properly treated as a literal. You can prove it to yourself by putting the command set texty after your set assignment - it will print the current value of texty to the screen and you can see that it is your desired value.
But when you echo the value it is no longer quoted, so the parser treats the & as an operator and you have your failure.
You could echo "%texty", but then you would get quotes in your output.
You could change the definition of texty to include the escape character.
set "texty=\Music\TheArchive\Aeph ^& Neonlight\Aeph ^& Neonlight - Space Truckers - 7b - 173.26.mp3"
But that is a pain. Thankfully there is a simple solution - delayed expansion of a variable occurs after the commands and operators have been parsed, so you no longer have to worry about special characters.
Delayed expansion must be enabled before it can be used using setlocal enableDelayedExpansion. Then you enclose the variable name in exclamation points instead of percents.
setlocal enableDelayedExpansion
set "texty=\Music\TheArchive\Aeph & Neonlight\Aeph & Neonlight - Space Truckers - 7b - 173.26.mp3"
echo !texty! >>C:\Music\Playlists\baseplaylist.m3u
Now the line is parsed for commands and operators before !texty! is expanded. So ECHO properly prints the literal value of texty.
The problem is the operator &, in that cases you will need to expand the variable or scape the operator or enclosing the "echo" string with another double-quotes... But the solution in your case is so simple:
set "texty=\Music\TheArchive\Aeph & Neonlight\Aeph & Neonlight - Space Truckers - 7b - 173.26.mp3"
<nul Set /P "string=%TEXTY%" >>C:\Music\Playlists\baseplaylist.m3u
PS: Using SETLOCAL ENABLEDELAYEDEXPANSION is a bad practice when you are playing with filenames (especially when they contain the character ! ) so try to never use the expansion, all can be done without expanding vars (ALL), enabling delayedexpansion is the easy and bad way to done the tmost things, but not the only way.
I hope this helped you.

Escape double quotes in parameter

In Unix I could run myscript '"test"' and I would get "test".
In Windows cmd I get 'test'.
How can I pass double-quotes as a parameter? I would like to know how to do this manually from a cmd window so I don't have to write a program to test my program.
Another way to escape quotes (though probably not preferable), which I've found used in certain places is to use multiple double-quotes. For the purpose of making other people's code legible, I'll explain.
Here's a set of basic rules:
When not wrapped in double-quoted groups, spaces separate parameters:program param1 param2 param 3 will pass four parameters to program.exe: param1, param2, param, and 3.
A double-quoted group ignores spaces as value separators when passing parameters to programs:program one two "three and more" will pass three parameters to program.exe: one, two, and three and more.
Now to explain some of the confusion:
Double-quoted groups that appear directly adjacent to text not wrapped with double-quotes join into one parameter:hello"to the entire"world acts as one parameter: helloto the entireworld.
Note: The previous rule does NOT imply that two double-quoted groups can appear directly adjacent to one another.
Any double-quote directly following a closing quote is treated as (or as part of) plain unwrapped text that is adjacent to the double-quoted group, but only one double-quote:"Tim says, ""Hi!""" will act as one parameter: Tim says, "Hi!"
Thus there are three different types of double-quotes: quotes that open, quotes that close, and quotes that act as plain-text.
Here's the breakdown of that last confusing line:
" open double-quote group
T inside ""s
i inside ""s
m inside ""s
inside ""s - space doesn't separate
s inside ""s
a inside ""s
y inside ""s
s inside ""s
, inside ""s
inside ""s - space doesn't separate
" close double-quoted group
" quote directly follows closer - acts as plain unwrapped text: "
H outside ""s - gets joined to previous adjacent group
i outside ""s - ...
! outside ""s - ...
" open double-quote group
" close double-quote group
" quote directly follows closer - acts as plain unwrapped text: "
Thus, the text effectively joins four groups of characters (one with nothing, however):
Tim says, is the first, wrapped to escape the spaces
"Hi! is the second, not wrapped (there are no spaces)
is the third, a double-quote group wrapping nothing
" is the fourth, the unwrapped close quote.
As you can see, the double-quote group wrapping nothing is still necessary since, without it, the following double-quote would open up a double-quoted group instead of acting as plain-text.
From this, it should be recognizable that therefore, inside and outside quotes, three double-quotes act as a plain-text unescaped double-quote:
"Tim said to him, """What's been happening lately?""""
will print Tim said to him, "What's been happening lately?" as expected. Therefore, three quotes can always be reliably used as an escape.However, in understanding it, you may note that the four quotes at the end can be reduced to a mere two since it technically is adding another unnecessary empty double-quoted group.
Here are a few examples to close it off:
program a b REM sends (a) and (b)
program """a""" REM sends ("a")
program """a b""" REM sends ("a) and (b")
program """"Hello,""" Mike said." REM sends ("Hello," Mike said.)
program ""a""b""c""d"" REM sends (abcd) since the "" groups wrap nothing
program "hello to """quotes"" REM sends (hello to "quotes")
program """"hello world"" REM sends ("hello world")
program """hello" world"" REM sends ("hello world")
program """hello "world"" REM sends ("hello) and (world")
program "hello ""world""" REM sends (hello "world")
program "hello """world"" REM sends (hello "world")
Final note: I did not read any of this from any tutorial - I came up with all of it by experimenting. Therefore, my explanation may not be true internally. Nonetheless all the examples above evaluate as given, thus validating (but not proving) my theory.
I tested this on Windows 7, 64bit using only *.exe calls with parameter passing (not *.bat, but I would suppose it works the same).
I cannot quickly reproduce the symptoms: if I try myscript '"test"' with a batch file myscript.bat containing just #echo.%1 or even #echo.%~1, I get all quotes: '"test"'
Perhaps you can try the escape character ^ like this: myscript '^"test^"'?
Try this:
myscript """test"""
"" escape to a single " in the parameter.
The 2nd document quoted by Peter Mortensen in his comment on the answer of Codesmith made things much clearer for me. That document was written by windowsinspired.com. The link repeated: A Better Way To Understand Quoting and Escaping of Windows Command Line Arguments.
Some further trial and error leads to the following guideline:
Escape every double quote " with a caret ^. If you want other characters with special meaning to the Windows command shell (e.g., <, >, |, &) to be interpreted as regular characters instead, then escape them with a caret, too.
If you want your program foo to receive the command line text "a\"b c" > d and redirect its output to file out.txt, then start your program as follows from the Windows command shell:
foo ^"a\^"b c^" ^> d > out.txt
If foo interprets \" as a literal double quote and expects unescaped double quotes to delimit arguments that include whitespace, then foo interprets the command as specifying one argument a"b c, one argument >, and one argument d.
If instead foo interprets a doubled double quote "" as a literal double quote, then start your program as
foo ^"a^"^"b c^" ^> d > out.txt
The key insight from the quoted document is that, to the Windows command shell, an unescaped double quote triggers switching between two possible states.
Some further trial and error implies that in the initial state, redirection (to a file or pipe) is recognized and a caret ^ escapes a double quote and the caret is removed from the input. In the other state, redirection is not recognized and a caret does not escape a double quote and isn't removed. Let's refer to these states as 'outside' and 'inside', respectively.
If you want to redirect the output of your command, then the command shell must be in the outside state when it reaches the redirection, so there must be an even number of unescaped (by caret) double quotes preceding the redirection. foo "a\"b " > out.txt won't work -- the command shell passes the entire "a\"b " > out.txt to foo as its combined command line arguments, instead of passing only "a\"b " and redirecting the output to out.txt.
foo "a\^"b " > out.txt won't work, either, because the caret ^ is encountered in the inside state where it is an ordinary character and not an escape character, so "a\^"b " > out.txt gets passed to foo.
The only way that (hopefully) always works is to keep the command shell always in the outside state, because then redirection works.
If you don't need redirection (or other characters with special meaning to the command shell), then you can do without the carets. If foo interprets \" as a literal double quote, then you can call it as
foo "a\"b c"
Then foo receives "a\"b c" as its combined arguments text and can interpret it as a single argument equal to a"b c.
Now -- finally -- to the original question. myscript '"test"' called from the Windows command shell passes '"test"' to myscript. Apparently myscript interprets the single and double quotes as argument delimiters and removes them. You need to figure out what myscript accepts as a literal double quote and then specify that in your command, using ^ to escape any characters that have special meaning to the Windows command shell. Given that myscript is also available on Unix, perhaps \" does the trick. Try
myscript \^"test\^"
or, if you don't need redirection,
myscript \"test\"
I'm calling powershell from cmd, and passing quotes and neither escapes here worked. The grave accent worked to escape double quotes on this Win 10 surface pro.
>powershell.exe "echo la`"" >> test
>type test
la"
Below are outputs I got for other characters to escape a double quote:
la\
la^
la
la~
Using another quote to escape a quote resulted in no quotes.
As you can see, the characters themselves got typed, but didn't escape the double quotes.
Maybe you came here, because you wonder how to escape quotes that you need in the command that you pass to /c on cmd.exe? Well you don't:
CMD /c "MKDIR "foo bar""
will execute
MKDIR "foo bar"
which is really a behavior that I did not expect in the first glance.

Resources