Dealing with potentially necessary escape characters - batch-file

Related: Using batch echo with special characters
How do I deal with using escape characters on text that might or mightn't be a special character?
Suppose we have user input:
Set /p var=prompt:
Now, I need to make sure that the text gets interpreted as text even if the user enters something like a special character. But I cannot simply add ^ before the variable...because that'd cancel the variable. The ^^%var% and ^%%var% options don't seem to work either.
How do I go about doing this?

You should realize that the escapes are required in the source code of your program or when you expand a variable via %variable% or in the replaceable parameter of a for command. This is not required if you expand a variable via !delayed! expansion. So, your problem may be easily solved this way:
setlocal EnableDelayedExpansion
Set /p var=prompt:
echo !var!
The standard method to avoid the problem when you read a file that may have special characters is enclosing the value of the replaceable parameter in quotes when the value is asigned with Delayed Expansion disabled, and then Enable Delayed Expansion in order to manage the value. Of course, this method forces to insert an endlocal command inside the loop:
for /F "delims=" %%a in (anyFile.txt) do (
set "line=%%a"
setlocal EnableDelayedExpansion
echo Line with special characters: !line!
endlocal
)

Not possible. The shell processes the user input before your script does. Your script won't even know the user typed an escape character.

Related

How to copy un-sanitized string to clipboard with batch script

I want to put a user inputted string which contains an ampersand into the clipboard using batch. I can modify the string, and I can get it to print to the terminal using setlocal EnableExtensions EnableDelayedExpansion
but I can't pipe it to the clipboard.
There is an in depth discussion here which talks about why pipes can break things, but I couldn't understand it well enough to get around my problem.
https://www.robvanderwoude.com/battech_inputvalidation_setp.php
setlocal EnableExtensions EnableDelayedExpansion
set /P "INPUT=Paste the stuff in the terminal please"
set "SEARCHTEXT=+"
set REPLACETEXT=%%2B
for /F "delims=" %%A in ("%INPUT%") do (
set "string=%%A"
set "modified=!string:%SEARCHTEXT%=%REPLACETEXT%!"
echo !modified! | clip
)
Because the string I'm trying to modify contains "&username" in it, the output I get is:
'username' is not recognized as an internal or external command,
operable program or batch file.
If I only echo !modified!, there are no errors. How can I get an arbitrary un-sanitized string into the clipboard?
The major problem in your code is the following line:
echo !modified! | clip
A pipe (|) creates a new cmd instance for either side. You have got delayed expansion enabled in your script, so the variable !modified! becomes expanded when the whole command line is parsed, then the pipe is executed, and then the new cmd instance for the left side receives the variable already expanded, including all potential poisonous characters, like &, for example.
To prevent !modified! to be expanded immediately, we need to escape the exclamation marks like ^^! (^^ becomes first escaped to a single ^, so ^! is left during the delayed expansion phase), which lets the ! be treated as a literal character and no variable expansion happens at first.
The new cmd instance (for the left side of the pipe in our situation) now has got delayed expansion disabled, so we need to explicitly instantiate another (nested) one with delayed expansion enabled (by cmd /V):
cmd /V /C echo(^^!modified^^!| clip
With this technique we force the variable !modified! to be expanded as late as possible, hence by the inner-most cmd instance, which avoids the expanded string to be received by any other instance, and therefore, poinsonous characters become hidden from the parser.
In addition, I used the safe echo variant echo(..., because echo ... might fail under certain circumstances (imagine ... is the literal string /?). Moreover, I removed the SPACE in front of |, because such would become echoed as well, unintentionally.

Problems with Ampersand

I'm making a batch using the current location to make some changes the problem is that this place may have the & and thus cause errors ... I wonder if there is a way to make a script that checks for the & in the variable and if you add has the ^&.
the way I am using this might be.
Set "Local_Script=%~dp0">nul 2>&1
Echo %Local_Script%> a00_Local.ini
Note: The txt file can only be with the way.
Escaping characters is fine when your code is supplying a string literal. But it is often impractical when dealing with existing strings contained within variables.
There are simpler solutions:
1) delayed expansion:
Note that you must assign batch arguments and/or FOR variables to environment variables prior to enabling delayed expansion. Otherwise values containing ! will be corrupted.
#echo off
set "local_script=%~dp0"
setlocal enableDelayedExpansion
echo !local_script!>a00_local.ini
2) transfer the quoted value to a simple FOR variable, then use the ~ modifier to safely remove the quotes:
#echo off
for %%F in ("%~dp0") do echo %%~F>a00_local.ini
3) transfer the quoted string value to a FOR /F variable, which automatically removes the quotes:
#echo off
for /f "delims=" %%F in ("%~dp0") do eho %%F>a00_local.ini

User Input with special character

