Batch String Manipulation - string

I am trying to write a batch program that writes more code based on what the user inputs. I am stuck on the string manipulation, as "!", ">", "&", and "%" all need to be escapeded before they are to be outputted to other files. This is what I have so far:
#echo off
set /p code=
set newcode=%code:^%=^%%,^>=^^>,!=^^!,^&=^^&%
echo %newcode%>>file.bat
All this escapinging escaped stuff is making my brain hurt, so can you please help me?
Thanks!

Since you haven't explained clearly what you are trying to do with the input, I am assuming you are trying to get the user to type something and then for that to be entered into a file. If that is the case te utilise copy in combination with con
#echo off
Echo Enter ^^Z (Ctrl + Z) and return it to end input
copy con file.txt
And that will allow the user to type WHATEVER they want, and it to be put in a file to be examined. Otherwise you're gonna have a lot of fun (And trouble) dealing with all the escaping you're gonna have to do (Rather ironic wasn't that?).
Mona.

Simply use delayed expansion in your case, as delayed expansion needs no further escaping for the content.
#echo off
setlocal EnableDelayedExpansion
set /p code=
echo(!code!
The echo( is for safe text output, even if the text is empty or is something like /?.
To the comment of #foxidrive:
When delayed expansion is enabled, then exclamation marks ! are altered, when they are visible before the delayed expansion is done, but the expanded content of delayed expanded variables will never be altered.
#echo off
set "var=Hello!"
setlocal EnableDelayedExpansion
echo "!var!" "Hello!"
This will result to: "Hello!" "Hello"

Related

How to get a substring with special characters in batch file?

I'm trying to remove a part of a string with special characters.
The string looks like: <abc>123</abc>
I want to extract only 123
I've tried
set substr=<abc>
%my_string:substr=%
and removing character by position
set result=%my_string:~5,-6%
but neither works. Someone can help me?
ps. I'm new in batch.
sorry for the english.
Delayed expansion and double quoting of strings are the methods needed here.
The use of the for loop is just as a way of defining all substrings to be removed in the one line.
#echo off
Setlocal enabledelayedexpansion
set "string=<abc>123</abc>"
For %%A in ("<abc>" "</abc>") do Set "string=!string:%%~A=!"
Echo(!string!
pause

What is the difference between "..." and x"..." in an IF condition in a Windows batch file?

I recently found the post Find if substring is in string (not in a file) where it is stated that considering
#setlocal enableextensions enabledelayedexpansion
#echo off
set str1=%1
if not x%str1:bcd=%==x%str1% echo It contains bcd
endlocal
then
the x before the two sides of the equality is to ensure that the string bcd works okay. It also protects against certain "improper" starting characters.
However, I haven't found any explanation about the actual effect of this x. So what is the difference between x"%string%" and "%string%"?
That is simply a very bad coded string comparison. The x on both sides makes it possible to compare the two strings even if %str1:bcd=% or %str1% is substituted by Windows command processor on parsing entire command line by an empty string before execution of command IF.
But the batch file execution is nevertheless exited immediately by cmd.exe because of a syntax error in case of value of environment variable str1 contains a space character or "&<>|.
Enclosing an argument string in double quotes results in getting all characters except percent sign and with enabled delayed environment variable expansion also the exclamation mark interpreted as literal character including space which is outside a double quoted string interpreted as argument string separator.
So much better is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
if "%~1" == "" goto EndBatch
set "str1=%~1"
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
The first argument of the batch file is compared first without surrounding double quotes with an empty string. So if the batch file is started without any argument or with just "" as first argument string, Windows command processor executes the command GOTO resulting in restoring previous environment pushed on stack with command SETLOCAL and exits the batch file.
Otherwise the batch file is called really with an argument string. This argument string is assigned to environment variable str1 with removing surrounding double quotes if there are one. So on calling batch file with argument test the value test is assigned to environment variable str1 and on calling it with "another test" the value another test without the double quotes is assigned to str1. And even on calling the batch file with wrong coded argument string "bcd test (missing second ") just bcd test is assigned to the environment variable str1.
The IF condition compares the value of environment variable str1 with all occurrences of bcd removed with the unmodified variable value. The double quotes around the two strings make it possible to compare the two strings even on containing space or ampersand or the redirection operators <>|. The command IF includes the double quotes on comparing the two strings.
So is this code now safe?
No, it is not in case of somebody calls the batch file invalid with test_bcd" as argument string on which first double quote is missing. In this case the first IF command line executed by cmd.exe is:
if "test_bcd"" == "" goto EndBatch
The trailing " of the wrong specified argument string is not removed by cmd.exe and cause a syntax error on this command line on execution as it can be seen on running the batch file from within a command prompt window with first line modified to #echo on.
One solution without using delayed environment variable expansion is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
if not defined str1 goto EndBatch
set "str1=%str1:"=%"
if not defined str1 goto EndBatch
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
This code makes sure that str1 does not contain any double quote before executing the IF command comparing the strings.
Another solution is using delayed environment variable expansion:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "str1=%~1"
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
That looks better as the above code without usage of delayed environment variable expansion. But it does not work as expected if the the argument string is for example "!Hello!" because in this case the if not condition is also true and output is therefore the message It contains bcd although the string !Hello! does not contain bcd.
The solution is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
setlocal EnableDelayedExpansion
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
endlocal
Delayed expansion is not enabled on assigning the argument string to environment variable str1 which results in getting the exclamation marks in string "!Hello!" interpreted as literal characters. Then delayed expansion is enabled for making the string comparison with using delayed environment variable expansion which avoids that the command line itself is modified by cmd.exe before execution of IF command.
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.
call /? ... explains %~1, not so good as done here.
echo /?
endlocal /?
goto /?
if /?
set /?
setlocal /?
See also:
How does the Windows Command Interpreter (CMD.EXE) parse scripts? ... a long answer every batch file writer should read carefully from top to bottom.
forfiles - FALSE vs. false (doesn't work with lower case?) ... this answer is about argument processing of command IF.
Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files ... this answer explains in detail how string comparisons are done by command IF.
Why is no string output with 'echo %var%' after using 'set var = text' on command line? ... this answer explains why set "variable=value" should be used in general and not other variants.
Single line with multiple commands using Windows batch file ... explains how & and || outside a double quoted argument string are interpreted by cmd.exe.
Microsoft article about Using command redirection operators explains how <>|& outside a double quoted argument string are interpreted by cmd.exe.
The addition of x (or any other alphabetic character) in front of a string ensures that the relational statement is syntactically valid even when/if the string is empty.
Suppose str1 is an empty string. Then the comparison %str1:bcd=%==%str1% after the substitution degenerates to ==, which is syntactically invalid.
However, with an x in front, the comparison becomes x==x and can be evaluated. Naturally, adding the same prefix to each of the two strings does not affect their (in)equality.

Why is processing of my batch file exited with a syntax error on string comparsion with IF command?

When I run the following code, the batch processing is exited with a syntax error message output that it does not recognize the opening parenthesis.
if %errorlevel2%==s (
echo.
echo.
echo Press any key to continue...
echo.
echo.
pause>nul
start haxmenu\pin.bat
goto menu
)
Are my parentheses set wrong or is something about code inside the block which lets the script fail on execution?
The error message is:
( was unexpected at this time.
Introduction
So you have a code similar to this one:
#echo off
:menu
set /P "errorlevel2=Please enter your choice: "
if %errorlevel2%==s (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
First you have to take into consideration that Windows command processor replaces %errorlevel2% with the string of this variable before the line is parsed for a command.
So it depends on existence and value of environment variable errorlevel2 how the line with the IF condition looks like on execution of the batch file.
For example with errorlevel2 not defined at all and user on prompt just hits the key RETURN or ENTER the variable errorlevel2 is still not existing after user prompt and the IF condition line looks therefore as follows:
if ==s (
This IF condition is of course invalid and command processor exits batch processing because of the syntax error.
Using double quotes around both strings to compare is a first step to avoid the syntax error.
if "%errorlevel2%"=="s" (
Now the resulting line on execution of the batch file with nothing entered by the user is:
if ""=="s" (
This is a valid syntax.
But it does not prevent your batch code completely on exiting because of a syntax error as it can be seen by entering on prompt for example a single double quote. This would result in
if """=="s" (
which results again in an exit of batch processing because of a syntax error caused by the odd number of double quotes on left side of the equal operator.
Before thinking about a solution for this additional issue, we should also think about what happens when the user really just press RETURN or ENTER on prompt without entering anything. Run the following code:
#echo off
set /P "UserInput=Enter a number: "
echo UserInput=%UserInput%
set /P "UserInput=Hit key RETURN: "
echo UserInput=%UserInput%
pause
After entering on first prompt a number like 23 and hit on second prompt just RETURN as requested, the number 23 is output also a second time.
The current value of the environment variable is not modified if the user just hits RETURN on user prompt. This should be always taken into account when using set /P in a menu, especially if this menu is within a loop because in this case the variable has already a value after first choice made by the user which would be kept unmodified on next prompt if the user does not enter anything.
Solution 1: Remove all double quotes before string comparison
One solution is predefining the variable with a single double quote and remove all double quotes from input string.
#echo off
:menu
set "errorlevel2=""
set /P "errorlevel2=Please enter your choice: "
set "errorlevel2=%errorlevel2:"=%"
if /I "%errorlevel2%"=="s" (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
It is important that the value of variable errorlevel2 is predefined with a string containing at least one double quote for this solution if not one of the options the user should choose from is defined as default value because the variable errorlevel2 must exist with any value to avoid a syntax error on line removing all double quotes from string.
See Why is no string output with 'echo %var%' after using 'set var = text' on command line? for an explanation why using syntax set "variable=value" even with value containing a double quote.
Solution 2: Using delayed expansion on string comparison
A syntax error on string comparison because of string entered by the user could be avoided by using delayed expansion because the IF condition line itself is not modified in this case by the input string.
#echo off
setlocal EnableDelayedExpansion
:menu
set "errorlevel2="
set /P "errorlevel2=Please enter your choice: "
if "!errorlevel2!"=="s" (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
endlocal
The variable errorlevel2 is deleted now always before prompting the user.
Whatever the user enters does not modify the batch code line itself and therefore the string comparison always works even with a string entered with an odd number of double quotes.
Solution 3: Using command choice for simple menus
Windows offers the command choice (Microsoft article) for tasks where a user should select one of offered options by key. Using this command is nearly always better for single character choices than using set /P.
But how choice (SS64 article) can be used depends also on version of Windows respectively which version of choice is used as there are multiple versions with different syntax and capabilities. choice should be used for simple menus if compatibility of batch file with Windows versions prior Windows Vista is not needed anymore as the batch file is definitely never run on Windows XP or even older Windows.
In all probability, the problem is that errorlevel2 is not set (ie is set to nothing
You could get over this with
if "%errorlevel2%"=="s" (
but It's likely that you're expecting errorlevel2 to have been set in a block statement (ie within a parenthesised series of statements) and in that case, you'd need to look at about 10,000 aricles on SO relating to delayedexpansion.
Or, of course, show us a little more of your code - back to the if or do that's supposed to set errorlevel2

Split string by special characters '*' in batch file

I need to delete the substring after *. with a batch file
Example :
The value of string is : TEST_SINISTRE*.csv
rem My code :
SET mystring="TEST_SINISTRE*.csv"
rem Do the split
SET ext_test=%mystring:*.="& rem %"
SET ext_test=%ext_test%
rem what i get
echo %ext_test% ===> "& rem csv"
rem What i want to see
===> TEST_SINISTRE
Can you help me :-)
If, and only if, the *. pattern can only occur once in the string, and the part after *. is not contained in the part before *., the following could be used:
rem this is the original string containing one `*.`:
set "STRING=TEST_SINISTRE*.csv"
rem now get everything after `*.`:
rem (if `*` is the first character in substring substitution, it means everything up to
rem and including the search string is to be replaced, by nothing here in this case)
set "SUBSTR=%STRING:**.=%"
rem get everything before `*.`, including the `*`:
setlocal EnableDelayedExpansion
set "SUBSTL=!STRING:.%SUBSTR%=!"
rem truncate the `*` from the string:
endlocal & set "SUBSTL=%SUBSTL:~,-1%"
Since the substitution syntax for variable expansion is used, this is done in a case-insensitive manner.
To make it more secure, you could append something that will most probably never occur to your original string temporarily and remove it afterwards. To accomplish this, replace the set command line between the setlocal/endlocal block with the following (using appendix ### here for instance):
set "STRING=!STRING!###"
set "SUBSTL=!STRING:.%SUBSTR%###=!"
The * character is a wildcard in batch variable substring substitution. When you do *.=something in an inline substitution, you're really saying "replace everything up to and including the dot". You should use a for /F loop so you can specify the asterisk as a delimiter.
set "str=TEST_SINISTRE*.csv"
for /f "tokens=1* delims=*" %%I in ("%str%") do set "ext_test=%%I%%J"
echo %ext_test%
I'm not sure what your ultimate goal is, but here's a hacksy possible alternative. You could actually create a file called TEST_SINISTRE.csv and then capture the filename into a variable as a wildcard match.
set "str=TEST_SINISTRE*.csv"
type NUL > TEST_SINSTRE.CSV
for %%I in (%str%) do set "ext_test=%%I"
echo %ext_test%
I'm sure that's not exactly you have in mind, but it does demonstrate that maybe you don't need to strip the asterisk if you are going to be performing filename matching.

Weird scope issue in .bat file

I'm writing a simple .bat file and I've run into some weird behavior. There are a couple places where I have to do a simple if/else, but the code inside the blocks don't seem to be working correctly.
Here's a simple case that demonstrates the error:
#echo off
set MODE=FOOBAR
if "%~1"=="" (
set MODE=all
echo mode: %MODE%
) else (
set MODE=%~1
echo mode: %MODE%
)
echo mode: %MODE%
The output I'm getting is:
C:\>test.bat test
mode: FOOBAR
mode: test
Why is the echo inside the code block not getting the new value of the variable? In the actual code I'm writing I need to build a few variables and reference them within the scope of the if/else. I could switch this to use labels and gotos instead of an if/else, but that doesn't seem nearly as clean.
What causes this behavior? Is there some kind of limit on variables within code blocks?
You are running into the problem of cmd's static variable expansion. The MODE variable is only evaluated once. You can see this if you omit the #echo off line.
From the set /? documentation:
Finally, support for delayed environment variable expansion has
been added. This support is always
disabled by default, but may be
enabled/disabled via the /V command
line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around
the limitations of the current
expansion which happens when a line of
text is read, not when it is executed.
The following example demonstrates the
problem with immediate variable
expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since
the %VAR% in BOTH IF statements is
substituted when the first IF
statement is read, since it logically
includes the body of the IF, which is
a compound statement. So the IF
inside the compound statement is
really comparing "before" with "after"
which will never be equal. Similarly,
the following example will not work as
expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of
files in the current directory, but
instead will just set the LIST
variable to the last file found.
Again, this is because the %LIST% is
expanded just once when the FOR
statement is read, and at that time
the LIST variable is empty. So the
actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the
last file found.
Delayed environment variable expansion
allows you to use a different
character (the exclamation mark) to
expand environment variables at
execution time. If delayed variable
expansion is enabled, the above
examples could be written as follows
to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
setlocal EnableDelayedExpansion
will enable the /v flag
Additionally to already anwered here.
Long answer article:
https://rsdn.org/article/winshell/NTCommandProcessor.xml
The google translated variant is pretty broken, but you can at least read the raw text:
https://translate.google.com/?sl=ru&tl=en&text=https%3A%2F%2Frsdn.org%2F%3Farticle%2Fwinshell%2FNTCommandProcessor.xml&op=translate
Start read from Conditional block section.
Short answer:
The block operator (...) blocks a %-variable expansion until the top most scope. You have to exit the scope out to be able to use %-variable as is:
#echo off
set "MODE="
(
(
set MODE=all
echo MODE=%MODE%
)
echo MODE=%MODE%
)
echo MODE=%MODE%
Or use call prefix to reevaluate it in place:
#echo off
set "MODE="
(
(
set MODE=all
call echo MODE=%%MODE%%
)
)
Looks like the read and write use different scoping rules.
If you eliminate this line
set MODE=FOOBAR
it will work as expected. So you'll probably need to have a complex series if if/elses to get the variables populated as you'd like.

Resources