I am calling a batch file like this:
test.bat C:\
The C:\ parameter is passed to a command within the batch file like this:
start program.bat "%1"
I am finding that program.bat is starting like this:
program.bat "C:\"
Is it possible to remove the enclosing quotation marks from the parameter so that program.bat receives C:\ instead of "C:\"?
%1 evaluates to the first parameter as-is. That is, if the parameter is enclosed in quotation marks, they will be preserved.
%~1 strips the quotation marks before evaluating.
So, use %~1 in program.bat where you need to use the value of the first parameter without quotation marks.
Related
I'm having trouble getting a FOR loop to read a quoted path as a file-set. I have a script similar to this, designed to process every line of the files I drag and drop onto it:
#ECHO OFF
FOR %%F IN (%*) DO (
ECHO %%F
FOR /F "DELIMS=" %%A IN (%%F) DO (
ECHO %%A
)
)
PAUSE
The first ECHO shows the file path. If it contains a space, there will be double-quotes around the path. The second ECHO shows each line in the file, unless the file path had a space. Then instead of lines, it's just the path again, but without quotes.
This makes sense to me after reading Windows' FOR documentation, which shows if quotes are given, it's read as a string, not a file.
FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]
FOR /F ["options"] %variable IN ("string") DO command [command-parameters]
But some paths have spaces and presumably need quotes. How can I force FOR to interpret those as files?
I tried passing %%~f to the second FOR loop to always strip quotes, but then FOR complained it could not find the file, because the path was cut off at the first space. That's when I added the DELIMS option, to stop reading spaces as delimiters, but there was no change.
Read the same documentation and look at the usebackq options.
usebackq - specifies that the new semantics are in force,
where a back quoted string is executed as a
command and a single quoted string is a
literal string command and allows the use of
double quotes to quote file names in
file-set.
A batch file can be called with multiple arguments which can be enclosed in " as required on an argument string like a file name contains a space or one of these characters &()[]{}^=;!'+,`~ (or literally to interpret <>| like in a password string), but can be also passed to the batch file without surrounding double quotes. That must be taken into account on processing the batch file arguments like file names by explicitly removing surrounding " from each argument string and reference the resulting file name string with always enclosing it in " to have finally every file name enclosed in double quotes.
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
FOR %%I IN (%*) DO (
ECHO "%%~I"
FOR /F usebackq^ delims^=^ eol^= %%J IN ("%%~I") DO ECHO(%%J
)
ENDLOCAL
PAUSE
The first two command lines define completely the required execution environment which is:
command echo mode turned off to prevent output of each command before execution,
command extensions enabled as required for this batch file for %* and FOR /F,
delayed variable expansion disabled to process correct all file names even those with an exclamation mark and all lines in the files even those with an exclamation mark.
The outer FOR loop assigns one argument string after the other to the loop variable I exactly as passed to the batch file on starting it which means without or with surrounding double quotes.
The first ECHO outputs the argument string being hopefully the file name of a text file always enclosed in double quotes independent on file name passed to the batch file without or with surrounding double quotes.
The inner FOR loop with option /F assigns each non-empty line to the loop variable J and runs one more ECHO to just output the non-empty line.
FOR /F interprets by default a string in double quotes as string to process. The usage of the option usebackq changes this behavior. A string in double quotes is now interpret as file name of a text file of which lines should be processed one after the other. The file name passed as argument string to the batch file is always enclosed in " because of using "%%~I" which references the argument string assigned to loop variable I with removal of surrounding " and explicitly enclose the resulting file name string in " by the code in the batch file.
FOR /F always ignores empty lines which means lines not containing any character other than the line termination characters carriage return and line-feed. A text file is also processed correct on lines are terminated just with a line-feed (UNIX format). A carriage return is only ignored by FOR on being in byte stream of the file before the line-feed. Otherwise the carriage return is not interpreted as line termination and becomes therefore part of the line to process further.
FOR /F splits up by default a line into substrings (tokens) using normal space and horizontal tab character as string delimiters whereby leading spaces/tabs are removed from each line. Then is checked if the first character of first substring starts with a semicolon being the default end of line character in which case the line is also ignored for further processing independent on option tokens= if that option is also used which is not the case here. Otherwise without usage of option tokens= only the first space/tab delimited string is assigned to the specified loop variable for further processing by the command(s) in the body of the FOR loop.
delims= turns off the line splitting behavior by the definition of an empty list of delimiters. eol= defines no character as end of line character. Both together results in processing all lines in file even blank lines containing only spaces/tabs with the exception of empty lines.
The only possible syntax to define the FOR /F options delims= and eol= together with no delimiter and no end of line character is the used syntax with not enclosing the three options in " as usual as the usage of "usebackq delims= eol=" would result in " being interpreted as end of line character. A normal space, an equal sign, a comma, a semicolon and an OEM encoded no-break space found by cmd.exe on a command line not within an argument string enclosed in " is interpreted as argument string separator. But usebackq delims= eol= should be interpreted by cmd.exe as one argument string to pass to its internal command FOR. The solution is to escape the two spaces and the two equal signs with ^ to get them interpreted as literal characters and not as argument string separators by cmd.exe.
The command ECHO with just spaces/tabs appended would output the current status of command echo mode instead of the spaces/tabs. ECHO(%%J is used instead of ECHO %%J to prevent an output of command echo mode status if the line assigned to loop variable J consists of only spaces/tabs. The opening round bracket is interpreted as argument string separator in this case between the command ECHO and the string to output which is the non-empty line read from the file.
To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
echo /?
endlocal /?
for /?
pause /?
setlocal /?
See also:
DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ for a full explanation for the usage of ECHO( in this special use case.
Issue 7: Usage of letters ADFNPSTXZadfnpstxz as loop variable in this answer for an explanation why the letters F and A are not used in the code above as loop variables although both could be used here too.
I want to process files in a subroutine by passing a filename and adding path + extension
This is what I have coded
echo %0 %1
setlocal EnableDelayedExpansion
set "input=P:\Convert\%1.ts"
echo.Process "!input!"
If the filename contains no blanks I dont need quotation marks. But if it includes blanks, then I must enclose the parameter in quotation marks to pass it to the subroutine. Here is what I get:
sample test
Process "P:\Convert\test.ts" <-- this is OK
sample "test file"
Process "P:\Convert\"test file".ts" <-- NOT OK
How do I get rid of these quotation marks ?
How do you pass quoted arguments to an executable in a single-quoted evaluation such as FOR /F "delims=" %%i IN ('"executable path" arg1 "arg2 which contains spaces"') do ...?
As per many of the answers here, I'm trying to use the output of a console app in a Windows batch script, using the single quotes to get the console to evaluate it.
However, I need to quote some of the arguments I want to pass to that executable, which also needs to be quoted, as the path contains spaces as well.
But when I do that, the quoting around the executable path breaks.
Here is the would-be line:
FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^="!album!"') DO set "falbum=%%i"
(Both !PathToExe! and !album! contain spaces. It seems like I need to escape the equal signs here, hence the circumflexes. Delayed expansion is on)
The above results in "Outcome A": Quoting is broken for the exe path
'<Part of path to exe>' is not recognized as an internal or external command, operable program or batch file.
I've tried different combinations of different quote usages and escapings, but haven't found a way to make it work.
Here are some attempts and their results:
FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=!album!') DO set "falbum=%%i")
No quotes around !album! results in "Outcome B": As expected, only the first word gets passed along with string=, all the other words get scattered as individual arguments.
FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=^"!album!^"') DO set "falbum=%%i")
FOR /F "delims=" %%i IN ('^"!PathToExe!^" action^=sanitize string^=^"!album!^"') DO set "falbum=%%i")
Trying to escape the quotes for the string= argument or both exe path and string arg: Outcome A (still breaks the quoting for the exe path, gives:)
'<Part of path to exe>' is not recognized as an internal or external command, operable program or batch file.
FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^=^'!album!^'') DO set "falbum=%%i")
Using escaped single quotes: Outcome B again
FOR /F "delims=" %%i IN ('"!PathToExe!" action^=sanitize string^='"!album!") DO set "falbum=%%i")
Ending the single quote before the string= value and simply having it in quotes after that seems to result in everything being taken as a single first argument (command/path):
The system cannot find the file '"<path to exe>" action=sanitize string='"<Album var with spaces and whatnot>".
It does not matter whether the quotes are part of the variables or literally typed in the IN ('...') line.
Simple testing:
You could test this behavior if you copied %SystemRoot%\System32\cmd.exe to a directory with spaces, e.g. C:\folder with spaces\ and pasted the following script there:
#echo off
setlocal EnableDelayedExpansion
set PathToExe="%~dp0cmd.exe"
REM Toggle the next line to compare between quoted path with spaces and path without quotes or spaces:
REM set PathToExe=cmd.exe
set string=%~dp0
FOR /F "delims=" %%i IN ('!PathToExe! /C CD C:\windows\system32 ^& dir !string!') DO set "fstring=%%i"
echo !fstring!
pause
This should illustrate the challenge of having two quoted sections in one statement.
If the !string! variable remains unquoted, you'll get "The system cannot find the file specified.".
If the quotes of the !PathToExe! variable break, you'll see something like "'C:\folder' is not recognized as an internal or external command, operable program or batch file.".
The for /F command, when used to capture and parse command output, actually uses cmd /C to execute that command, which handles quotation marks in a particular way. From its help (cmd /?):
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:
1. 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.
2. 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.
This means that:
for /F "delims=" %%I in ('"executable path" arg1 "arg2 which contains spaces"') do (…)
actually tries to execute the command line:
cmd /C "executable path" arg1 "arg2 which contains spaces"
leading to:
executable path" arg1 "arg2 which contains spaces
which is obviously invalid syntax.
To overcome this issue provide an additional pair of quotes:
for /F "delims=" %%I in ('^""executable path" arg1 "arg2 which contains spaces"^"') do (…)
It is a good idea to escape these additional quotes (by ^"), so there is no need to alter any potential escape sequences.
I want to put the following content into the file: <ScriptFile Make="3">
It fails for the reason of the string containing the angle brackets < and > and the double quote character ".
I have tried escaping the characters following way: ^<ScriptFile Make=""3""^>
It worked, but the output in the file was exactly the same as the escaped string.
The code snippet:
#echo off
set TEMP="^<ScriptFile Make=""3""^>"
echo %TEMP% > gen.xml
pause
How can I output the string value of TEMP variable into file gen.xml without loosing the double quotes and the angle brackets?
You can extract the angle brackets out of the variable, like this:
#echo off
set TEMP1=ScriptFile Make="3"
echo ^<%TEMP1%^> > gen.xml
pause
This way, the brackets can be escaped properly, you do not need any special escaping for the string put in the variable and the gen.xml looks like expected:
D:\temp>type gen.xml
<ScriptFile Make="3">
This worked for me:
#echo off
set "TEMP=^<ScriptFile Make="3"^>"
echo %TEMP% > gen.xml
pause
Another method would be to use delayed expansion:
#echo off
set "TEMP=<ScriptFile Make="3">"
setlocal EnableDelayedExpansion
echo !TEMP! > gen.xml
endlocal
pause
TEMP should not be used as environment variable name because TEMP is an important environment variable predefined by Windows. It has as value the name of the directory for temporary files of current user account with complete path. For details see Wikipedia article Windows Environment Variables.
One method is using delayed expansion as suggested also by Andriy M.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "TempVar=<ScriptFile Make="3">"
echo !TempVar!>gen.xml
endlocal
First a local environment is created for the next two command lines with command extensions and delayed expansion of environment variables enabled which are both needed here. Command extensions are enabled by default, but delayed expansion is disabled by default. See this answer explaining in detail what the commands SETLOCAL and ENDLOCAL do.
The string <ScriptFile Make="3"> is assigned next to environment variable TempVar. There is no need to escape the angle brackets or the double quotes. For a detailed explanation why there is no need to escape anything in this string and why first double quote character is left of variable name and not after equal sign read this answer.
The value of the environment variable is output by command ECHO with redirecting this output with the redirection operator > into the file gen.xml using delayed expansion.
There is no space character between second exclamation mark ! and redirection operator >. This avoids writing also a trailing space after <ScriptFile Make="3"> into the file. 1 or more space characters between > and file name gen.xml are ignored on parsing this command line. But any whitespace character left of redirection operator > is also output by command ECHO and for that reason also written into the file.
Another method is not using an environment variable at all and escape the angle brackets with character caret ^ as demonstrated below:
#echo off
echo ^<ScriptFile Make="3"^>>gen.xml
endlocal
Double quotes must not be escaped on using command ECHO as in this special case the double quote characters are interpreted as literal characters.
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.
echo /?
endlocal /?
set /?
setlocal /?
And read also the Microsoft TechNet article Using command redirection operators.
I have a batch script which needs to perform an action on each of its arguments. Each argument is a file name (there are not switches), and of course file names may contain spaces. The batch script is run either by dragging files into the .bat icon in Explorer or by entering the files at the command line, enclosing arguments with spaces in quotes.
Within the batch script, there are problems with handling arguments with spaces. If I use %* as follows, the quotations are ignored and each 'word' between spaces is treated as an argument.
for %%x in (%*) do (
echo %%x
)
I have also tried using shift, which doesn't seem to work right either, choking on files with spaces in their name:
:next
if not %1 == "" (
echo %1
shift /1
goto next
)
What is the ideal way to iterate through all arguments?
In Bash, one would simply use "$#" and everything Just Works™, but of course that doesn't seem to be the case with Windows batch scripts.
The substitution modifiers for for variable references also allow for using the ~ expansions. See for command reference.
By using "%%~x" you should get a properly quoted parameter, similar to how bash handles "$#".
#echo off
setlocal enableextensions
for %%x in (%*) do (
echo "%%~x"
)
The characters , and ; can be used to separate command parameters. See command shell overview. Thus you have to put quotes around file names that contain these characters.
If you drag a file from the Explorer onto the .bat, Explorer will only quote the file correctly if it has a white space character in its path. E.g., D:\a,b,c.exe will not be quoted by Explorer and thus will be parsed as three separate arguments by cmd.exe.
To make the script work with drag and drop from the Explorer for these freak cases, you can use the following (ugly) work-around:
#echo off
setlocal enableextensions enabledelayedexpansion
set "args=%*"
set "args=%args:,=:comma:%"
set "args=%args:;=:semicolon:%"
for %%x in (%args%) do (
set "filepath=%%~x"
set "filepath=!filepath::comma:=,!"
set "filepath=!filepath::semicolon:=;!"
echo "!filepath!"
)
The script introduces a helper variable args, where each occurrence of a troublesome character is replaced with a placeholder (note that the colon character itself cannot be used in a file name under Windows).
The body of the for loop uses another helper variable filepath which undos the transformation to produce the original path.
I had a similar issue with file names that contain equal signs (=), which causes the file name to be split into multiple arguments. I solved it by using "%*".
If you have a file with spaces, e.g. foo bar baz.txt, this will be quoted twice: ""foo bar baz.txt"". Now the double double-quotes are escaped: foo bar baz.txt, resulting in %1 = foo, %2 = bar, and %3 = baz.txt. So this does not work.
If you have a file with spaces AND/OR equal signs, you can use:
set input=""%*""
set input=%input:"=%
your_program "%input%"
Now, foo bar=baz.txt will be quoted thrice: """foo bar=baz.txt""". Two quotes will be escaped and input becomes "foo bar=baz.txt". With the second line, double-quotes are replaced by nothing (removed). You need to put the quotes around input again when you enter it into your_program, otherwise it will see spaces as separate input!
If you only have equal signs, then ""%*"" makes foo=bar=baz.txt into ""foo=bar=baz.txt"", which enters your program as %1 = foo=bar=baz.txt.