:: :: which.bat - Find or execute programs (or documents) on the PATH or any path. :: :: Jason Hood, 22 to 26 July, 2004. :: :: v1.01, 16 March, 2005: :: fixed problem with / with no arguments (was using program as arg). :: :: v1.02, 3 October, 2005: :: fixed problem finding thousands separator when using 12-hour time. :: :: v1.03, 25 July, 2006: :: fixed problem when a path in PATH contained spaces. :: @echo off SetLocal EnableExtensions EnableDelayedExpansion ::~ only removes enclosing quotes, not embedded quotes. ::Using substitution on an empty variable returns the substitution. set "arg=%~1" if not defined arg goto help set "arg=!arg:"=! if "!arg!" == "/?" goto help if "!arg!" == "-?" goto help if "!arg!" == "--help" goto help set all= set detail= ::Recognise options beginning with '-' as well. set "arg=!arg:-=/!" if "!arg:~,1!" == "/" ( ::Combine multiple options (so "/a/d" becomes "ad"). set "arg=!arg:/=!" if not defined arg goto for ::Determine if a number option has been given (ignore expression errors). set /a "run=!arg!" 2>nul if "/!run!" == "%arg%" ( ::Strip the number and program, leaving the arguments to the program set "arg=%*" set "arg=!arg:%1 =!" if "!run!" == "0" goto run if "%~3" == "" ( set arg= ) else ( set "arg=!arg:%2 =!" ) set /a run-=1 set count=!run! set all=1 shift ) else ( set run= if /i "!arg!" == "a" set all=1 & shift if /i "!arg!" == "d" set detail=1 if /i "!arg!" == "ad" set all=1 & set detail=1 if /i "!arg!" == "da" set all=1 & set detail=1 ) ) ::Shift is not used with detail because find_sep requires %0. if defined detail ( call :find_sep shift ) :for set "file=%~1" if not defined file goto :eof set "file=!file:"=! ::Display file for error messages. set "dfile=!file!" set "file=!file:/=\!" if "!file:~-1!" == "\" ( if not exist "!file!" ( echo "!dfile!" does not exist. goto :eof ) if defined all ( call :recurse "%~dp1" "%PATHEXT:.=*.%" if !count! == 0 echo "!dfile!" and its subdirectories have no programs. goto :eof ) set "file=!file!*" set dir_progs=1 ) else ( set dir_progs= ) ::Test for a path and extension (can't use ~p since it will use the current ::directory if no path was given; can't use ~x because it expands wildcards). ::Skip over the drive, but if it's only a drive ask to list every program on it. if "!file:~1,1!" == ":" ( set "pth=!file:~2!" if not defined pth ( set /p "all=List all programs on %~d1? " if /i "!all:~,1!" == "y" ( call :recurse %~d1\ "%PATHEXT:.=*.%" if !count! == 0 ( echo %~d1 has no programs^^! ) else ( echo. echo %~d1 has !count! programs. ) ) goto :eof ) ) else ( set "pth=!file!" ) ::Find the name portion. :path_loop set "name=!pth!" set "pth=!pth:*\=!" if not "!name!" == "!pth!" goto path_loop if "!file!" == "!name!" ( ::No path, so search the current directory and PATH. set "pth=.;!Path!" set "file=\!name!" ) else ( ::A path was given, so use only it. set pth="" ) ::If there's no extension use all the ones in PATHEXT. if "!name:.=!" == "!name!" (set "ext=!PATHEXT!") else set ext="" if "!pth!" == """" ( if defined all ( if "!ext!" == """" ( call :recurse "%~dp1" "!name!" set msg=files ) else ( call :recurse "%~dp1" "%PATHEXT:.=!name!.%" set msg=programs ) if !count! == 0 ( echo "%~dp1" and its subdirectories have no !msg! matching "!name!". ) goto :eof ) else ( set all=1 ) ) ::Scan each directory in PATH (pth) and each extension in PATHEXT (ext). set found= for %%D in ("!pth:;=" "!") do for %%E in (!ext!) do ( if exist "%%~D!file!%%~E" for %%P in ("%%~D!file!%%~E") do ( if defined run ( if !run! == 0 ( "%%~fP" !arg! goto :eof ) else set /a run-=1 ) else ( ::Disable delayed expansion to allow display of filenames containing '!'. SetLocal DisableDelayedExpansion if defined detail ( call :display "%%~fP" ) else ( echo %%~fP ) EndLocal ) if not defined all goto next set found=1 ) ) if defined run ( set /a run=!count! - !run! echo "!dfile!" only has !run! possibilities. goto :eof ) if not defined found ( if defined dir_progs ( echo "!dfile!" has no programs. ) else ( if "!ext!" == """" ( set msg="!dfile!" does not exist ) else ( set msg="!dfile!" is not a program ) if "!pth!" == """" ( echo !msg!. ) else ( echo !msg! in the current directory or on the PATH. ) ) ) :next shift goto for :run ::Move this out of the way to avoid problems with not quoting. !arg! goto :eof :recurse ::Find and count all the programs in the specified directory and its subdirs. SetLocal DisableDelayedExpansion set count=0 for /R "%~1" %%P in (%~2) do ( if defined detail ( call :display "%%~fP" ) else ( echo %%~fP ) set /a count+=1 ) EndLocal & set count=%count% goto :eof :display call :pad %%~z1 echo %~t1 %size% %~f1 goto :eof ::Space-pad the size to 8 digits and optionally thousands-separate. ::Using carets prevents tab compression. :pad SetLocal EnableDelayedExpansion if not defined sep ( if %1 GTR 99^999^999 ( set size=%1 ) else ( set size=^ ^ ^ ^ ^ ^ ^ %1 set size=!size:~-8! ) ) else ( if %1 LSS 1000 ( set size=^ ^ ^ ^ ^ ^ ^ ^ ^ %1 set size=!size:~-10! ) else ( set /a thou=%1 / 1000 set /a remr=%1 %% 1000 set remr=00!remr! if %1 LSS 1^000^000 ( set thou=^ ^ ^ ^ ^ !thou! set size=!thou:~-6!%sep%!remr:~-3! ) else ( set /a mill=%1 / 1^000^000 set /a thou%%=1000 if !mill! LSS 10 set mill= !mill! set thou=00!thou! set size=!mill!%sep%!thou:~-3!%sep%!remr:~-3! ) ) ) EndLocal & set size=%size% goto :eof ::Use DIR to find the local thousands separator (assumes %0 is still valid). :find_sep if defined DIRCMD ( ::Has the user prevented DIR from displaying the separator? set dirc=!DIRCMD:/-c=! if defined dirc if not [!dirc!] == [!DIRCMD!] set dirc= ) else ( set dirc=1 ) if defined dirc ( ::Use /-n to put the name and extension first, followed by the size; without ^ ::it, the time comes first, which uses one or two tokens depending on the ^ ::system using 12- or 24-hour time. /w/-w ensures a long listing (/-w ^ ::without DIRCMD=/w still results in a wide listing). for /F "tokens=3" %%S in ('dir/-n/w/-w %~s0^|findstr %~sn0') do set sep=%%S ::The fourth-last character is the thousands separator (just in case this ^ ::program grows above 9k; does everybody group by three?). set sep=!sep:~-4,1! ) else ( set sep= ) goto :eof :help SetLocal DisableDelayedExpansion set help= for /F "skip=250 delims=" %%L in (%~f0) do ( if defined help ( if "%%L" == "." (echo.) else echo %%L ) else ( if "%%L" == "::Help" set help=1 ) ) goto :eof ::Help Which by Jason Hood . Version 1.03 (25 July, 2006). Public Domain. http://misc.adoxa.cjb.net/ . Determine where on the PATH a file is located. Execute a program (or document) further along the PATH. Find all programs on a drive or in a directory (tree). . Usage: which [/a][/d] file... which / program [arguments] . /a will find all instances of file or recurse subdirectories /d will display file details (date, time & size) / will execute the th instance of program (0 runs directly) . Notes: . * If file has no path then it will be searched for in each directory in %PATH%, stopping at the first match found; /a will keep searching. * If file has a path then it will be searched for in that directory only, finding all matches; /a will also search in subdirectories. * If file has no extension then all extensions in %PATHEXT% will be used. * Wildcards are allowed. * Files (or arguments) containing "&()^" should be quoted and/or prefixed with "^"; "!" should be prefixed with "^^^^" (just "^^" within quoted files).