Double Quotes in batch string redux - windows

I'm trying to understand how the ^ Caret escape works in a Batch CMD. Lets say I type this line in a CMD Window, I would expect it to send the DIR listing to "c:\my text.txt"
CMD.EXE "/C > ^"c:\my text.txt^" DIR C:\*.*"
Instead I get the error:
'txt.txt" DIR *.*"' is not recognized as an internal or external command,
operable program or batch file.
If I do something as simple as this:
ECHO "^"^""
Expected: """"
Actual : "^"""
Even if I try double quoting like this:
ECHO " "" "" "
Expected: " " " "
Actual : " "" "" "
Can someone 'splain how this works and what is a reliable way is to escape double quotes in a command line?
Thanks.
Additional Example:
Why does this work:
cmd.exe "/C sqlcmd.exe -S.\SQLEXPRESS -E -Q"select suser_name()" > "c:\temp\test 1.txt""
but this gives error "The system cannot find the path specified." and does not create the txt file.
cmd.exe "/C "sqlcmd.exe" -S.\SQLEXPRESS -E -Q"select suser_name()" > "c:\temp\test 1.txt""
I know it is not needed in this case, but how would I enclose the command sqlcmd.exe in quotes?
I also tried removing the quotes from the whole line with the same results, i.e.,
cmd.exe /C "sqlcmd.exe" -S.\SQLEXPRESS -E -Q"select suser_name()" > "c:\temp\test 1.txt"
Thanks again.

Quotes are a state machine within cmd.exe. The first quote turns quoting semantics on, the next quote turns it off, the next back on, etc.
It is possible to escape a quote such that it does not turn quote semantics on.
For example, the following will send a quote character to test.txt
echo ^" >test.txt
Without the escape, the string " >test.txt is simply printed to the screen.
Unfortunately, it is not possible to escape a quote that turns off quote semantics.
For example, suppose you want to print the string "hello world" >"test.txt" to the screen. None of the techniques you tried will work because an internal quote cannot be escaped. The ^" form treats the ^ as a literal, and the output will still be redirected to a file. The "" for properly quotes the >, so there is no redirection, but now you have two quotes instead of one.
You have two options.
1) escape all "poison" characters that are not quoted
echo "hello world" ^>"test.txt"
2) hide the quote literal within a delayed expansion variable
I'm assuming this code is in a batch script.
setlocal enableDelayedExpansion
set "quote=""
echo "hello world!quote! >!quote!test.txt"
Note that using this technique can become tricky if dealing with FOR /F ... IN('command') or CMD /C because delayed expansion will probably be off within the new CMD.EXE context.
Here is a way to do your original command
cmd /c dir c:\*.* ^>"c:\my text.txt"
Note that the entire command string after /c does not have to be enclosed within quotes. In this case, it is easier if it is not.
I believe your new command at the end can be written as follows:
cmd.exe /C ""sqlcmd.exe" -S.\SQLEXPRESS -E -Q"select suser_name()" > "c:\temp\test 1.txt""
If used within a parenthesized block, then the right paren will need to be escaped.
(
cmd.exe /C ""sqlcmd.exe" -S.\SQLEXPRESS -E -Q"select suser_name(^)" > "c:\temp\test 1.txt""
)

Related

Cygwin: Invoke batch file with argument, output redirection and complex windows paths

