Spaces in batch script arguments - cmd

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.

Related

Batch for-loop paths with quotes are strings instead of file-sets

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.

Get result of command with quoted arguments within single quote command evaluation in Windows batch

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.

cmd for loop mass renaming again oneliner

I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

How to output special characters like angle brackets into an HTML/XHTML/XML file?

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.

Return each item on a single line defined in %PATH% via windows command prompt

I would like to like to view each item on a single line that are defined in %PATH% environment variable.
I have used the following
for %A in ("%path:;=" "%") do #echo %A
It returns each item in a single line, but with double quotes around them as shown below.
"C:\Windows\System32"
"C:\Windows\system32\Wbem"
"C:\Windows\System32\WindowsPowerShell\v1.0\"
How do I print them without the double quotes? I tried using single quotes in the command, but couldn't get it right. (There are spaces in paths in some of the entires in %PATH%)
ECHO %path:;=&ECHO(%
:) :)
Edit for expansion.
The syntax %var:string1=string2% replaces all string1 with string2 within var hence this command is resolved to
echo directory1&echo(directory2&echo(directory3...
at each ; which is the separator used between directorynames in %path%.
The echo( is a trick construct. There are many characters which are ignored to one extent or another DIRECTLY following echo. SPACE is the most common, but the command ECHO will show the ECHO state - ECHO is on/off. ECHO( however will show nothing (other than any characters following the ( ).
As it happens, my path ends with ; so if I was to use &echo to replace ; then the line would be resolved to ....echo dirlast&echo and produce the ECHO state as a last line. Using echo( neatly overcomes this problem.

Resources