In the following script I call a subroutine with a string, the maximal length of a substring of that string and a 3rd non-existing variable to get the substring back.
The script should check if the next character after the string with the maximal substrings length is a space and if yes, then cut the string by that space (delete space to) and returning the substring and change the passed string by cutting the substring part. Example:
string: "Hello World Bla"
substringLength: 5
CALL :get_substring string substringLength substring
=> string: "World Bla" (no space), substringLength: 5 (no change), substring: "Hello"
This works fine without if clause but it doesn't when i use an if clause, even if i use delayed expansion.
Here is the working code without if statement:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET string=Hello World wasserschutzpolizei
SET /A substringLength=5
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
EXIT /B 0
ENDLOCAL
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET nextChar=!string:~%substringLength%,1!
REM IF "!nextChar!"==" " (
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
REM ) ELSE (
REM Some other case
REM )
EXIT /B 0
This doesn't work when I comment in the if statement:
IF "!nextChar!"==" " (
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
) ELSE (
REM Some other case
)
Why does the script doesn't work with an if-statement?
How can fix it?
Be aware that my routine should also include an else statement which I cutted out because the problem is the same.
The problem is this line:
SET string=!string:~%cutSpaceCount%!
When this line is placed inside the IF command, then the value of cutSpaceCount is changed inside the code block (parentheses) of the IF, and hence it must be expanded via !cutSpaceCount! delayed expansion, not via a %cutSpaceCount% standard expansion.
You should use something like a "double delayed expansion", that is, similar to this construct:
SET string=!string:~!cutSpaceCount!!
Of course, this don't work, so the trick is use a for command to get the value of the first delayed expansion, and then use the FOR parameter to complete the second delayed expansion:
for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"
A similar problem happen when the final values in the subroutine are returned to the calling program. This is the final working code:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET string=Hello World wasserschutzpolizei
SET /A substringLength=5
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
EXIT /B 0
ENDLOCAL
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET "nextChar=!string:~%substringLength%,1!"
IF "!nextChar!"==" " (
SET "substring=!string:~0,%substringLength%!"
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"
ECHO !string!
for /F "delims=" %%s in ("!string!") do for /F "delims=" %%b in ("!substring!") do (
ENDLOCAL & SET "%1=%%s" & SET "%3=%%b" & EXIT /B 0
)
) ELSE (
ECHO Some other case
)
PS - You don't need to expand variable values in SET /A command. Instead of:
SET /A cutSpaceCount=!substringLength!+1
you may simply use:
SET /A cutSpaceCount=substringLength+1
You seem to be very confused about the sequence of operations that occurs when delayed expansion has been invoked.
First, the value of var is substituted for %var%.
Then !var! is evaluated using the results.
The scope of this sequence of operations is one logical line, which may be one physical line or any number of physical lines continued with a terminal ^ or more commonly using a parenthesised sequence of lines.
In your mainline then,
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
Since these statements are not within the same logical line, they will be individually evaluated, so !var!==%var%.
Within your subroutine (non-IF version),
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
again are individual statements. The first set will first substitute for substringlength, and then execute SET substring=!string:~0,5! as a second operation.
Each of the echoes is a stand-alone statement, and the ! could (and preferably should) be replaced by %.
The set /a statement - well, set /a allows the current value of a variable to be used undecorated, so SET /A cutSpaceCount=substringLength+1 or SET /A cutSpaceCount=%substringLength%+1could be used here with no logical effect.
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will be evaluated according to the values established by the previous code-sequence.
However when you add the if, the code is parenthesised and thus becomes one logical statement and acts differently.
The echoes then require ! because you want to display the modified values within the code-block. Since cutSpaceCount is not set at the start of the code-block, SET string=!string:~%cutSpaceCount%! will be evaluated as SET string=!string:~!
and then ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will duly substitute the values of the variables as they stood when the IFwas encountered
So, a replacement routine might be
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET "substring=!string:~0,%substringLength%!"
SET "string=!string:~%substringLength%!"
IF "%string:~0,1%"==" " SET "string=%string:~1%"
ENDLOCAL & SET "%1=%string%" & SET "%3=%substring%"
EXIT /B 0
As others already explained, the problem is the line:
SET string=!string:~%cutSpaceCount%!
because you use immediate expansion (%) for variable cutSpaceCount which is changed in the same logical line/block of code.
A possible solution is to use call like this:
call set "string=%%string:~!cutSpaceCount!%%"
call introduces another variable expansion phase, so the sequence goes as follows:
immediate expansion phase where %% becomes %:
call set "string=%string:~!cutSpaceCount!%"
then delayed expansion occurs (let us assume a sample value of 5):
call set "string=%string:~5%"
another immediate expansion phase introduced by call to finally get %string:~5%.
Related
I come to find some guidance on accomplishing the following:
I have a variable with content like this:
varname = asdfiuytgy12$gggsy22.oihbcxew
or
varname = oiujedc$thisisit.oiju
which $ and . are exactly my partters and I need to get what is within them so gggsy22 or thisisit.
I need to use batch to create a simple bat file. I hope someone can provide some guidance.
Edit - (from comment section)
Actually a friend of mine helped and it did work but with a quite amount of lines:
Set "sstr=$"
SET stemp=%nameVar%&SET pos=0
:loop
SET /a pos+=1
echo %stemp%|FINDSTR /b /c:"%sstr%" >NUL
IF ERRORLEVEL 1 (
SET stemp=%stemp:~1%
IF DEFINED stemp GOTO loop
SET pos=0
)
Set "pos1=%pos%"
Set "sstr=."
SET stemp=%nameVar%&SET pos=0
:loop
SET /a pos+=1
echo %stemp%|FINDSTR /b /c:"%sstr%" >NUL
IF ERRORLEVEL 1 (
SET stemp=%stemp:~1%
IF DEFINED stemp GOTO loop
SET pos=0
)
Set "pos2=%pos%"
set /a "pos2=%pos2%-%pos1%-1"
call set env=%%nameVar:~%pos1%,%pos2%%%
#echo off
set "varname=asdfiuytgy12$gggsy22.oihbcxew"
for /f "tokens=2 delims=$." %%a in ("%varname%") do set "sub=%%a"
The following works in nearly any situation. The only thing that could break the code is if the string contains a quote " followed by a poison character like &, |, etc.
#echo off
setlocal
set "str=oiujedc$thisisit.oiju"
:: Verify string exists and has the proper format
echo "%str%"|findstr "\$.*\." >nul || (echo Value not found & exit /b)
:: Extract the value
:: The extra "x" is needed in case there is no character between $ and .,
:: in which case the result should be No Value (result variable not defined)
for /f "delims=." %%A in ("x%str:*$=%") do set "val=%%A"
set "val=%val:~1%"
:: Show the result
echo value = "%val%"
A bullet proof variant can be made by incorporating delayed expansion.
I am writing a batch script and I am having trouble echoing a variable, here is the script,
#echo off
set num1=1
set num2=10
set /a "out=%num1%*%num2%"
echo %out%
pause`
The output I receive is 10 which makes sense but I want it to echo 'num1' ten times instead of multiplying 'num1' by 'num2'. So I want the output to be 1111111111.
Also I don't want to loop the command 10 times as I am putting the output into a text file with 'output>> file.txt' otherwise I will end up with this in the text file,
1
1
1
1
1
1
1
1
1
1
I want to end up with 1111111111, thank you.
#ECHO OFF
SETLOCAL
set num1=1
set num2=10
SET "out="&FOR /L %%a IN (1,1,%num2%) DO CALL SET "out=%%out%%%%num1%%"
echo %out%
GOTO :EOF
The SET "out=" and FOR /L %%a IN (1,1,%num2%) DO CALL SET "out=%%out%%%%num1%%" may be on separate lines if desired. Setting out to nothing is simply a safety measure to ensure that if it contains a value, it's cleared first.
The for /L performs the call command num2 times.
The call command executes SET "out=%out%%num1%" in a subprocess because each %% is interpreted as an escaped-% (% is the escape character for %) - "escaping" a character means turning off its special meaning.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
Just to show a different method with set /A and a loop:
#echo off
set /A "num1=1,num2=10,out=0"
:loop
set /a "out*=10,out+=num1,num2-=1"
If %num2% gtr 0 goto :loop
echo %out%
pause
This is the simplest way to solve this problem, using Delayed Expansion.
#echo off
setlocal EnableDelayedExpansion
set num1=1
set num2=10
set "out="
for /L %%i in (1,1,%num2%) do set "out=!out!%num1%"
echo %out%
pause
PS - The multiply term is not exact in this case; perhaps "echo a variable the times indicated by another variable" be more clear...
If what you want is to print num1 num2 times in the same line you can do something like:
#echo off
set "num1=1"
set "num2=10"
(for /L %%i in (1,1,%num2%) do set /p "=%num1%" <nul
echo()>file.txt
The command set /p "=%num1%" <nul prints the text %num1% in the current line without the LF character. So num1 gets printed num2 times in the same line.
(set /a "m1=1,m2=2")
for /f %%c in ("%m1%%m2%") do echo %%c
pause
The brackets else where than due to the for command are used in cases, a space key should have been added.
The echo of the for command, is 12. I used the number characters to face the set /A command with decimal Expression.
When i try the same procedure only with a set for a Shell, may also be named m1 it is just possible without comma seperation.
With set command the m1 Expression would be 1 m2 2 and not two values like with a set /A SET.
Is there a way to use set only once and not only with the set /A?
As other answers and comments already indicated, there is no way to directly do this in one command, but via a procedure. The method below is the simplest one:
#echo off
rem Define the several values
set "vars=m1=1,m2=2"
rem Do it:
set "%vars:,=" & set "%"
echo m1=%m1%
echo m2=%m2%
You may remove the #echo off command and execute this program to see what exactly is executed...
As I understand the question you want something like:
set x=1,y=2
and as a result to have two variables (like set /a). The answer is no.
Though you can iterate trough expressions with plain for :
#echo off
for %%a in (
"x=1" "a=5"
"y=2" "b=6"
"z=3" "c=7"
) do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
Mind that the quotes around the items are mandatory because = is a delimiter. You can put everything on line and to use as separators , ,; ,<space>
May be with a lot of variables this can save you from some writing...?
this can be rewritten like this:
#echo off
set "vars=x=1,y=2,z=3,a=5,b=6,c=7"
for %%a in ("%vars:,=","%") do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
And thus you'll need to change only the vars value.
I am trying to figure out what the following expression means in a bat file I am working with:
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
!modified! >> %outvar%
This is declared for execution right after a delimiting if statement for "_" and obtain arguments passed to the execution of a script then separate them as strings and write the strings in a file.
More specifically: what do the exclamations (!*!) do in this situation?
I've searched for like an hour but to no result. Can anyone give a hint?
The ! marks are the delayed expansion notation for variables (see SETLOCAL /? for documentation on this). Essentially, the ! marks tell the processor to evaluate the variable at the time the line is executed instead of when it is parsed, which is the behavior of %.
Take this simple example:
SET MyValue=This
IF "%MyValue%"=="This" (
SET NewValue=That
SET MyValue=%NewValue%
)
ECHO NewValue = %NewValue%
ECHO MyValue = %MyValue%
REM Outputs:
REM NewValue = That
REM MyValue =
MyValue does not have a value because when the IF statement was parsed, %NewValue% did not have a value assigned yet (because the SET line had not yet been processed).
Now consider this:
SETLOCAL EnableDelayedExpansion
SET MyValue=This
IF "%MyValue%"=="This" (
SET NewValue=That
SET MyValue=!NewValue!
)
ECHO NewValue = %NewValue%
ECHO MyValue = %MyValue%
REM Outputs:
REM NewValue = That
REM MyValue = That
ENDLOCAL
This works because the delayed expansion notation !, tells the processor to evaluate !NewValue! when the respective line is executed.
Additionally in your case, the SET line doing the replacement allows for variables to be used as the replacement parameters. Delayed expansion notation is much easier to work with instead of having to break it out into CALL statements.
The exclamation marks are used for delayed variable expansions, and are used in the same way as %-signs are for variables. This is mainly used inside parenthesis. This is because of the way batch executes loops and if-statements. consider this code:
#echo off
set "i=0"
if %i% equ 0 (
set "i=1"
echo %i%
)
pause
When executed, this doesn't echo 1, like expected, but echoes 0. That is because %i% get's replaced with 0 before the statements inside the if are executed. However, if you use !i! and setlocal EnableDelayedExpansion, like this:
#echo off
setlocal EnableDelayedExpansion
set "i=0"
if %i% equ 0 (
set "i=1"
echo !i!
)
echo %i%
pause
It will correctly echo 1. Also note that ! is only needed inside parenthesis, not outside of them.
How would I do to use the variable %number% in this case?
Set Test=%strToMeasure:~-%Number%%
Whenever I use a variable the result comes out like this:
No variable only the number:
Set Test=%strToMeasure:~-3%
With variable:
Set Test=%strToMeasure:~-%Number%%
Full Code
#echo off
Set "strToMeasure=This is a string"
call :strLen strToMeasure strlen
echo.String is %strlen% characters long
Set /A number = %strlen% - 13
Set Test=%strToMeasure:~-%Number%%
Echo %strToMeasure%
Echo %Test%
pause
exit /b
:strLen
setlocal enabledelayedexpansion
:strLen_Loop
if not "!%1:~%len%!"=="" set /A len+=1 & goto :strLen_Loop
(endlocal & set %2=%len%)
goto :eof
You have two options in order to correctly execute this line:
Set Test=%strToMeasure:~-%Number%%
1- Doubling the percent signs for the second expansion and using call command:
Call Set Test=%%strToMeasure:~-%Number%%%
2- Using Delayed Expansion and enclosing the second expansion in exclamation marks:
setlocal EnableDelayedExpansion
. . .
Set Test=!strToMeasure:~-%Number%!
The second method is the usual way to solve this problem and it run faster than the former. You may review further details of this behavior at this post.