1

I'm trying to write my own batch files to switch npm versions (just for fun).

@echo off
for /f "delims=" %%i in ('npm -v') do set output=%%i
move "%NPM_PATH%" "%NPM_PATH%-%output%"

FOR %%A IN (%*) DO (
  IF "%%A"=="--change" (
    SETLOCAL EnableDelayedExpansion

    SET /A c=1
    ECHO "ID : VERSION"
    ECHO -------------------------

    FOR /F "tokens=*" %%F in ('dir /on /b /a:d /p "%NPM_DIR%"') DO ( 
      ECHO !c! : %%F
      SET dir_!c!=%%F
      SET /a c=c+1   
    )

    set /p id="enter version ID: (e.g. !dir_1! enter 1):"
    set target_dir= "dir_!id!"
    echo !target_dir!
  )
)

I can echo out the right variable I want to abstract.

But how do I actually output the value of !target_dir!?

Example:

User selects 1

echo !target_dir! will print dir_1 to the screen.

But when I use next echo !dir_1! then npm-4.0.0 would be printed.

How do I now output npm-4.0.0 when the user inputs 1?

Mofi
  • 38,783
  • 14
  • 62
  • 115
TheRealMrCrowley
  • 936
  • 7
  • 23
  • Change this line: `set target_dir= "dir_!id!"` by this one: `call set target_dir= "%%dir_!id!%%"` or this one: `for %%i in (!id!) do set target_dir= "!dir_%%i!"`; the second is the preferable one. See [this question](http://stackoverflow.com/questions/40096886/how-to-use-delayed-variable-expansion-to-reference-a-variable-from-a-string/). – Aacini Oct 22 '16 at 10:49
  • this is the answer to my question. – TheRealMrCrowley Oct 22 '16 at 18:48
  • though i had to remove the space and quotes `for %%i in (!id!) do set target_dir=!dir_%%i!` – TheRealMrCrowley Oct 22 '16 at 18:56

2 Answers2

1

I suggest using the following code with the second suggestion offered by Aacini used in code and some other improvements:

@echo off
for /F "delims=" %%I in ('npm.bat -v') do set "output=%%I"
move "%NPM_PATH%" "%NPM_PATH%-%output%"

for %%A in (%*) do (
    if "%%A" == "--change" (
        setlocal EnableDelayedExpansion
        cls
        set "Count=1"
        echo ID : VERSION
        echo -------------------------

        for /F "tokens=*" %%F in ('dir /A:D /B /ON "%NPM_DIR%"') do (
            if !Count! LSS 10 (
                echo  !Count! : %%F
            ) else (
                echo !Count! : %%F
            )
            set "dir_!Count!=%%F"
            set /A Count+=1
        )

        echo.
        set "id=1"
        set /P "id=Enter version ID: (e.g. !dir_1! enter 1): "
        for %%I in (!id!) do set "TargetDir=!dir_%%I!"
        echo !TargetDir!
        endlocal
    )
)

Why for %%I in (!id!) do set "TargetDir=!dir_%%I!" assigns the value of the environment variable with name dir_X with X being the entered number stored in environment variable id is explained in detail in Aacini's answer on Arrays, linked lists and other data structures in cmd.exe (batch) script.

For the applied improvements see:

It is advisable using environment variables with a self-explaining name in CamelCase notation. This has the advantage of knowing what the variable is for and makes it easier to search for all occurrences of a variable. It is a bit difficult to search for a variable with name c as a single character often occurs in many strings.

The command set with option /A means everything next should be interpreted as arithmetic expression. This means that the string after the equal sign is split up into a list of strings using spaces/tabs as string separators. Each string which could be interpreted as decimal, octal or hexadecimal number is converted to a 32-bit signed integer. All other strings which are not operators are interpreted as name of a variable whose value should be converted to a 32-bit signed integer. If the variable does not exist or has not a number as value, 0 is used for the variable. Then the arithmetic expression is evaluated and the result is converted back from integer to string. Finally the result string is assigned to the environment variable left to the equal sign. So with SET /A c=1 at lot of extra work is done in comparison to SET "c=1" although the result is the same, a string with value 1 is assigned to variable with name c. Therefore it is recommended not using /A on assigning a value to an environment variable except there is a very good reason to do so like a primitive check for string being really a number. Note: An assignment like set /A c=009 results in an error message because of 009 being an invalid octal number and c is not defined or keeps its current value in case of being already defined while set c=009 works.

When a batch user is prompted for a string, the user has the freedom to just hit RETURN or ENTER without entering anything at all. In this case the environment variable keeps its current value or is still not defined if it was not defined already before prompting the user. This explains why set "id=1" is present in batch code above before prompting the user. Well, it is not checked if the user has really entered a number and if this number is within 1 ... !Count!, but perhaps this is not really necessary although a typing mistake could always occur.

Community
  • 1
  • 1
Mofi
  • 38,783
  • 14
  • 62
  • 115
  • My only question here is about abstraction. why would i write an if statement to do two the exact same thing as the else? that's just pointless – TheRealMrCrowley Oct 22 '16 at 18:48
  • and i don't how specifying it is an int, is "a lot of extra work" there can't be much more than a validator going on behind the scenes for the difference – TheRealMrCrowley Oct 22 '16 at 18:50
  • The two __echo__ command lines in the two __IF__ branches are not the same. Look at them carefully. There is a difference of 1 space. The TRUE branch for count values smaller than 10 output the number and the directory name with a leading space. The FALSE branch for count values of 10 or greater output the number without a leading space. The result is right aligned numbers below `ID` for `1` to `99`. Well, if the number of directories matching `%NPM_DIR%` is always less than 10, then the IF condition is indeed useless and __echo__ line of TRUE branch is all really needed. – Mofi Oct 23 '16 at 08:11
  • You can of course always use `/A` on assigning a string value being a number to an environment variable and let `cmd.exe` do the extra work taking just a few microseconds (or less) to convert the number to integer and back to a string. But be aware of `set /A Number=009` results in environment variable `Number` not being defined and `set /A Number=020` results in `16` being assigned to the environment variable instead of 20 with a leading 0. – Mofi Oct 23 '16 at 08:13
  • there now it's back to even for you – TheRealMrCrowley Oct 26 '16 at 20:05
0

As per @Aacini I just needed to change this line: set target_dir= "dir_!id!" to this one: : for %%i in (!id!) do set target_dir=!dir_%%i!

See this question

TheRealMrCrowley
  • 936
  • 7
  • 23