Home > Net >  Batch script to check whether the required NPM package is installed or not doesn't work reliabl
Batch script to check whether the required NPM package is installed or not doesn't work reliabl

Time:09-07

I have come up with a batch script, through some Googling and analysis on batch scripts, for checking if the package I mention in user prompt exists in system and get its version if it does. Here it is in its most possible final version:

@echo off && setlocal EnableDelayedExpansion
set "output_cnt=0"
set /p "pkg=Package? "
for /f "delims=" %%a in ('npm -v 2^>nul') do @set "npmV=%%a"
if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
for /f "delims=" %%b in ('node -v 2^>nul') do @set "nodeV=%%b"
if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
    set /a output_cnt =1
    set "pkgV[!output_cnt!]=%%c"
)
if defined pkgV (for /l %%n in (1 1 !output_cnt!) do echo !pkg! Version: !pkgV[%%n]!) else (echo %pkg% isn't installed)
setlocal DisableDelayedExpansion && endlocal && pause

Now when I run this script, it does check properly whether Node and NPM are installed and gets their version accurately, but when I pass any package name to the user prompt (e.g. cowsay, and this package is installed, of that I am sure), it always says <package> isn't installed(as in cowsay isn't installed).

As you can see I am trying to capture full output of npm list -g <package> with multiple lines through array, but it clearly screws up.

Can anyone assist in figuring out what's wrong and get what I want?

CodePudding user response:

Welcome to batch; arrays aren't real!*

Instead, you've got a collection of variables that all simply happen to start with pkgV[ that you're able to iterate over. So while %pkgV[1]%, %pkgV[2]%, etc. are all defined, %pkgV% by itself isn't because that particular variable was never set.

However, since you'll always have a %pkgV[1]% if any valid packages have been provided, you can check that pkgV[1] is defined and go from there. (Alternately, you can simply check that %output_cnt% is greater than 0.)

if defined pkgV[1] (for /l %%n in (1 1 !output_cnt!) do echo !pkg! Version: !pkgV[%%n]!) else (echo %pkg% isn't installed)

* - At least, not in the way that other languages do arrays.

CodePudding user response:

Why do you use a (pseudo-)array after all? Why not simply doing all in a single loop?

I see two possible code portions to replace the for /f %%c loop and the subsequent if defined pkgV condition (which actually is the faulty code fragment as explained in SomethingDark's answer) with:

  1. Use a flag-style variable that indicates whether npm list returned any items:

    @echo off & setlocal EnableExtensions DisableDelayedExpansion
    set "output_cnt=0"
    set /p "pkg=Package? "
    for /f "delims=" %%a in ('npm -v 2^>nul') do @set "npmV=%%a"
    if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
    for /f "delims=" %%b in ('node -v 2^>nul') do @set "nodeV=%%b"
    if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
    set "FLAG="
    for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
        set "FLAG=#" & echo !pkg! Version: %%c
    )
    if not defined FLAG echo %pkg% isn't installed
    pause
    
  2. Detect the exit code of the for /f %%c loop by conditional execution, which is set to 1 when the loop doesn't iterate:

    @echo off & setlocal EnableExtensions DisableDelayedExpansion
    set "output_cnt=0"
    set /p "pkg=Package? "
    for /f "delims=" %%a in ('npm -v 2^>nul') do @set "npmV=%%a"
    if defined npmV (echo NPM Version: %npmV%) else (echo NPM isn't installed)
    for /f "delims=" %%b in ('node -v 2^>nul') do @set "nodeV=%%b"
    if defined nodeV (echo Node Version: %nodeV%) else (echo Node isn't installed)
    (
        for /f "delims=" %%c in ('npm list -g %pkg% 2^>nul') do (
            echo !pkg! Version: %%c
        )
    ) || echo %pkg% isn't installed
    pause
    

N. B.:
By the way, the command sequence setlocal DisableDelayedExpansion && endlocal at the end doesn't make any sense at all, since the state afterwards is just the same as without it.
If the intention was to surely have delayed expansion disabled at the pause command, you should have replaced your initial line with @echo off & setlocal EnableExtensions DisableDelayedExpansion and have inserted the line setlocal EnableDelayedExpansion before the for /f %%c loop in your original code.

CodePudding user response:

If there will only be one installed version of any input package, then perhaps this would work for you:

@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion

Set "nodeV="
For %%G In ("node.js") Do For /F %%H In (
    '"%%~$PATH:%%G" -v') Do Set "nodeV=%%H"
If Defined nodeV (Echo Node Version: %nodeV%
) Else Echo node.js is not in your %%PATH%%

Set "npmV="
For %%G In ("npm.cmd") Do For /F %%H In (
    '"%%~$PATH:%%G" -v') Do Set "npmV=%%H"
If Defined npmV (Echo NPM Version: %npmV%
) Else (Echo npm.cmd is not in your %%PATH%%
    Echo Press any key to exit ...
    Pause 1>NUL
    Exit /B)

:GetPkg
Set "pkg="
Set /P "pkg=Package? "
If Not Defined pkg GoTo GetPkg

Set "pkgV="
For /F "Tokens=2 Delims=@" %%G In (
    'npm.cmd list "%pkg%" -g 2^>NUL
     ^| %SystemRoot%\System32\find.exe "@"'
) Do Set "pkgV=%%G"
If Defined pkgV (Echo %pkg% Version: %pkgV%
) Else Echo %pkg% is not globally installed

Pause
EndLocal
GoTo :EOF

However, if there could be multiple installed versions of your input package, as implied by your code, then this modification may prove more useful:

@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion

Set "nodeV="
For %%G In ("node.js") Do For /F %%H In (
    '"%%~$PATH:%%G" -v') Do Set "nodeV=%%H"
If Defined nodeV (Echo Node Version: %nodeV%
) Else Echo node.js is not in your %%PATH%%

Set "npmV="
For %%G In ("npm.cmd") Do For /F %%H In (
    '"%%~$PATH:%%G" -v') Do Set "npmV=%%H"
If Defined npmV (Echo NPM Version: %npmV%
) Else (Echo npm.cmd is not in your %%PATH%%
    Echo Press any key to exit ...
    Pause 1>NUL
    Exit /B)

:GetPkg
Set "pkg="
Set /P "pkg=Package? "
If Not Defined pkg GoTo GetPkg

For /F "Delims==" %%G In ('"(Set pkgV[) 2>NUL"'
) Do Set "%%G="
Set "output_cnt=0"
For /F "Tokens=2 Delims=@" %%G In (
    'npm.cmd list %pkg% -g 2^>NUL
     ^| %SystemRoot%\System32\find.exe "@"'
) Do (Set /A output_cnt  = 1
    SetLocal EnableDelayedExpansion
    For %%H In (!output_cnt!) Do (EndLocal
        Set "pkgV[%%H]=%%G"))
If %output_cnt GEq 1 (
    For /F "Tokens=2,* Delims=[]=" %%G In (
        '"(Set pkgV[) 2>NUL"'
    ) Do Echo %pkg% version %%G: %%H
) Else Echo %pkg% is not globally installed

Pause
EndLocal
GoTo :EOF

Please note, that none of the above code has been tested at all, if there are obvious typos, or errors, please let me know, and I'll take a look at it later.

  • Related