I'm currently using set /P input = to get the users input for a batch script.
My problem now is that it is very likely for them to use the & character.
Is there any way to get user input containing these special characters?
Sure. Use setlocal enabledelayedexpansion, then refer to your input variable as !input! whenever needed. Enclosing your "var=prompt: " string in quotations is always good practice, too.
#echo off
setlocal enabledelayedexpansion
set /P "data=Enter a string to test: "
echo(!data!
If you run that and enter a string with an ampersand, it gets echoed without being evaluated.
(Note about using echo(!data! rather than echo !data!: That's just a way to prevent a user entry of /? from breaking things.)

How to split string without for loop in batch file

I want to split a string in two parts, without using any for loop.
For example, I have the string in a variable:
str=45:abc
I want to get 45 in a variable and abc in another variable. Is it possible in batch file?
pattern is like somenumber:somestring
You could split the str with different ways.
The for loop, you don't want use it.
The trailing part is easy with the * (match anything until ...)
set "var2=%str:*:=%"
The leading part can be done with a nasty trick
set "var1=%str::="^&REM #%
The caret is needed to escape the ampersand,
so effectivly the colon will be replaced by "&REM #
So in your case you got the line after replacing
set "var1=4567"&REM #abcde
And this is splitted into two commands
set "var1=4567"
REM #abcde`
And the complete code is here:
set "str=4567:abcde"
echo %str%
set "var1=%str::="^&REM #%
set "var2=%str:*:=%"
echo var1=%var1% var2=%var2%
Edit 2: More stable leading part
Thanks Dave for the idea to use a linefeed.
The REM technic isn't very stable against content with quotes and special characters.
But with a linefeed trick there exists a more stable version which also works when the split argument is longer than a single character.
#echo off
setlocal enableDelayedExpansion
set ^"str=456789#$#abc"
for /F "delims=" %%a in (^"!str:#$#^=^
!^") do (
set "lead=%%a"
goto :break
)
:break
echo !lead!
Solution 3: Adpated dbenhams answer
Dbenham uses in his solution a linefeed with a pipe.
This seems a bit over complicated.
As the solution uses the fact, that the parser removes the rest of the line after an unescaped linefeed (when this is found before or in the special character phase).
At first the colon character is replaced to a linefeed with delayed expansion replacement.
That is allowed and the linefeed is now part of the variable.
Then the line set lead=%lead% strips the trailing part.
It's better not to use the extended syntax here, as set "lead=%lead%" would break if a quote is part of the string.
setlocal enableDelayedExpansion
set "str=45:abc"
set ^"lead=!str::=^
!"
set lead=%lead%
echo "!lead!"
You can try this . If its fixed that numbers to left of the colon will be always 2 & to the right will be 3. Then following code should work assuming your str has the value.
set "str=45:abc"
echo %str%
set var1=%str:~0,2%
set var2=%str:~3,3%
echo %var1% %var2%
Keep me posted. :)
It seems pointless to avoid using a FOR loop, but it does make the problem interesting.
As jeb has pointed out, getting the trailing part is easy using !str:*:=!.
The tricky bit is the leading part. Here is an alternative to jeb's solution.
You can insert a linefeed into a variable in place of the : using the following syntax
setlocal enableDelayedExpansion
set "str=45:abc"
echo !str::=^
!
--OUTPUT--
45
abc
The empty line above the last ! is critical.
I'm not sure why, but when the output of the above is piped to a command, only the first line is preserved. So the output can be piped to a FINDSTR that matches any line, and that result directed to a file that can then be read into a variable using SET /P.
The 2nd line must be eliminated prior to using SET /P because SET /P does not recognize <LF> as a line terminator - it only recognizes <CR><LF>.
Here is a complete solution:
#echo off
setlocal enableDelayedExpansion
set "str=45:abc"
echo(!str::=^
!|findstr "^" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1! var2=!var2!
Update
I believe I've mostly figured out why the 2nd line is stripped from the output :)
It has to do with how pipes are handled by Windows cmd.exe with each side being processed by a new CMD.EXE thread. See Why does delayed expansion fail when inside a piped block of code? for a related question with a great answer from jeb.
Just looking at the left side of the piped command, I believe it is parsed (in memory) into a statement that looks like
C:\Windows\system32\cmd.exe /S /D /c" echo {delayedExpansionExpression}"
I use {delayedExpansionExpression} to represent the multi-line search and replace expansion that has not yet occurred.
Next, I think the variable expression is actually expanded and the line is broken in two by the search and replace:
C:\Windows\system32\cmd.exe /S /D /c" echo 43
abc"
Only then is the command executed, and by normal cmd.exe rules, the command ends at the linefeed. The quoted command string is missing the end quote, but the parser doesn't care about that.
The part I am still puzzled by is what happens to the abc"? I would have thought that an attempt would be made to execute it, resulting in an error message like 'abc"' is not recognized as an internal or external command, operable program or batch file. But instead it appears to simply get lost in the ether.
note - jeb's 3rd comment explains why :)
Safe version without FOR
My original solution will not work with a string like this & that:cats & dogs. Here is a variation without FOR that should work with nearly any string, except for string length limits and trailing control chars will be stripped from leading part.
#echo off
setlocal enableDelayedExpansion
set "str=this & that:cats & dogs"
set ^"str2=!str::=^
!^"
cmd /v:on /c echo ^^!str2^^!|findstr /v "$" >test.tmp
<test.tmp set /p "var1="
del test.tmp
set "var2=!str:*:=!"
echo var1=!var1! var2=!var2!
I delay the expansion until the new CMD thread, and I use a quirk of FINDSTR regex that $ only matches lines that end with <cr>. The first line doesn't have it and the second does. The /v option inverts the result.
Yes, I know this is a very old topic, but I just discovered it and I can't resist the temptation of post my solution:
#echo off
setlocal
set "str=45:abc"
set "var1=%str::=" & set "var2=%"
echo var1="%var1%" var2="%var2%"
You may read full details of this method here.
In the Light of people posting all sorts of methots for splitting variables here i might as well post my own method, allowing for not only one but several splits out of a variable, indicated by the same symbol, which is not possible with the REM-Method (which i used for some time, thanks #jeb).
With the method below, the string defined in the second line is split into three parts:
setlocal EnableDelayedExpansion
set fulline=one/two/three or/more
set fulline=%fulline%//
REM above line prevents unexpected results when input string has less than two /
set line2=%fulline:*/=%
set line3=%line2:*/=%
set line1=!fulline:/%line2%=!
set line2=!line2:/%line3%=!
setlocal DisableDelayedExpansion
echo."%line1%"
echo."%line2%"
echo."%line3%"
OUTPUT:
"one"
"two"
"three or/more//"
i recommend using the last so-created partition of the string as a "bin" for the remaining "safety" split-characters.
Here's a solution without nasty tricks for leading piece
REM accepts userID#host
setlocal enableDelayedExpansion
set "str=%1"
set "host=%str:*#=%"
for /F "tokens=1 delims=#" %%F IN ("%str%") do set "user=%%F"
echo user#host = %user%#%host%
endlocal

Using inputs from a batch file to create a new, saved as a different name batch file

I am trying to create a batch file to input 3 pieces of data and use that data to create another batch file. Just create it, and stop. The batch maps several network drives for users that haaven't a clue as to how to do it.
I have a "master.bat" and using notepad I am using "replace" to fill in the "username" "Password" and "drive path". I thought I would try to get it down to entering the variables into the "master.bat" creating a "custom.bat" for that user.
I got a lot of help here getting to the final step. Everything is working except the final part. Now that I have all the variables as well as a template to put them in, how do I get that first batch file to create the cuctomized output as a workable file that I can send the user where all they do is run it.
One way would be to use your template in file form and replace placeholders in there by your actual values:
setlocal enabledelayedexpansion
for /f %%L in (template.cmd) (
set "Line=%%L"
set "Line=!Line:[username]=!username!"
...
>network-drives.cmd echo !Line!
)
This assumes placeholders like [username] in the template and corresponding variables defined.
However, I always get a little anxious if I use data read from a file in a batch. When I recently had to create a batch file from another I went the following route:
(
echo #echo off
echo net use !drivepath! \\server\share "/user:!username!" "!password!"
echo net use !drivepath2! \\server\share2 "/user:!username!" "!password!"
) > network_drives.cmd
Care has to be taken with things like closing parentheses and several characters reserved for the syntax you may need in the generated batch file. But this approach is entirely self-contained, albeit a little harder to maintain.
It is simple to embed the template within your batch file. There are multiple ways to do this. One is to simply prefix each template line with :::. I chose that sequence because : is already used as a batch label and :: is frequently used as a batch comment.
Delayed expansion can be used to do your search and replace automatically!
There are just 3 special characters you need to worry about if you want to include them in your output. These special characters are probably not needed for the original question. But it is good to know how to handle them in a general sense.
An exclamation literal ! must be either escaped or substituted
A caret literal ^ can be escaped or substituted if it appears on a line with an exclamation. But it must not be escaped if there is not an exclamation on the line. Caret substitution is always safe.
Use substitution to start a line with :
#echo off
setlocal
::The following would be set by your existing script code
set drivePath=x:
set username=rumpelstiltskin
set password=gold
::This is only needed if you want to include ! literals using substitution
set "X=!"
::This is only needed if you want to include ^ literal on same line
::containing ! literal
set "C=^"
::This is only needed if you want to start a line with :
set ":=:"
::This is all that is needed to write your output
setlocal enableDelayedExpansion
>mapDrive.bat (
for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do #echo(%%A
)
::----------- Here begins the template -----------
:::#echo off
:::net use !drivePath! \\server\share "/user:!username!" "!password!"
:::!:!: Use substitution to start a line with a :
:::!:!: The following blank line will be preserved
:::
:::echo Exclamation literal must be escaped ^! or substituted !X!
:::echo Caret with exclamation must be escaped ^^ or substituted !C!
:::echo Caret ^ without exclamation must not be escaped

Resources