Batch script (Windows) string substitution with a twist - windows

I know how to do a literal string substitution in a batch script. However, I have a specific situation where I need to substitute the value of a numeric variable
This is the script:
setlocal enableextensions enabledelayedexpansion
set /A L=2
:L1
if %L% EQU 0 goto :EOF
set STRING="THIS IS # TEST"
SET NEW=%STRING:#=%%L%
echo %NEW%
set /A L=%L% - 1
goto L1
I want it to display this:
THIS IS 2 TEST
THIS IS 1 TEST
But it ends up diplaying this instead:
THIS IS TEST2
THIS IS TEST1
Any tips on how to get it to do what I need?
Thanks.

Even the solutions of aphoria and Bali C will work, it's better to use
set "NEW=!STRING:#=%L%!"
As then the replacement will be done in the delayed expansion phase and not in the percent expansion phase.
This will also work with exclamation marks and carets in STRING
#echo off
set L=2
set "String=This is # test!"
setlocal EnableDelayedExpansion
set "NEW=!STRING:#=%L%!"
echo !NEW!

Your almost there, just change
SET NEW=%STRING:#=%%L%
to
SET NEW=%STRING:#=!L!%

You need to use !L! to use delayed expansion.
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET /A L=2
:L1
IF !L! EQU 0 goto :EOF
SET STRING=THIS IS # TEST
SET NEW=%STRING:#=!L!%
ECHO %NEW%
SET /A L=!L! - 1
GOTO L1
Actually, you don't have to use !L! everywhere, just in the SET NEW=%STRING:#!L!% line. I used it everywhere for visual consistency.

Just for fun. Here is how to do it without delayed expansion. :)
Using the call command to double expand the variables. Use double percents %% around the variable to evaluate second. Single percents % around the variable to evaluate first.
setlocal EnableExtensions
set /A L=2
:L1
if %L% EQU 0 goto :EOF
set "STRING=THIS IS # TEST"
call set "NEW=%%STRING:#=%L%%%"
echo %NEW%
set /A L=%L% - 1
goto L1

You may also use the STRING as a "format string" placing the desired variables in the right places enclosed in exclamation marks. This way, no further replacement of the values is needed, just display the format string in the usual way:
rem Define the "format string" with Delayed Expansion disabled:
set STRING=THIS IS !L! TEST
setlocal enableextensions enabledelayedexpansion
set /A L=2
:L1
if %L% EQU 0 goto :EOF
echo %STRING%
set /A L=L - 1
goto L1
Or, with no Delayed\Expansion:
set STRING=THIS IS %%L%% TEST
set /A L=2
:L1
if %L% EQU 0 goto :EOF
call echo %STRING%
set /A L=L - 1
goto L1
Antonio

Related

Windows Batch SET with Variable Substring Length?

Take this simple example:
#ECHO OFF
SET /P phrase="Enter Word : "
SET /a rnum=%random% %%10 +1
ECHO %phrase%
ECHO %rnum%
SET rchar=%phrase:~0,%rnum%%
ECHO %rchar%
Pause
I just want to be able to pass that rnum variable to pick that as the character chosen from the left of that user entered word to that random character.
I can't seem to figure out how to pass that as a variable.
I tried with enabledelayedexpansion with no luck:
#ECHO OFF
SET /P Phrase="Enter Word : "
SET /a rnum=%random% %%10 +1
ECHO %phrase%
ECHO %rnum%
setlocal enabledelayedexpansion
SET rchar=!phrase:~0,%rnum%!
endlocal
ECHO %rchar%
Pause
So how do I pass rnum as a variable in this instance? Thanks for any assistance.
Here's a simple modification of your example delayed expansion code, which shows one method of maintaining your variable value beyond endlocal:
#ECHO OFF
SET /P "phrase=Enter Word : "
SET /A rnum = %RANDOM% %% 10 + 1
ECHO %phrase%
ECHO %rnum%
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%G IN ("!phrase:~0,%rnum%!") DO ENDLOCAL & SET "rchar=%%~G"
ECHO rchar=%rchar%
PAUSE
The above example should be fine, as long as the end user does not begin to input strings with problematic characters. If you wanted to make it a little more robust for such scenarios then perhaps this will help:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
:AskString
Rem Get interactive string input
Set "String="
Set /P String="Enter Word : "
If Not Defined String GoTo AskString
Set String
Rem Generate a random integer 1..10
Set /A "Integer = (%RANDOM% %% 10) + 1"
Set Integer
Rem Create a substring variable using %String% and %Integer%
Echo %%SubString%% = %%String:~0,%Integer%%%
SetLocal EnableDelayedExpansion
For /F Delims^=^ EOL^=^ UseBackQ %%G In ('"!String:~0,%Integer%!"') Do (
EndLocal
Set "SubString=%%~G"
)
Set SubString
Pause
Please note that the above code uses Set Variable to display the variable name along side its value. If your variable contains certain poison characters just using Echo %Variable% may not work, and you would probably be better off keeping delayed expansion enabled at that time.
As Compo already comments, the position of your endlocal is the problem.
You could just move the endlocal after the echo
#ECHO OFF
setlocal enabledelayedexpansion
SET /P Phrase="Enter Word : "
SET /a rnum=%random% %%10 +1
ECHO !phrase!
ECHO !rnum!
SET "rchar=!phrase:~0,%rnum%!"
ECHO !rchar!
endlocal