How do I invoke a batch file from a bash script under Cygwin, with...
absolute path to the batch file
batch file argument(s) representing absolute (windows) path(s)
output redirection into a file at an absolute path
where...
all paths may contain whitespace and special characters like parentheses
all paths are initially present only in Unix format within the bash script?
I have a windows batch script C:\Program Files (x86)\bin\script.bat, which takes a (windows) path to an input file as argument - example:
#ECHO OFF
echo "This is script.bat. The value of the first argument is [%~1]"
I want to redirect the output of script.bat into an output file at another absolute path.
On the windows command prompt (cmd.exe), I would invoke the command like this:
C:\> "C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt" > "C:\Output Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
I can omit the double quotes around the script and the redirection target if I apply "batch-style escaping", but that doesn't work for the argument (for some reason):
C:\> C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt" > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
...but:
C:\> C:\Program^ Files^ ^(x86^)\bin\script.bat C:\Input^ Path\input.txt > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input].
I can also wrap the command into an additional call to cmd.exe, using additional double quotes around the whole term:
C:\> cmd.exe /C ""C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt" > "C:\Output Path\output.txt""
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
C:\> cmd.exe /C "C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt" > C:\Output^ Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
I can also apply the output redirection to the outer cmd.exe instance:
C:\> cmd.exe /C ""C:\Program Files (x86)\bin\script.bat" "C:\Input Path\input.txt"" > "C:\Output Path\output.txt"
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
C:\> cmd.exe /C "C:\Program^ Files^ ^(x86^)\bin\script.bat "C:\Input Path\input.txt"" > C:\Output^ Path\output.txt
output.txt: This is script.bat. The value of the first argument is [C:\Input Path\input.txt].
So far so good. But how can I invoke any of the above command lines from a bash script under Cygwin?
All paths initially exist only in Unix format:
#!/bin/sh
BIN_UNIX="/cygdrive/c/Program Files (x86)/bin/script.bat"
ARG_UNIX="/cygdrive/c/Input Path/input.txt"
OUT_UNIX="/cygdrive/c/Output Path/output.txt"
Note that the file $OUT_UNIX does not exist at the time the script is called (so 'cygpath --dos ...' doesn't work).
I have experimented with dozens of more or less clumsy combinations of Unix / Windows paths (converting with cygpath), no quotes, single quotes, double quotes, no escaping, "bash-style" escaping, "batch-style" escaping, etc. The only working solution I could find depends on an 8.3-style path for the batch script, which eliminates white spaces and special characters:
#!/bin/sh
# [...]
BIN_DOS=$( cygpath --dos "${BIN_UNIX}" )
ARG_WIN=$( cygpath --windows "${ARG_UNIX}" )
cmd /C "${BIN_DOS}" "${ARG_WIN}" > "$OUT_UNIX"
But there must be a more systematic and robust way to do this, right?
Related questions which all don't fully answer my question:
Why is it that Cygwin can run .bat scripts?
Windows batches in Cygwin with spaces in path and arguments
correct quoting for cmd.exe for multiple arguments
How to Pass Command Line Parameters with space in Batch File
You could use
BIN_WIN=$( cygpath --windows "${BIN_UNIX}" )
ARG_WIN=$( cygpath --windows "${ARG_UNIX}" )
OUT_WIN=$( cygpath --windows "${OUT_UNIX}" )
cmd /c CALL "$BIN_WIN" "${ARG_WIN}" > "$OUT_WIN"
The important point is the CALL in front of your command.
cmd /c fails to start a command with spaces inside, because it splits the command even when there are quotes around.
The cmd /c would also work without CALL, but with additional enclosing quotes.
cmd /c " "C:\Program Files (x86)\bin\script.bat" "args" "
But it seems to be impossible to transfer raw quotes from cygwin to cmd.exe, because cygwin translates quotes to \", so the expression becomes
cmd /c \" "C:\Program Files (x86)\bin\script.bat" "args" \"

proper way to remove double quotes from string in batch

I've got a batch script (app1.bat) calling another batch script (app2.bat) which itself calls a program in windows (program.exe).
app2.bat calls program.exe with a parameter after a flag in this way:
program.exe -f Parameter with whitespaces coming into the program
What I want to do is to pass the phrase that comes to program.exe from app1.bat into app2.bat but i don't know how to properly handle the doublequotes. Currently I am passing the phrase from app1.bat to app2.bat in double quotes and inside an app2.bat (prior to executing program.exe) I get rid of the quotes like that:
inside app1.bat
call app2.bat "Parameter with whitespaces coming into the program"
inside app2.bat
set old_phrase=%1%
set new_phrase=%old_phrase:"=%
program.exe -f %new_phrase%
old_phrase is
"Parameter with whitespaces coming into the program"
and new_phrase I end up with is
Parameter with whitespaces coming into the program
Is there any standard way to handle such a situation (being passing a string to an external program which expects a tring without quotes and being ok with whitespaces, whereas batch does not allow for no-quotes-and-whitespaces strings)
When you execute call /? from cmd to launch the help you will see quite a bit around expansion of %n
The first one states:
%~1 - expands %1 removing any surrounding quotes (")
You can therefore dump all the other set commands and simply run this in your batch file:
program.exe -f %~1

cmd wmic escaping nightmare in windows

I'm trying to start a process from cmd and return it's PID (process id).
The command works, but there is something wrong with the escaping because some command arguments make it fail.
The command i'm trying to run is this one:
start /B wmic process call create "\"d:\software\winnmp\bin\PHP\php-7.1.12/php.exe\" \"D:\projects\php\masked/artisan\" queue:work --queue=high,default,low" | find "ProcessId"
This will return "Invalid Verb Switch."
I narrowed it down to the comma sign in the high,default,low argument because if i run this one, it works as expected:
start /B wmic process call create "\"d:\software\winnmp\bin\PHP\php-7.1.12/php.exe\" \"D:\projects\php\masked/artisan\" queue:work --queue=high" | find "ProcessId"
Returns: "ProcessId = 1234"
I got the impression wmic is trying to interpret the --queue=high,default,low argument, so i guess it's not properly escaped. I also tried to wrap everything after the start /B in quotes, but i can't figure out how to do it. I tried escaping double quotes with "" and ^" and \" and with inner escpaced backslashes i tried \.
All paths do need to be in quotes because i don't know if they are going to contain spaces or not.

Windows echo command: The odds of cmd.exe escaping

I'm in the unfortunate position to be forced to invoke a program via echo <input> | program.exe. Of course, I wondered how to escape <input> and found:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Escape angle brackets in a Windows command prompt
In essence, it seems sufficient to escape all special chars with ^. Out of curiosity I still would like to know, why echo ingores double-quote escaping in the first place:
C:\>echo "foo"
"foo"
C:\>
Is there any normative reference?
Bonus question: How to echo the strings on and off with the echo command?
Edit: I just found this. I states that only |<> need to be escaped. However, expansion like %FOO% still work.
Special characters like ^, &, (, ), <, >, %, ! and " may cause problems with echo, also when trying to echo a string into a pipe; odd numbers of " are particularly difficult to handle.
Building escape sequences can be very complicated particularly with pipes, because such initiates new cmd instances for either side, so multi-escaping might become necessary.
The only reliable way to pipe the output of echo into a program is to use a variable holding the string to return and to apply delayed expansion, but within the left side of the pipe, like this:
cmd /V /C echo(^^!VARIABLE^^!| program.exe
Note the double-escaping of ! like ^^!, which makes this code even work when delayed expansion is also enabled in the parent cmd instance. There must not be a SPACE in front of the |, because this was echoed too otherwise. Note that echo terminates the output by a line-break.

What is `cmd /s` for?

The Windows command prompt (cmd.exe) has an optional /s parameter, which modifies the behavior of /c (run a particular command and then exit) or /k (run a particular command and then show a shell prompt). This /s parameter evidently has something to do with some arcane quote handling.
The docs are confusing, but as far as I can tell, when you do cmd /csomething, and the something contains quotation marks, then by default cmd will sometimes strip off those quotes, and /s tells it to leave them alone.
What I don't understand is when the quote removal would break anything, because that's the only time /s ("suppress the default quote-removal behavior") would be necessary. It only removes quotes under a certain arcane set of conditions, and one of those conditions is that the first character after the /c must be a quotation mark. So it's not removing quotes around arguments; it's either removing quotes around the path to the EXE you're running, or around the entire command line (or possibly around the first half of the command line, which would be bizarre).
If the path to the EXE is quoted, e.g. cmd /c "c:\tools\foo.exe" arg1 arg2, then quotes are unnecessary, and if cmd wants to remove them, fine. (It won't remove them if the path has a space in the name -- that's another of the arcane rules.) I can't imagine any reason to suppress the quote removal, so /s seems unnecessary.
If the entire command line is quoted, e.g. cmd /c "foo.exe arg1 arg2", then it seems like quote removal would be a necessity, since there's no EXE named foo.exe arg1 arg2 on the system; so it seems like opting out of quote removal using /s would actually break things. (In actual fact, however, it does not break things: cmd /s /c "foo.exe arg1 arg2" works just fine.)
Is there some subtlety to /s that's eluding me? When would it ever be necessary? When would it even make any difference?
Cmd /S is very useful as it saves you having to worry about "quoting quotes". Recall that the /C argument means "execute this command as if I had typed it at the prompt, then quit".
So if you have a complicated command which you want to pass to CMD.exe you either have to remember CMD's argument quoting rules, and properly escape all of the quotes, or use /S, which triggers a special non-parsing rule of "Strip first and last " and treat all other characters as the command to execute unchanged".
You would use it where you want to take advantage of the capabilities of the CMD shell, rather than directly calling another program. For example environment variable expansion, output or input redirection, or using CMD.exe built-ins.
Example:
Use a shell built-in: This executes as-if you had typed DEL /Q/S "%TMP%\TestFile" at the prompt:
CMD.exe /S /C " DEL /Q/S "%TMP%\TestFile" "
This executes SomeCommand.exe redirecting standard output to a temp file and standard error to the same place:
CMD.exe /S /C " "%UserProfile%\SomeCommand.exe" > "%TMP%\TestOutput.txt" 2>&1 "
So what does /S give you extra? Mainly it saves you from having to worry about quoting the quotes. It also helps where you are unsure whether for example an environtment variable contains quote characters. Just say /S and put an extra quote at the beginning and end.
Vaguely Related: $* in Bourne Shell.
Some background
Recall that the list of arguments to main() is a C-ism and Unix-ism. The Unix/Linux shell (e.g. Bourne Shell etc) interprets the command line, un-quotes the arguments, expands wildcards like * to lists of files, and passes a list of arguments to the called program.
So if you say:
$ vi *.txt
The vi command sees for example these arguments:
vi
a.txt
b.txt
c.txt
d.txt
This is because unix/linux operates internally on the basis of "list of arguments".
Windows, which derives ultimately from CP/M and VAX, does not use this system internally. To the operating system, the command line is just a single string of characters. It is the responsibility of the called program to interpret the command line, expand file globs (* etc) and deal with unquoting quoted arguments.
So the arguments expected by C, have to be hacked up by the C runtime library. The operating system only supplies a single string with the arguments in, and if your language is not C (or even if it is) it may not be interpreted as space-separated arguments quoted according to shell rules, but as something completely different.
Here's an example of how it can make a difference.
Suppose you have two executables: c:\Program.exe and c:\Program Files\foo.exe.
If you say
cmd /c "c:\Program Files\foo"
you'll run foo.exe (with no arguments) whereas if you say
cmd /s /c "c:\Program Files\foo"
you'll run Program.exe with Files\foo as the argument.
(Oddly enough, in the first example, if foo.exe didn't exist, Program.exe would run instead.)
Addendum: if you were to type
c:\Program Files\foo
at the command prompt, you would run Program.exe (as happens with cmd /s /c) rather than foo.exe (as happens with just cmd /c). So one reason for using /s would be if you want to make sure a command is parsed in exactly the same way as if it were being typed at the command prompt. This is probably more likely to be desirable in the scenario in the question Michael Burr linked to, where cmd.exe is being launched by CreateProcess rather than from a batch file or the command line itself..
That is, if you say
CreateProcess("cmd.exe", "cmd /s /c \"" MY_COMMAND "\"", ...)
then the string MY_COMMAND will be parsed exactly as if it were typed at the command prompt. If you're taking command-line input from the user, or if you're a library processing a command line provided by an application, that's probably a good idea. For example, the C runtime library system() function might be implemented in this way.
In all but one specific case, the /S won't actually make any difference.
The help for cmd.exe is accurate, if a bit complicated:
If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:
If all of the following conditions are met, then quote characters
on the command line are preserved:
no /S switch
exactly two quote characters
no special characters between the two quote characters,
where special is one of: &<>()#^|
there are one or more whitespace characters between the
two quote characters
the string between the two quote characters is the name
of an executable file.
Otherwise, old behavior is to see if the first character is
a quote character and if so, strip the leading character and
remove the last quote character on the command line, preserving
any text after the last quote character.
I'd summarize as follows:
Normal behavior:
If the rest of the command line after /K or /C starts with a quote, both that quote and the final quote are removed. (See exception below.) Other than that, no quotes are removed.
Exception:
If the rest of the command line after /K or /C starts with a quote, followed by the name of an executable file, followed by another quote, AND if those are the only two quotes, AND if the file name contains spaces but contains no special characters, then the quotes are not removed (even though they normally would have been removed according to the rule above).
The only effect of /S is to override this one exception, so that the two quote characters are still removed in that case.
If you always use /S, you can forget about the exception and just remember the "normal" case. The downside is that cmd.exe /S /C "file name with spaces.exe" argument1 won't work without adding an extra set of quotes, whereas without /S it would have worked... until you decide to replace argument1 with "argument1".

Resources