Implement 'which' command in batch script

2019-04-11 13:23发布

I have tried to make a script that works like which. So I want to check if this command is internal or external or both. I tried to execute program, but when the program(.exe for example) was incorrect my program exit with errors so I tried to call help for this program. But now if I try to check for example echo and I have echo.exe in my current dir I get that it is external command, but not internal. So my question is how to do it right. How to check if this program internal or external. Thanks. Here is my code:

@echo off
setlocal enabledelayedexpansion
if "%1"=="" goto help

:start
if not "%1"=="" (
    shift
    if "%1"=="/?" goto help 
    goto :start
)
set arg=%*

for %%a in ("%pathext:;=" "%") do (
    echo %arg%|findstr /E /I %%a >nul
    rem If file was given with extension
    if not ERRORLEVEL 1 goto with_rashr
)

set ext=0
for %%a in ("%pathext:;=" "%") do (
    if EXIST "%CD%\!arg!%%~a" (
        echo This is an external command: %CD%\!arg!%%~a
        set ext=1
        goto :internal
    )
)

for %%G in ("%path:;=" "%") do (
    for %%a in ("%pathext:;=" "%") do (
        if EXIST "%%~G\!arg!%%~a" (
            echo This is an external command: %%~G\!arg!%%~a
            set ext=1
            goto :internal
        )
    )
)
goto :internal

:with_rashr
echo Command with extension was given
if EXIST "%CD%\!arg!" (
    echo This is an external command: %CD%\!arg!
    set ext=1
    goto :internal
)
for %%G in ("%path:;=" "%") do (
    if EXIST "%%~G\!arg!" (
        echo This is an external command: %%~G\!arg!
        set ext=1
        goto :internal
    )
)

:internal
set PATH=
rem set PATH=%PATH%;%CD%
help %arg% >nul 2>&1
rem set error_check=%ERRORLEVEL%
rem echo %error_check%
rem %arg% /?
rem ERRORLEVEL 9009 when a batch attempts to execute a program that is not found.
rem echo %ext%
if ERRORLEVEL 9009 (
    echo We couldn't execute command
    if "%ext%"=="0" (
        echo This is not a command
    )
    endlocal
    goto :EOF
)
if "%ext%"=="1" (
    if ERRORLEVEL 0 (
        echo This is also an internal command
    )
    endlocal
    goto :EOF
)
echo This is internal command

endlocal
goto :EOF

:help
echo Like which
echo Shows if this command external or internal

1条回答
家丑人穷心不美
2楼-- · 2019-04-11 13:41

Good question, and it is surprisingly difficult to solve.

I thought I had a good working version at http://ss64.org/viewtopic.php?pid=5752#p5752, in which I responded to another persons attempt to do basically the same thing. But after reading your question, I realize my old "solution" suffers from the same problem - it falsely reports an internal command as an external command if there happens to be an exe with the same root name somewhere in the path.

I was first checking if I could find an external command, and if not, then I assumed any command that HELP knew about was an internal command.

I think I now have a working version.

I check to see if the command is internal first, using similar techniques as you tried. Some twists that I added.

  • I am depending on an empty folder in the %temp% folder (I create it if it does not exist)
  • I worry about command injection, so I verify that the command contains nothing but letters before I do my internal test.
  • The (CALL ) command is a quick method to clear the ERRORLEVEL

As far as I know, every internal command will print out help info if given an argument of /?. If the command is not internal, then it will fail to execute and set ERRORLEVEL to 9009 because PATH is empty and the current directory is empty.

::WHICH  CommandName  [ReturnVar]
::
::  Determines the full path of the file that would execute if
::  CommandName were executed.
::
::  The result is stored in variable ReturnVar, or else it is
::  echoed to stdout if ReturnVar is not specified.
::
::  If no file is found, then an error message is echoed to stderr.
::
::  The ERRORLEVEL is set to one of the following values
::    0 - Success: A matching file was found
::    1 - CommandName is an internal command
::    2 - No file was found and CommandName is not an internal command
::    3 - Improper syntax - no CommandName specified
::
@echo off
setlocal disableDelayedExpansion

set "file=%~1"
setlocal enableDelayedExpansion

if not defined file (
  >&2 echo Syntax error: No CommandName specified
  exit /b 3
)


:: test for internal command
echo(!file!|findstr /i "[^abcdefghijklmnopqrstuvwxyz]" >nul || (
  set "empty=!temp!\emptyFolder"
  md "!empty!" 2>nul
  del /q "!empty!\*" 2>nul >nul
  setlocal
  pushd "!empty!"
  set path=
  (call )
  !file! /? >nul 2>nul
  if not errorlevel 9009 (
    >&2 echo "!file!" is an internal command
    popd
    exit /b 1
  )
  popd
  endlocal
)


:: test for external command
set "noExt="
if "%~x1" neq "" if "!PATHEXT:%~x1=!" neq "!PATHEXT!" set noExt="";
set "modpath=.\;!PATH!"
@for %%E in (%noExt%%PATHEXT%) do @for %%F in ("!file!%%~E") do (
  setlocal disableDelayedExpansion
  if not "%%~$modpath:F"=="" if not exist "%%~$modpath:F\" (
    endlocal & endlocal & endlocal
    if "%~2"=="" (echo %%~$modpath:F) else set "%~2=%%~$modpath:F"
    exit /b 0
  )
  endlocal
)
endlocal


>&2 echo "%~1" is not a valid command
exit /b 2
查看更多
登录 后发表回答