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.
CodePudding user response:
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.
CodePudding user response:
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
%*
andFOR /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
andA
are not used in the code above as loop variables although both could be used here too.