Remove quotation marks from invocation parameter - cmd

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 ?

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.

batch: How to write text to file without expanding the variables

I'm trying to write a text to a file using a batch file:
start https://www.google.com/search?q=%clipboard%%"
#echo off
set "Text=start https://www.google.com/search?q=%clipboard%%"
set "Text=%Text:^%=%%%"
echo %Text%>>lala.txt
But what is writen to the text file is only:
start https://www.google.com/search?q=%
The idea is to duplicate the percent signs so they are escaped and seen as text only but I guess for some reaseon the batch still things it's a variable. I know I could just use:
echo start https://www.google.com/search?q=%%clipboard%%%%"
But let's say I don't know what the line looks like I only know it has percent signs in it how should I proceed? The only thing I want to do is write the line as is into a text file without any manipulation.
When you define a literal string in a batch file, like set "Text=A single %%-sign" or echo A single %%-sign, you always have to manual double %-signs; otherwise, the %-expansion phase consumes them and tries to expand variables (refer to this answer for more details).
However, when the input text comes from somewhere else, like user input (set /P Text="Enter text: ") or from a file (e. g., read by for /F) you do not have to manually double %-signs, because the %-expansion phase is already completed when the text arrives.
This is the original answer before I recognised the real problem:
Well, the following line in your code cannot work:
set "Text=%Text:^%=%%%"
Because, besides the fact that it would actually replace ^% rather than %, the %-sign behind the =-sign finishes this sub-string substitution expression, and the remaining %% become then replaced by a single literal % (refer to this answer for more details).
To double %-signs in an arbitrary string you need to enable delayed expansion, because this uses ! instead of % to mark variables, which do not interfere with the literal %-signs you want to replace/double:
#echo off
rem /* You have to double `%`-signs when you put a literal string here; so
rem this literaly sets `https://www.google.com/search?q=%clipboard%`: */
set "Text=https://www.google.com/search?q=%%clipboard%%"
rem // Enable delayed expansion:
setlocal EnableDelayedExpansion
rem // Literal `%`-signs still have to be doubled here:
set "Text=!Text:%%=%%%%!"
rem // Return the string with `%`-signs doubled:
echo Delayed expansion: !Text!
echo Normal expansion: %Text%
set Text & rem // (avoiding `echo` here to review the true variable value)
rem // Variables set/changed since `setlocal` become lost past this point:
endlocal

Difference between %variable% and !variable! in batch file

I am writing a batch file where I need to output a string containing '!' to another file. But when I echo that string to another file, it removes "!" from the output.
Eg:
Input:
set LINE=Hi this is! output
echo !LINE!>>new_file.txt
Output in new_file.txt is:
Hi this is output
Also, if input is
set LINE=Hello!! this is output!!
echo !LINE!>>new_file.txt
Output in new_file.txt:
Hello
Hence, it skips the ! (Exclamation mark) from the output to the new_file.
If I use %LINE%, then it simply displays "echo is on" to the output file.
Please suggest a way to overcome this problem.
If you have delayed expansion enabled and want to output an exclamation mark, you need to escape it.
Escaping of exclamation marks needs none, one or two carets, depending on the placement.
#echo off
REM No escaping required, if delayed expansion is disabled
set test1=Test1!
setlocal EnableDelayedExpansion
REM One caret required
REM Delayed expansion uses carets independent of quotes to escape the exclamation mark
set "test2=Test2^!"
REM Two carets required
REM The first caret escapes the second caret in phase2 of the parser
REM Later in the delayed expansion phase, the remaining caret escapes the exclamation mark
set test3=Test3^^!
echo !test1!
echo !test2!
echo !test3!
The difference between !var! and %var% in blocks is explained at DOS batch: Why are my set commands resulting in nothing getting stored?
An explanation of the batch parser can be found at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
It seems you have called SETLOCAL EnableDelayedExpansion somewhere higher in the code. Take a look here to see what the effects from that are.

Remove enclosing double quotes from a parameter passed to a batch file

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.

Spaces in batch script arguments

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.

Resources