Batch file error "/100 was unexpected at this time"

I make a game for fun with batch script but in this code i have an error message "/100 unexpected at this time" i really don't understand why? Please help me!!
#echo off
mode con cols=110 lines=32
setlocal enabledelayedexpansion
set npctier=0
goto randomnpc
:randomnpc
if %npctier% EQU 0 (
set npctype=Wooden Dummy
set /a npclvl=%random% %% 5+1
set /a npchp=%npclvl% * 100
set /a npcdmg=0
set /a npcdef=(%npchp%*5)/100
set /a npcxp=%npclvl%*100 )
:combatchoice
echo.
echo. You see %npctype% level %npclvl%.
echo.
echo. The %npctype%'s Health: %npchp% HP
echo.
goto main
I recommend to first open a command prompt, run set /? and read the output help carefully and completely from top of first to bottom of last page. There is explained:
Any non-numeric strings in the expression are treated as environment variable names whose values are converted to numbers before using them. If an environment variable name is specified but is not defined in the current environment, then a value of zero is used. This allows you to do arithmetic with environment variable values without having to type all those % signs to get their values.
So there can be written just set /A npchp=npclvl * 100 as npclvl inside the arithmetic expression is interpreted as name of an environment variable and the command line works even on being inside a command block starting with ( and ending with matching ) without usage of delayed environment variable expansion.
Then run cmd /? and read again the output help carefully and completely from top of first to bottom of last page. There is explained that a file name (or any other argument string) containing a space one of these characters &()[]{}^=;!'+,`~ must be enclosed in " to get those characters interpreted as literal characters of an argument string.
Please read also How does the Windows Command Interpreter (CMD.EXE) parse scripts?
For that reason the command line set /a npcdef=(%npchp%*5)/100 should be written with one of the following notations:
set /A npcdef=npchp*5/100
set /A npcdef=npchp * 5 / 100
set /A "npcdef=(npchp*5)/100"
set /A npcdef=(npchp*5^)/100
The caret character ^ escapes the next character for being interpreted as literal character except the next character is % which must be escaped with %.
The recommendations posted on DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ should be also taken into account on writing batch files which output empty lines.
The batch file with the main improvement of changing the IF condition to avoid completely the usage of a command block.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
%SystemRoot%\System32\mode.com con cols=110 lines=32
set npctier=0
set "npctype=Wooden Dummy"
:randomnpc
if %npctier% NEQ 0 goto CombatChoice
set /A npclvl=%random% %% 5 + 1
set /A npchp=npclvl*100
set npcdmg=0
set /A npcdef=npchp*5/100
set /A npcxp=npclvl*100
:CombatChoice
echo/
echo You see %npctype% level %npclvl%.
echo/
echo The %npctype%'s health: %npchp% HP
echo/
endlocal
See also Why is no string output with 'echo %var%' after using 'set var = text' on command line? It contains several hints on how to use command SET not written in help/documentation of this command.
If you wish to maintain the same structure, then something lke this would be better:
#Echo Off
SetLocal EnableExtensions
%__APPDIR__%mode.com 110, 32
Set "npctier=0"
:randomnpc
If %npctier% Equ 0 (
Set "npctype=Wooden Dummy"
Set "npcdmg=0"
Set /A "npclvl=(%RANDOM% %% 5) + 1"
Set /A "npchp=npclvl * 100, npcdef=npclvl * 5, npcxp=npchp"
)
:combatchoice
Echo=
Echo You see %npctype% level %npclvl%.
Echo=
Echo The %npctype%'s Health: %npchp% HP
Echo=
GoTo main
You should note, that it is possible to define multiple values using Set /A in one arithmetic instruction
Before you get too carried away with creating a lengthy script which repeats code, I think it's worthwhile introducing you to batch macro's.
Macro's in Batch are commands or command blocks assigned to variables, and through the use of For loops combined with If / Else conditioning can be used to Capture arguments allowing variables to be used as Functions.
The below example contains two variations of a macro that can be used to easily generate different encounter types with minimal repetative scripting whilst also avoiding the use of inefficient calls to functions.
Macro's must be defined prior to delayed expansion being enabled as variables referenced during macro execution are defined using ! expansion so that the value the macro is parsed with at expansion is the value of the variable at the time of parsing, not it's value during definition.
#echo off
mode con cols=110 lines=32
(Set \n=^^^
%= macro newline DNR =%
)
(Set LF=^
%= Linefeed DNR =%)
rem Example 1: npc generation; Fixed Formula macro
rem USAGE: %npc{generic}%{Npc name / type}{npc max level}{npc multiplier}{damage value or formula}
Set npc{generic}=For %%n in (1 2)Do if %%n==2 (%\n%
For /F "Tokens=1,2,3,4 Delims={}" %%G in ("!Params!")Do (%\n%
Set "npctype=%%G" %\n%
Set /a "npclvl= !random! %% %%H + 1" %\n%
Set /a "npchp= !npclvl! * %%I" %\n%
Set /a "npcdmg= %%J" %\n%
Set /a "npcdef= ( !npchp! * %%H ) / %%I" %\n%
Set /a "npcxp=!npclvl! * %%I" %\n%
echo/Enemy: !npctype!!LF!level: !npclvl!!LF!HP: !npchp!!LF!Damage: !npcdmg!!LF!Defence: !npcdef!!LF!XP: !npcxp!!LF!%\n%
)%\n%
) Else Set Params=
rem Example 2: npc generation; Supplied Formula macro
rem USAGE: %npc{boss}%{Npc name / type}{level value or formula}{hp value or formula}{damage value or formula}{defense value or formula}{xp value or formula}
Set npc{boss}=For %%n in (1 2)Do if %%n==2 (%\n%
For /F "Tokens=1,2,3,4,5,6 Delims={}" %%G in ("!Params!")Do (%\n%
Set "npctype=%%G" %\n%
Set /a "npclvl= %%H " %\n%
Set /a "npchp= %%I " %\n%
Set /a "npcdmg= %%J " %\n%
Set /a "npcdef= %%K " %\n%
Set /a "npcxp= %%L " %\n%
echo/Enemy: !npctype!!LF!level: !npclvl!!LF!HP: !npchp!!LF!Damage: !npcdmg!!LF!Defence: !npcdef!!LF!XP: !npcxp!!LF!%\n%
)%\n%
) Else Set Params=
rem enable delayed expansion after macro definitions
setlocal enableextensions enabledelayedexpansion
:randomnpc
%npc{generic}%{Wooden Dummy}{5}{100}{0}
Pause
%npc{boss}%{Dragon}{!random! %% 10 + 10}{npclvl * 200}{npchp / 20}{npchp / (npclvl / 2)}{npclvl * 150}
Endlocal
Goto :Eof

