I'm having a lot of trouble figuring out why my .bat file randomly closes... but I've narrowed it down to this:
IF %var:~-1%==\ SET var=%var:~0,-1%
IF %var:~0,1%==\ SET var=%var:~1%
Ideally what I'm eventually trying to accomplish is to remove any leading or trailing slashes or backslashes from %var%, which is set by user input.
The above lines work, but not when inside an IF statement when setlocal EnableDelayedExpansion is set AND when the variable is also set inside the IF statement.
For example, the following works if I remove the above lines, but crashes otherwise:
#echo off
setlocal EnableDelayedExpansion
set somevar=blah
if !somevar!==blah (
set /p var="-> "
IF %var:~-1%==\ SET var=%var:~0,-1%
IF %var:~0,1%==\ SET var=%var:~1%
echo !var!
)
pause
If I remove the IF condition, however, it does work:
#echo off
setlocal EnableDelayedExpansion
set /p var="-> "
IF %var:~-1%==\ SET var=%var:~0,-1%
IF %var:~0,1%==\ SET var=%var:~1%
echo !var!
pause
And if I keep the IF statement but move the set /p var="-> " outside of it, then it also works:
#echo off
setlocal EnableDelayedExpansion
set somevar=blah
set /p var="-> "
if !somevar!==blah (
IF %var:~-1%==\ SET var=%var:~0,-1%
IF %var:~0,1%==\ SET var=%var:~1%
echo !var!
)
pause
I know that when Delayed Expansion is enabled that I have to use ! instead of % for variables, but I've tried every combination of that on those lines to no avail, and I don't understand how they work enough to troubleshoot further.
And if there's a more efficient way to accomplish what I've described, by all means please mention it. I'm somewhat new to batch-writing.
As you've implied, the key to your problem is the use of delayed expansion.
When delayedexpansion has been invoked,
%var% refers to the value of var as it was when any code block (parenthesised sequence of instructions) was encountered (actually, var is simply replaced by its value at that time and then the instructions are executed)
!var! refers to the run-time value of var - that is, the value at the current time, having possibly been altered by the operation of the code block.
so, examining from your code
set /p var="-> "
IF %var:~-1%==\ SET var=%var:~0,-1%
IF %var:~0,1%==\ SET var=%var:~1%
echo !var!
This is executed as
set /p var="-> "
IF ==\ SET var=
IF ==\ SET var=
echo !var!
because the value of var at the start of the code is nothing, consequently it "crashes" because the if statement syntax is incorrect.
To fix the problem, use
set /p var="-> "
IF !var:~-1!==\ SET var=!var:~0,-1!
IF !var:~0,1!==\ SET var=!var:~1!
echo !var!
where each reference to var becomes the value as changed by the "set /p" or subsequent set operations.
However, set/p does not clear the variable if you simply press Enter. It leaves the variable unchanged (but its value happens to be nothing at the time)
Hence, a better approach is
set /p var="-> "
IF "!var:~-1!"=="\" SET "var=!var:~0,-1!"
IF "!var:~0,1!"=="\" SET "var=!var:~1!"
echo !var!
where the quotes around the if arguments ensure that the arguments are not empty and those around the set arguments ensure that the variable does not acquire any stray trailing spaces that may exist on the instruction line.
Related
I'm making a bat file which add/load/rename savegame slots, and I had problem with some variables, the first variable is savegame number, the second variable is the name of the savegame, here is the code I've tried:
#echo off
set savename2=FortySeven
set nb=2
echo %savename%nb%%
pause
The result I have got is nb%
Either enable delayed expansion:
#Echo Off
Set "savename2=FortySeven"
Set "nb=2"
SetLocal EnableDelayedExpansion
Echo !savename%nb%!
Pause
EndLocal
Or, use Call for that expansion:
#Echo Off
Set "savename2=FortySeven"
Set "nb=2"
Call Echo %%savename%nb%%%
Pause
Is it possible to reassess Batch variables?
Here an example what i want to do :
set a=Hello
set b=%a%
set a=Bye
echo %b%
rem Here, i want to show 'Bye' instead of 'Hello'
You could use delayed expansion to pass by reference.
setlocal enabledelayedexpansion
set "a=Hello"
set "b=a"
set "a=Bye"
echo !%b%!
... should output Bye. You're setting b to the variable name of a with this method. In the echo line, the batch thread first gets the value of b, which is a. It then expands the value of !a! via delayed expansion.
Be advised that if your values are likely to contain exclamation marks, you probably need to limit enabledelayedexpansion to the retrieval of your values only, avoiding having it active during the variable setting.
set "a=Hello"
set "b=a"
set "a=Bye"
setlocal enabledelayedexpansion
echo !%b%!
endlocal
You may store in variable b a reference to the value in a, and then use Delayed Expansion to access it:
#echo off
setlocal DisableDelayedExpansion
rem Store in "b" a *reference* to the value in "a"
set b=!a!
setlocal EnableDelayedExpansion
set a=Hello
echo Show the value in a: %b%
set a=Bye
echo Show the value in a: %b%
I'm looping through all command-line arguments using SHIFT. I'm getting result of ECHO is off.. It is likely printing the empty variable.
:argLoopStart
SET paramName=
SET arg=%1
IF -%arg%-==-- GOTO argLoopEnd
IF %arg:~0,2%==-- (
SET paramName=%arg%
ECHO %arg%
ECHO %paramName%
)
SHIFT
GOTO argLoopStart
:argLoopEnd
By running the command fake-command --dbs=mydbname, I got this:
--dbs
ECHO is off.
According to the code above, ECHO %arg% prints --dbs and ECHO %paramName% prints ECHO is off. The line of SET paramName=%arg% is not working as I expected. %parameName% should print --dbs as well. However, it seems printing an empty variable.
You need to enable delayed expansion with SETLOCAL EnableDelayedExpansion at the top of your script:
Delayed Expansion will cause variables to be expanded at execution
time rather than at parse time, this option is turned on with the
SETLOCAL command. When delayed expansion is in effect variables may be
referenced using !variable_name! (in addition to the normal
%variable_name% )
#echo off
setlocal enabledelayedexpansion
:argLoopStart
set paramName=
set arg=%1
if -!arg!-==-- goto argLoopEnd
if %arg:~0,2%==-- (
set paramName=!arg!
echo !arg!
echo !paramName!
)
shift
goto argLoopStart
:argLoopEnd
The below code works, echo test.test
set replaceWith=.
set str="test\test"
call set str=%%str:\=%replaceWith%%%
echo %str%
But, the below code echo ggg.hhhhh all the 4 times.
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
for %%i in %SERVICE_LIST% do (
set replaceWith=.
set str="%%i"
call set str=%%str:\=%replaceWith%%%
echo %str%
)
What am I doing wrong here?
If you understand why your code uses call set str=%%str:\=%replaceWith%%%, then you should be able to figure this out ;-)
Syntax like %var% is expanded when the line is parsed, and your entire parenthesized FOR loop is parsed in one pass. So %replaceWith% and echo %str% will use the values that existed before you entered your loop.
The CALL statement goes through an extra level of parsing for each iteration, but that only partially solves the issue.
The first time you ran the script, you probably just got "ECHO is on." (or off) 4 times. However, the value of str was probably ggghhhhh and replaceWith was . after the script finished. You don't have SETLOCAL, so when you run again, the values are now set before the loop starts. After the second time you run you probably got ggghhhhh 4 times. And then from then on, every time you run the script you get ggg.hhhhh 4 times.
You could get your desired result by using CALL with your ECHO statement, and moving the assignment of replaceWith before the loop.
#echo off
setlocal
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
set "replaceWith=."
for %%i in %SERVICE_LIST% do (
set str="%%i"
call set str=%%str:\=%replaceWith%%%
call echo %%str%%
)
But there is a better way - delayed expansion
#echo off
setlocal enableDelayedExpansion
SET "SERVICE_LIST=aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh"
set "replaceWith=."
for %%i in (%SERVICE_LIST%) do (
set str="%%i"
set str=!str:\=%replaceWith%!
echo !str!
)
Please have a text book for Windows Command Shell Script Language and try this:
#ECHO OFF &SETLOCAL
SET "SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)"
for /f "delims=" %%i in ("%SERVICE_LIST%") do (
set "replaceWith=."
set "str=%%i"
SETLOCAL ENABLEDELAYEDEXPANSION
call set "str=%%str:\=!replaceWith!%%"
echo !str!
ENDLOCAL
)
This is my script:
#echo off
setlocal
for /f %%i in ('echo aaa/') do set REPO=%%i
if "%REPO%"=="" (
echo No input
) else (
echo %REPO:~-1%
echo %REPO:~0,-1%
if %REPO:~-1%==/ set REPO=%REPO:~0,-1%
echo %REPO%
)
endlocal
Please, observe:
c:\dev\shunra\GlobalLibrary\Server>c:\Utils\hgbackup.cmd
/
aaa
aaa/
c:\dev\shunra\GlobalLibrary\Server>
What is going on?
EDIT
Note, that I am assigning to REPO something that evaluates to "aaa", hence I expect it to print "aaa", not "aaa/". It drives me crazy.
EDIT2
Apparently, here is the culprit (from help on the set command):
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%
But I tried using the ! sign, still it does not work for me. I use get ! printed on the screen or the wrong result again.
As has been discussed in the comments, and in your edited question, you need delayed expansion.
Delayed expansion must be enabled before you can use it. Within a batch script you can use setlocal enableDelayedExpansion
#echo off
setlocal enableDelayedExpansion
for /f %%i in ('echo aaa/') do set REPO=%%i
if "%REPO%"=="" (
echo No input
) else (
echo %REPO:~-1%
echo %REPO:~0,-1%
if %REPO:~-1%==/ set REPO=%REPO:~0,-1%
echo !REPO!
)
endlocal
EDIT
The above fails if the IN() clause is changed such that REPO is undefined. For example: in (echo.)
It fails because the entire IF/ELSE construct must have valid syntax, even it the ELSE clause will not be executed.
If REPO is undefined, then
if %REPO:~-1%==/ set REPO=%REPO:~0,-1%
expands to
if ~-1REPO:~0,-1
which is invalid syntax.
The problem again is solved by using delayed expansion.
#echo off
setlocal enableDelayedExpansion
for /f %%i in ('echo.') do set REPO=%%i
if "%REPO%"=="" (
echo No input
) else (
echo %REPO:~-1%
echo %REPO:~0,-1%
if !REPO:~-1!==/ set REPO=%REPO:~0,-1%
echo !REPO!
)
endlocal
Note, that I am assigning to REPO something that evaluates to "aaa"
Actually, you're conditionally assigning something. Have you testing whether the then-part is actually executing (for example, echo If Entered).
This works for me (just an extract from my whole script)
choice /C 1234567H /M "Select an option or ctrl+C to cancel"
set _dpi=%ERRORLEVEL%
if "%_dpi%" == "8" call :helpme && goto menu
for /F "tokens=%_dpi%,*" %%1 in ("032 060 064 096 0C8 0FA 12C") do set _dpi=%%1
echo _dpi:%_dpi%: