I have smth like %SystemRoot%\blahblahblah in a variable (for example, variable a).
But echo !a! will return %SystemRoot%\blahblahblah (without expanding %SystemRoot%). How can I expand it?
You can use a trick of calling a subroutine that will cause the variable to be expanded twice:
#Echo Off
set a=%%SystemRoot%%\blahblahblah
call :reparse set b=%a%
echo value of a: %a%
echo value of b: %b%
goto :EOF
:reparse
%*
goto :EOF
The :reparse subroutine just executes all its parameters. The first expansion occurs when executing the call and the second occurs when %* is interpreted as set b=%SystemRoot%\blahblahblah.
Related
I'm new to batch and I know that you can use
%~dp0
to get the directory from a path, but then how do I use it on a path and put it into a variable.
Say I have this Location:
C:\someFolderName\anotherFolderName\FinalFolderName\file.txt
I want to know what the path is(excluding the drive and the file) and store it into a variable
The tilde notation works for for loop variables and call function arguments. Check out this sample .bat script for an example of each method:
#echo off
setlocal
set "fqname=C:\someFolderName\anotherFolderName\FinalFolderName\file.txt"
rem // set var1 to the path-to-file
for %%I in ("%fqname%") do set "var1=%%~dpI"
rem // set var2 to the path-to-file
call :get_path var2 "%fqname%"
echo var1: %var1%
echo var2: %var2%
rem // end main runtime
goto :EOF
rem // get_path function
:get_path <var_to_set> <filename>
set "%~1=%~dp2"
goto :EOF
In a cmd window, enter help for and see the last couple of pages for full details on tilde notation.
#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.)
I have written a script that contains a function that should loop through a list and return a value given the index of the item in said list. I have a function called: :find that should take 2 arguments: the list and the item position. I am unsure of how to process the multiple parameters in the function. This script runs fine if I replace %LIST% with %MY_LIST% inside the loop and I remove the %MY_LIST% from the argument list tht is passed to call :find, but I really want to know how to pass multiple arguments. I take it that they are just passed to the function as a whole string...
#echo off
setlocal enableDelayedExpansion
cls
:: ----------------------------------------------------------
:: Variable declarations
:: ----------------------------------------------------------
set RETURN=-1
set MY_LIST=("foo" "bar" "baz")
set TARGET_INDEX=1
:: ----------------------------------------------------------
:: Main procedure
:: ----------------------------------------------------------
call :log "Finding item %TARGET_INDEX%..."
call :find %MY_LIST% %TARGET_INDEX%
call :log "The value is: %RETURN%"
goto Exit
:: ----------------------------------------------------------
:: Function declarations
:: ----------------------------------------------------------
:find
call :log "Called `:find` with params: [%*]"
set /a i=0
set LIST=%~1 & shift
for %%a in %LIST% do (
if !i! == %~1 (
set RETURN=%%a
)
set /a i=!i!+1
)
goto:EOF
:printDate
for /f "tokens=2-4 delims=/ " %%a in ('echo %DATE%') do (
set mydate=%%c/%%a/%%b)
for /f "tokens=1-3 delims=/:./ " %%a in ('echo %TIME%') do (
set mytime=%%a:%%b:%%c)
echo|set /p="[%mydate% %mytime%] "
goto:EOF
:log
call :printDate
echo %~1
goto:EOF
:: ----------------------------------------------------------
:: End of script
:: ----------------------------------------------------------
:Exit
Update
My script now works fine; thanks to nephi12. http://pastebin.com/xGdFWmnM
call :find "%MY_LIST%" %TARGET_INDEX%
goto :EOF
:find
echo %~1 %~2
goto :EOF
they are passed the same as args to the script... ;)
Here is another method to do an index lookup on a space delimited list of values. Define the list without enclosing parentheses. Single words don't need to be quoted. Phrases with space, tab, semicolon, or equal must be quoted. Also values with special characters like & and | should be quoted.
Then reverse the order of your :FIND arguments - first the index, followed by the actual list. Use SHIFT in a FOR /L loop to get the desired index value into the first argument.
One advantage of this solution is that it can support any number of values, as long as they fit within the 8191 character per line limit. The nephi12 solution is limited to 9 values.
#echo off
setlocal
set MY_LIST=foo bar baz "Hello world!"
call :find %~1 %MY_LIST%
echo return=%return%
exit /b
:find index list...
for /L %%N in (1 1 %~1) do shift /1
set "return=%~1"
exit /b
Try this, I think it answers your question. Put it in a bat file and then build whatever else you need around it after you see this work. Execute it with a quoted string from a command prompt. YourBatFile "Arg1 Arg2 Arg3 Etc"
#echo off
call :DoSomethingWithEach %~1
goto :eof
:DoSomethingWithEach
for %%a in (%*) do echo.%%a
goto :eof
How can I set result variable in a scope surrounded with parantheses ('if' or 'for'-loop). The result is correct (>> RESULT: aaa = bbb), when procedure is called directly, and fails when used in for-loop or if-statement (>> RESULT: ccc = ).
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
echo RESULT: ccc = %ccc%
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
Thanks!!
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
inside a code block (=surrounded with parantheses) you need delayed expansion and !variables!, not %variables%:
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
setlocal enabledelayedexpansion
echo RESULT: ccc = !ccc!
endlocal
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
There are two files, output.txt and test.bat. In output.txt, there's only one line, '1 2 3 4', and the content of test.bat is
#echo off
set condition=1
if "%condition%" == "1" (
for /F "tokens=1,2,3,4* delims= " %%a in (output.txt) do set variable=%%a
echo %variable%
)
pause
run test.bat will echo nothing. but if I change it a little bit, to the following:
#echo off
set condition=1
for /F "tokens=1,2,3,4* delims= " %%a in (output.txt) do set variable=%%a
echo %variable%
if "%condition%" == "1" (
for /F "tokens=1,2,3,4* delims= " %%a in (output.txt) do set variable=%%a
echo %variable%
)
pause
it will echo '1' twice.
Weird? Bug?
I don't think either of the two scripts does what you think it does (specifically, the second script works like that only because you run it after the first).
Why does it behave like that?
The important thing to note is that unless delayed variable expansion is turned on, variables will be evaluated for each command before that command is executed. This is especially critical when using IF, because the whole command block inside the parens is considered as one command.
To illustrate:
SET foo=
ECHO foo = %foo%
IF 1==1 (
SET foo=bar
ECHO foo = %foo%
)
The above script will output:
foo =
foo =
The reason is that the condition is equivalent to
IF 1==1 SET foo=bar && ECHO foo = %foo%
That's just one command, so variables are expanded just once before it runs (in particular, they are not expanded after the SET and before the ECHO).
This is also what happens in your script. Because %variable% is set and echoed inside the block, the ECHO actually operates on the value %variable% had before the block was entered and thus you don't see the "current" value.
How to fix it?
Solving the problem with delayed variable expansion
There are two ways you can go about this. The straightforward one is to enable delayed variable expansion with SETLOCAL and then refer to variables with the syntax !var! instead of %var%:
SETLOCAL ENABLEDELAYEDEXPANSION
SET foo=
ECHO foo = !foo!
IF 1==1 (
SET foo=bar
ECHO foo = !foo!
)
The above script will output:
foo =
foo = bar
Solving the problem by breaking up the command block
There's also another way to do this: remember that variables are expanded once before each command (or block) is executed. It follows that if you want an expansion to occur between the SET and the ECHO, you can cause that by breaking up the block. You can do this by reversing the test with NOT and using a GOTO to skip the code that was earlier inside the "successful" branch:
SET foo=
ECHO foo = %foo%
IF NOT 1==1 GOTO :proceed
SET foo=bar
ECHO foo = %foo%
:proceed
The above script will also output:
foo =
foo = bar
Applying the solution to your example
The two approaches translate to either this:
SETLOCAL ENABLEDELAYEDEXPANSION
IF "%condition%" == "1" (
FOR /F "tokens=1,2,3,4* delims= " %%a in (output.txt) DO SET variable=%%a
ECHO !variable!
)
Or this:
IF NOT "%condition%" == "1" GOTO :proceed
FOR /F "tokens=1,2,3,4* delims= " %%a in (output.txt) DO SET variable=%%a
ECHO %variable%
:proceed
Not a bug -- it's a side effect of how CMD implements variable expansion. Raymond Chen has an article on it here:
http://blogs.msdn.com/b/oldnewthing/archive/2006/08/23/714650.aspx
Specifically, in your first example, as of when %variable% is expanded, it's never been set.