Count the underscores in the Filename with a bat

I have searched this everywhere and I didn't found it, so what I want to do is simple, I want to count the underscores in a filename and put it in a variable to later use it.
Is there any simple batch code to do it ?
#echo off
setlocal enabledelayedexpansion
set filename=__example_file.bin_
set cnt=0
set pos=0
:loop
if "!filename:~%pos%,1!"=="_" set /a cnt=%cnt%+1
set /a pos=%pos%+1
if not "!filename:~%pos%,1!"=="" goto loop
echo Count: %cnt%
That code is pretty ugly but I can't find a better way so far.
A different method to count underscores (or more exact elements separated by an underscore)
is to use self expanding code:
:: Q:\Test\2017\08\28\SO_45917406.cmd
#echo off & setlocal enabledelayedexpansion
set "FileName=example_file_name_20170828_181000.txt"
Set i=1&Set "FileName[!i!]=%FileName:_="&Set /a i+=1&Set "FileName[!i!]=%"
Echo Counted %i% underscore separated elements
Set FileName
Sample output:
> Q:\Test\2017\08\28\SO_45917406.cmd
Counted 5 underscore separated elements
FileName=example_file_name_20170828_181000.txt
FileName[1]=example
FileName[2]=file
FileName[3]=name
FileName[4]=20170828
FileName[5]=181000.txt
This solution is inspired from #xmcp :
#echo off
Rem The srting to count in the filename is the underscrore "_"
Rem we can of course set another one ;)
set "MyString=_"
setlocal enabledelayedexpansion
#for /f "delims=" %%a in ('Dir /b "%userprofile%\Desktop"') do (
set "filename=%%a"
set /a "cnt=0"
set /a "pos=0"
Call:Counting_String "!filename!" "%Mystring%"
)
pause & exit
::*********************************************************
:Counting_String <filename> <MyString>
set "filename=%~1"
set "string=%~2"
if /I "!filename:~%pos%,1!"=="%Mystring%" set /a cnt+=1
set /a pos+=1
if not "!filename:~%pos%,1!"=="" goto Counting_String
echo !filename!: [!cnt!] "%Mystring%"
exit /b
::*********************************************************

goto was not expected at this time batch

#echo off
:start
set string=
set lo=1
set a=0
set b=0
set cl=1
set cloop=
set google=0
set k=0
set r=0
set id=
set t=0
set f=0
set /p string=?
if defined string (
echo %string%
goto loop
) else (
echo please enter a string
goto start
)
:loop
set a=
for /f "tokens=%lo%" %%G IN ("%string%") DO echo %%G
if defined a (
echo %a%
set google=0
set /p cloop=<greetings.txt
pause
:cloop
set b=
for /f "tokens=%cl%" %%g IN ("%cloop%") DO set b=%%g
if defined string (
if %a%==%b% goto greetings
set /a cl=%cl%+1
goto cloop
) else (
set cl=0
set /a lo=%lo%+1
goto loop
)
) else (
goto google
)
:greetings
set f=0
set k=0
set r=0
set /p id=<greetingtone.dat
for /f "tokens=%cl%" %%g IN ("%id%") DO set t=%%g
start greeting.bat
call greeting.bat
goto talk
:google
echo not done yet
pause
goto start
i have narrowed it down to this line
if %a%==%b% goto greetings
when i remove it it runs
i have looked but i have no idea why it does not work
please help the greetings.txt has "hi hello grunt"
i think it might be the variables
If %a% or %b% are empty values, it is likely the compare is incomplete, and it is saying that the goto is not expected yet. For instance, if you type the following at a C:\ prompt:
c:\>if a== echo ok
c:\>if ==a echo ok
echo was unexpected at this time.
c:\>if == echo ok
ok was unexpected at this time.
c:\>
If you enclose each value in quotes, then the comparison will still work even if one or both of the values are empty. For instance:
if "%a%"=="%b%" goto greetings
The normal reason for that an unexpected word in an IF statement is that IF has a very specific syntax, IF item1 operator item2 actionstatement(s).
What is likely to be happening is that item1 AND item2 appear to be missing, so IF resolves that as IF == goto greetings. Since goto is not one of its known operators (==, equ, neq, leq, lss, geq, gtr`) then it complains.
The question from here is - why do %a% and %b% appear to be empty?
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered. In your case, that means the outermost IF - in if defined string.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Next problem is using a label within a block. Not a good idea. On some versions, a label will terminate the block. Call a subroutine instead.
call :cloop
...
goto start
:cloop
(whatever needs to be done)
goto :eof
(note that :cloop and :EOF have a required colon. on cloop it means "this is an internal subroutine - it's in the cuurrent batchfile." :EOF is a predefined label understood by CMD to mean end of file.)

windows batch file array extraction counter not being incremented by +=

I am translating a shell script to windows batch. What I need to do is take all except 1,2 and last from command line arguments. join them and send to another program as argv.
#echo off
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF %i% geq 2 (
set /a c+=1;
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
I want the candidates array be argv[3..n-1]
e.g. If I write batch x 2 a b p it should pass a b to that another program
The problem is loop counter is not being incremented by += operator. If I write echo %1% inside FOR I see 0 always
You should not use for %%A in (%*) as it treats %* as filename set. This may cause problems, especially if you can pass * or ? (wildcard match characters in cmd) in parameters - as they will be expanded to all files satisfying pattern. Second, batch does really know nothing about arrays - a[1] and a[2] are just a shorthand notation for humans - they are two distinct variables.
Given the problem Parse command line, take second parameter as count of parameters to concatenate into a variable, here is my take:
#echo off
setlocal
set subject=%1
shift
set exp_count=%1
if not defined exp_count (
echo Count not specified
exit /b 1
)
set /a "verify=%exp_count%"
if %verify% leq 0 (
echo Count not valid /not a positive integer/
exit /b 2
)
set real_count=0
:loop
shift
if "%~1"=="" goto end_params
set /a real_count+=1
if %real_count% leq %exp_count% set "candidates=%candidates%%~1"
goto loop
)
:end_params
if %real_count% lss %exp_count% (
echo Less parameters passed than specified!
exit /b 3
)
echo %subject%
echo %candidates%
Please note I'm not checking if there is a 'hanging' parameter (the last, not being concatenated) but it should be trivial to add that check. I left it out on purpose to make the code more flexible.
I have two answers for your question:
1- The first problem is that in IF %i% ... command the value of i variable not change (although set /a i+=1 command will correctly increment the variable) and the way to solve it is by including setlocal EnableDelayedExpansion command at beginning and enclose i in percents signs this way: IF !i! ... (as said in previous answers). However, you must note that an array variable in Batch is different than a simple variable with same name (they both can exist at same time), so array elements must always be written with subscripts and there is NO way to process an entire array in a single operation. See this topic for further details.
In your program you must transfer the elements of candidates array into a simple variable, that in the example below have the same name (just to state my point):
#echo off
setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
REM Transfer "candidates" array elements into "candidates" simple variable:
set candidates=
FOR /L %%i IN (1,1,%c%) do (
set candidates=!candidates! !candidates[%%i]!
)
REM Show "candidates" simple variable:
echo %candidates%
Note that in Batch files you may insert commas, semicolons and equal-signs as separators instead spaces in most commands. However, SET /A command have other rules at this respect, so the semicolon must be omitted.
2- Independently of the array management explained above, this is the way I would solve your problem using a list instead of an array:
#echo off
SET subject=%1
shift
set count=%1
set candidates=
set lastArg=
set i=0
:nextArg
shift
if "%1" equ "" goto endArgv
set /a i+=1
set candidates=!candidates! !lastArg!
set lastArg=%1
goto nextArg
:endArgv
SET /a count_actual=i-3, count_expected=count
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
Antonio
Yes your code will not increment i. Batch variable replacement occurs when a block is parsed, not when it is executed. The entire for block is parsed once, so %i% is replaced with zero before the for block is executed.
To disable that you need to enable delayed expansion and change your variable escape characters from %'s to !'s to have the replacement made at runtime. Then you will see i incremented in the for loop.
#echo off
Setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
You will also need to get rid of the ; at the end of the set /a c+=1; line and I'm not sure what you are trying to do on line set candidates[!c!]=%%A as the brackets don't mean anything in batch.
While there are a bunch of answers already listed, I decided to add one more. My approach is to keep the answer as simple as possible for your specific needs. If you have any questions feel free to ask.
This will create the array as you desired [3,...,n-1] without the need for delayed expansion or fancy logic.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates[%index%]=%param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show the Results
echo %count_actual%
echo %count_expected%
echo %subject%
set candidates
Here is an alternate where the candidates are stored in a space delimited string instead of seperate variables. Replace the space between the %candidates% %param% to whatever delimiter you desire.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates=%candidates% %param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show Results
echo %count_actual%
echo %count_expected%
echo %subject%
echo %candidates%

Resources