RRR
RRR

Reputation: 91

How to assign values to a variable dynamically in batch script?

Assume a loop is run over an array of command line arguments like shown below. Now how do i assign the next following command line value to a variable within the loop?

FOR %%q IN (%*) DO
(
IF %%q == "abc" (
set a = //next command line value
             )
)

In linux this can be done like

argv=("$@");
i=0
while(($i < ${#argv[*]})); do
case "${argv[$i]}" in
abc)
  ((i++)); a="${argv[$i]}"
;;
esac
((i++))
done

I want to know the equivalent of this in batch file.

Upvotes: 0

Views: 3563

Answers (3)

J.Baoby
J.Baoby

Reputation: 2231

I wouldn't use the for-loop to read arguments. As LinuxDisciple mentioned it, don't compare batch to bash or csh too quickly. For eg. it isn't easy to create or access arrays or hashes in batch (if interested I added some links at the end of my answer where they explain how it is possible). If you really want to use a for-loop, the use of the state variable is probably your best shot (see Klitos Kyriacou's answer).

I would use the traditional labels and goto instead in batch

@echo off
SetLocal EnableDelayedExpansion
:check_args
REM Check if arguments left
IF "%~1%" == "" goto :EndArgs
IF "%~1" == "abc" (
    REM next argument is %2
    set var=%~2
    REM Don't forget extra shift because you already handled next argument too
    shift
    echo I just saw you gave "abc", I set var to !var! 
) ELSE (
    echo It's just a normal argument: "%~1"
)
shift
goto :check_args

:EndArgs
EndLocal
exit

As LinuxDisciple mentionned also, don't surround the "=" with spaces when assigning variables, it may give unexpected results(variable names with trailing spaces, values with heading space, ...). The above code will result in stuff like (script is called argum.bat):

>argum abc blabla def
I just saw you gave "abc", I set var to "blabla"
It's just a normal argument: def

>argum "just random things" abc "this is more serious"
It's just a normal argument: "just random things"
I just saw you gave "abc", I set var to "this is more serious"

Here is a link to delayed expansion. It is needed in this case because the cmd-parser will load IF-clauses and FOR-loops as one single command (as if it was written on one line). You can't create a variable and read from it in the same command unless you use delayed expansion. It also explains why I use !var! instead of %var%. I also used %~1 instead of %1 to remove the surrounding quotation marks ("). This link has some other "tricks" you can use to manipulate arguments and for-loop variables

Off course if you want to do some more complex things with your arguments this isn't a good option because of the size the if-else clauses will take. You'll have to use some other way to treat your arguments like the one I used when I asked this question (please read the answer as well) or the method proposed as answer in this question (also posted as link in an answer to my question as well).

Good luck!

PS As promissed, the links to the array solutions in batch:

  • In this tutorial you'll see why it's "tricky" to use the normal array indexing
  • Another way is to list all elements in a string, separate them with a separator (whitespace is mostly used) and use the for /f-loop with the separator as delimiter to iterate (as what you did). Problem here is that you'll have to use extra variables to acces other array elements than the one you're at during the iteration.

Upvotes: 1

LinuxDisciple
LinuxDisciple

Reputation: 2379

Like J. Baoby's solution, this solution doesn't use a for loop or a state variable. This solution also doesn't need delayed expansion or setlocal
Adding handling for more arguments is straightforward here; there are no nested IFs.
This parses out the value preceded by "-a" and the value preceded by "-b". Then it stores the other arguments separately in a variable called otherArgs in case you want them later.

@ECHO OFF
REM make sure we don't start with unintended values
set avar=
set bvar=
set otherArgs=
:loop
REM Exit the loop when we run out of input
if "%1" == "" goto outsideOfLoop

REM ==Check for an argument which is preceded by '-a'===============
if "%1"=="-a"  set avar=%2 & shift /1 & shift /1 & goto :loop
REM ==END '-a' handling here========================================

REM ==Check for an argument which is preceded by '-b'===============
if "%1"=="-b"  set bvar=%2 & shift /1 & shift /1 & goto :loop
REM ==END -'b' handling here========================================

REM default case here
set otherArgs=%otherArgs% %1
shift /1
goto :loop
:outsideOfLoop
echo.%%avar%% is %avar%
echo.%%bvar%% is %bvar%
echo.Other arguments were: %otherArgs%

Output:

C:\Users\me>junk.bat hi there def -b BValue ef -a aValue valuebc 123 456
%avar% is aValue
%bvar% is BValue
Other arguments were:  hi there def ef valuebc 123 456

This has the side-effect of consuming the %I arguments, but it doesn't require an ever-increasing depth of nested if-statements in order to handle more arguments, and it separates the arguments you've processed from the others that were supplied so if you needed to process those later, you don't have to remove the ones you already recognized.

Upvotes: 0

Klitos Kyriacou
Klitos Kyriacou

Reputation: 11621

Use a state variable.

setlocal
rem Undefine state variable first, in case it's defined somewhere else.
set is_abc=
for %%q in (%*) DO (
   if defined is_abc (
      set a=%%q
      set is_abc=
   ) else (
      if "%%q" == "abc" set is_abc=1
   )
)

Upvotes: 0

Related Questions