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

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

Related

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.

Percent sign disappears when passed by CALL

This question is originally coming from Escape percent signs in given variables. I do not want to disarrange the good answer over there. But my issue changed a little bit...
Let's assume there is a given string variable enclosed by double quotes which may include one or more percent signes. It is not possible to switch to enabled delayed expansion permanently (other code is already usable). Calling a function including the string variable as a parameter is necessary. This is what I determined so far:
#echo off & setlocal ENABLEEXTENSIONS
SET AlbumArtist=%1
CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN
echo %AlbumArtist_VDN%
CALL :EscapePoisonChars %%AlbumArtist%% AlbumArtist_VDN
echo %AlbumArtist_VDN%
endlocal &GOTO:EOF
:EscapePoisonChars
#echo off & setlocal ENABLEEXTENSIONS
SET TmpString="%~1"
SET TmpString=%TmpString:&=^^^&%
SET TmpString=%TmpString:(=^^^(%
SET TmpString=%TmpString:)=^^^)%
endlocal&SET %2=%TmpString:~1,-1%&GOTO :EOF
I know that this is probably not a "clean solution". But I would like to understand why when the routine is invoked by CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN the percent sign disappears. When called with the string variable %%AlbumArtist%% enclosed by doubled percent signs it gives the wanted output:
D:\Batch>PercentTwins.bat "100% Rock & Roll"
100 Rock & Roll
100% Rock & Roll
D:\Batch>
Why there is a different result if %AlbumArtist% is expanded in- or outside the function :EscapePoisonChars? With echo on I see that the percent sign just disappears with SET TmpString="~1". Any explanations will help me to improve my further cmd techniques. Thanks!
Anyone correct me if I am wrong, but I think when single percent signs in one command are passed on to another command, they will disappear. If it is just used within the same command, it normally will not disappear.
(There is probably a more 'correct' way of saying this, or this idea might be wrong altogether)
(some info on percent signs at Microsoft Support website)
The call command initiates the % parsing phase a second time (reference the accepted answer to the question post How does the Windows Command Interpreter (CMD.EXE) parse scripts?); this lets the % signs disappear. You could double the % signs in advance, but you would need to enable delayed expansion for that, like this:
#echo off & setlocal ENABLEEXTENSIONS EnableDelayedExpansion
SET AlbumArtist=%1
set AlbumArtist=!AlbumArtist:%%=%%%%!
CALL :EscapePoisonChars %AlbumArtist% AlbumArtist_VDN
echo %AlbumArtist_VDN%
endlocal &GOTO:EOF
In your second sub-routine call, the two parsing phases do not ever see the % signs of the original string; the first phase expands %%AlbumArtist%% to %AlbumArtist% literally, the second phase expands it to 100% Rock & Roll.
But:
There is no need for all that, you do not need the sub-routine at all, when you stick to the only safe set syntax and ensure to always have the string quoted properly:
set "AlbumArtist=%~1"
%1 would return the string as given; %~1 removes potential surrounding quotation marks. Enclosing the entire assignment expression within quotation marks makes the syntax robust against poisonous characters by not considering the "" as part of the assigned string itself.
When using/returning the string (by echo as in the example here), you need to enclose the string within quotes again to maintain safety, in case you are using normal (immediate) % expansion:
set "AlbumArtist=%~1"
echo "%AlbumArtist%"
Alternatively, delayed expansion makes reading variables safe even without quotation marks:
set "AlbumArtist=%~1"
setlocal EnableDelayedExpansion
echo !AlbumArtist!
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.

Batch String Concatenation

I am trying to create a batch string like this: >abcd_
I have a variable called soeid, with value as abcd. So this is what i am doing, but it does not work.
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo %str%
I'm sure it is working just fine. To prove it, add SET STR after you define the value, and you will see the correct value.
The problem you are having is when you try to echo the value, the line that is executing becomes: echo >abcd_. The > is not quoted or escaped, so it is simply taking the ouput of ECHO with no arguments and redirecting it to a file named "abcd_"
If you don't mind seeing quotes, then change your line to echo "%str%" and it will work.
The other option is to enable and use delayed expansion (I'm assuming this is a batch script code, and not executing on the command line)
setlocal enableDelayedExpansion
set soeid=abcd
set "val1=>"
set "val2=_"
set "str=%val1%%soeid%%val2%"
echo !str!
Normal %var% expansion occurs early on while the interpreter is parsing the line. Delayed !var! expansion occurs at the end just before it is executed. The redirection is detected somewhere in the middle. That is why the normal expansion doesn't work - the interpreter sees the expanded > character and interprets it as the output redirection operator. The delayed expansion hides the > character from the interpreter until after redirection is parsed.
For more info about delayed expansion, type SET /? from the command line and read starting with the paragraph that starts with "Finally, support for delayed environment variable expansion...".

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