I have a command's name and I need to check if this command is internal. How can I do it in a batch script?
So after a lot of tweaking, and thanks to the help of #Andriy M, it finally works.
#ECHO off
CALL :isInternalCommand dir dirInternal
ECHO is dir internal: %dirInternal%
CALL :isInternalCommand find findInternal
ECHO is find internal: %findInternal%
exit /b 0
:isInternalCommand
SETLOCAL
MKDIR %TEMP%\EMPTY_DIR_FOR_TEST > NUL 2>& 1
CD /D %TEMP%\EMPTY_DIR_FOR_TEST
SET PATH=
%~1 /? > NUL 2>&1
IF ERRORLEVEL 9009 (ENDLOCAL
SET "%~2=no"
) ELSE (ENDLOCAL
SET "%~2=yes"
)
GOTO :EOF
OLD SOLUTION
You can use where. If it fails, the command is probably internal. If it succeeds, you get the executable path that proves it's not internal.
C:\Users\user>where path
INFO: Could not find files for the given pattern(s).
C:\Users\user>where find
C:\Windows\System32\find.exe
EDIT: As the comments suggest, this might not be the best solution if you're looking for portability and not just research. So here's another possible solution.
Set %PATH% to nothing so HELP can't find anything and then run HELP on the command you're trying to check.
C:\Users\user>set PATH=
C:\Users\user>path
PATH=(null)
C:\Users\user>%WINDIR%\System32\help del
Deletes one or more files.
DEL [/P] [/F] [/S] [/Q] [/A[[:]attributes]] names
[...]
C:\Users\user>%WINDIR%\System32\help find
'find' is not recognized as an internal or external command,
operable program or batch file.
This might still fail if the command doesn't have help.
EDIT 2: Never mind, this won't work either. Both cases return %ERRORLEVEL%=1.
kichik has a good answer. However, it can give a false positive if there happens to be an executable or batch script within the current directory that matches the supplied command name.
The only way I can think of to avoid that problem is to create a folder that is known to be empty within the %TEMP% directory, and then run the test from that folder.
Here is a modified version of kichik's solution that should work.
#echo off
setlocal
::Print the result to the screen
call :isInternal find
call :isInternal dir
::Save the result to a variable
call :isInternal find resultFind
call :isInternal dir resultDir
set result
exit /b
:isInternal command [rtnVar]
setlocal
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
set path=
>nul 2>nul %1 /?
if errorlevel 9009 (set rtn=not internal) else (set rtn=internal)
popd
rd "%empty%"
(
endlocal
if "%~2" neq "" (set %~2=%rtn%) else echo %1 is %rtn%
)
exit /b 0
Here is a script that will simply list all internal commands, assuming that HELP includes a complete list of internal commands.
Update: Both FOR and IF have special parsing rules that prevent those commands from working if executed via a FOR variable or delayed expansion. I had to rewrite this script to use a CALL and execute the command via a CALL argument instead.
#echo off
setlocal enableDelayedExpansion
set "empty=%temp%\empty%random%"
md "%empty%"
pushd "%empty%"
for /f "delims= " %%A in ('help^|findstr /rc:"^[^ ][^ ]* "') do call :test %%A
popd
rd "%empty%"
exit /b
:test
setlocal
set path=
%1 /? >nul 2>nul
if not errorlevel 9009 echo %1
exit /b 0
Related
This could possibly a duplicate of several questions out there in the SO, dostips and ss64. The research I've done point me to look out for _scope_ in functions. but my solutions is simple and straight forward but still problem exists
what is really behind the SETLOCAL and ENDLOCAL
How contextworkd in batch scripts ex (goto) 2>nul
Why (goto) wrapped in braces (explained in dostips)
Here is the code i've written so far to copy file from one place to another.
My Goals were:
Research the scope and contextof batch script
code reuse
#echo off
setlocal EnableDelayedExpansion
goto :main
:main
setlocal
set _app=test
set _base=C:/wamp64/www
set _destination=!_base!/test
set _source=%~dp0%/build
set /A _flag=0
echo *********************************************
echo Deploying in %~1 mode: %TIME%
echo Deploy path: !^_destination!
echo *********************************************
call :check !_base!, !_app!, _flag
if !_flag!==0 (
call :create !_base!, !_app!
)
xcopy "!_source!" "!_destination!" /D /C
exit /b 0
endlocal
:setbase
echo ::::: setting up base :::::
chdir /D C:
rem the base dir for app to exists=> %1
chdir %~1
exit /b 0
:check
echo ::::: checking for local web server destination :::::
call :setbase %~1
set %~3= dir /p|find /C "%~2"
exit /b 0
:create
echo ::::: creating the app folders :::::
rem setting the base to create app folder %1
call :setbase %~1
mkdir %~2
exit /b 0
endlocal
This is the output i get when i initiate deploy.bat
*********************************************
Deploying in production mode: 19:28:53.13
Deploy path: C:/wamp64/www/test
*********************************************
::::: checking for local web server destination :::::
::::: setting up base :::::
0
::::: creating the app folders :::::
::::: setting up base :::::
A subdirectory or file test already exists.
seems like the If !_flag!==0 which is checking whether the app folder exist in the server root and is not working at all. When i learnt the way to pass parameters to other functions; i thought it as passing a pointer like reference but it looks like it is deeply tied to scope.
So what's going on here in the code.
Checking for File/Dir Existence
The code you're employing to verify that a directory (?) exists is
set %~3= dir /p|find /C "%~2"
I don't think this is doing what you intend.
SET /P is often used like this in order to pipe the output of a command into an environment variable. (Though, you are giving /P to dir, which paginates the output—probably not what you intended.) The command you have won't do that, though. I'd guess that what this is accomplishing is setting the var to (literally) " dir /p" and then piping that through find-count. That results of find.exe /C never make it back into the envvar though. The output line "0" is the result of piping (nothing) through find.exe /C.
I propose a simpler test for existence:
IF EXIST "%~2" (SET /A %~3=1) ELSE (SET /A %~3=0)
This test worked on my Win10 machine.
I have seen some variance in how IF EXIST works. If you didn't want to use that, you could do it with a FOR loop.
FOR /F "tokens=*" %%e IN ('DIR /B') DO IF "%%~e"=="%~2" SET /A %~3=1
If you want to use the pipe in the FOR command, you'll have to escape it.
FOR /F "tokens=*" %%e IN ('DIR /B ^| find /C "%~2"') DO SET /A %~3=%%~e
All three of these methods worked for me.
SETLOCAL
I do not think there exists an official reference guide for cmd syntax other than the built-in help (SETLOCAL /?)
My experience with it is that it pushes all the envvars and working directories onto a "stack" and a corresponding ENDLOCAL or EXIT (though in scripts, you almost always want to use EXIT /B) will pop the environment off that "stack." In effect, it means that envvar/CWD changes you make in a SETLOCAL will only be temporary.
The path to the folder and the format come to the input of the batch file
file(for example, txt) (as parameters of a batch file).
The folder must contain different files.
If such folder does not exist, then write “This folder does not exist” and
terminate the program.
If such a folder exists, then find everything in it and in its subfolders
files of the specified extension for which it is installed
archive attribute. Output the number of such files in
console
[Edit /]
This is what I have:
#echo off
if not exist %1 (echo "This folder does not exist" && pause && exit /B )
set /a count=0
for %%i in (dir %1\*.%2 /A:A /S ) do ( set /a count+=1 )
Echo in the folder %1, found %count% files with extension %2 and attribute
Archive
pause
The final count is incorrect
This in a batch file works for me. One problem is that without the /B option, it is also counting extra records coming back for directories. Your current "for" is actually counting the parts of the statement inside it, not the actual output in running the command.
#echo off
cls
SETLOCAL EnableDelayedExpansion
if not exist %1 (echo "This folder does not exist" && pause && exit /B )
set /a count=0
for /f "tokens=*" %%G in ('dir "%1\*.%2" /A:A /S /B') do (
set /a count+=1
)
Echo in the folder %1, found %count% files with extension %2 and attribute Archive
pause
Your line for %%i in (dir %1\*.%2 /A:A /S ) do ... is wrong in a few ways.
You want to process the output of a command: add /f and single-quoting the command.
You count every line of the output (including header and summary: add /b to list just the filenames.
if there are spaces in your parameters, it will fail: use %~n to remove any surrounding quotes and quote the full path.
So allthogether, the line should probably be:
for /f %%i in ('dir /b "%~1\*.%~2" /A:A /S') do ...
See for /? and dir /? for details.
(to be exact, you should also add "delims=" to get the whole filename instead of just its first word, but as you are just counting the lines, it wouldn't change anything)
Another thing: if not exist %1 (echo "This folder does not exist" is suboptimal. If no parameter was given, %1 is empty, the if results in if not exist (echo (tries to find a file named (echo and the command to be executed would be "This folder does not exist" which results in the error message '"This folder does not exist"' is not recognized as an internal or external command, operable program or batch file.
Safer Syntax: if not exist "%~1" (echo ...
The batch has to remove files and directories from specific locations and output success or stdout/stderr messages to a new .txt file. I have created the most of the script and it performs exactly as it should, except when the deletion is successful it moves forward to the next line rather than echo a 'successful' message on the log.
echo Basic Deletion Batch Script > results.txt
#echo off
call :filelog >> results.txt 2>&1
notepad results.txt
exit /b
:filelog
call :delete new.txt
call :delete newer.txt
call :delete newest.txt
call :remove c:\NoSuchDirectory
GOTO :EOF
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 0 echo succesful
GOTO :EOF
:remove
echo deleting directory %1
rmdir /q /s %1
GOTO :EOF
For some reason I can't find the syntax for if del succeeds echo 'successful'. In the above example if I remove the line
if errorlevel 0 echo successful
Everything works fine, but no success message. With this line left in it echoes success for every line.
del and ErrorLevel?
The del command does not set the ErrorLevel as long as the given arguments are valid, it even resets the ErrorLevel to 0 in such cases (at least for Windows 7).
del modifies the ErrorLevel only in case an invalid switch is provided (del /X sets ErrorLevel to 1), no arguments are specified at all (del sets ErrorLevel to 1 too), or an incorrect file path is given (del : sets ErrorLevel to 123), at least for Windows 7.
Possible Work-Around
A possible work-around is to capture the STDERR output of del, because in case of deletion errors, the related messages (Could Not Find [...], Access is denied., The process cannot access the file because it is being used by another process.) are written there. Such might look like:
for /F "tokens=*" %%# in ('del /F /Q "\path\to\the\file_s.txt" 2^>^&1 1^> nul') do (2> nul set =)
To use the code in command prompt directly rather than in a batch file, write %# instead of %%#.
If you do not want to delete read-only files, remove /F from the del command line;
if you do want prompts (in case wildcards ? and/or * are present in the file path), remove /Q.
Explanation of Code
This executes the command line del /F /Q "\path\to\the\file_s.txt". By the part 2>&1 1> nul, the command output at STDOUT will be dismissed, and its STDERR output will be redirected so that for /F receives it.
If the deletion was successful, del does not generate a STDERR output, hence the for /F loop does not iterate, because there is nothing to parse. Notice that ErrorLevel will not be reset in that case, its value remains unchanged.
If for /F recieves any STDERR output from the del command line, the command in the loop body is executed, which is set =; this is an invalid syntax, therefore set sets the ErrorLevel to 1. The 2> nul portion avoids the message The syntax of the command is incorrect. to be displayed.
To set the ErrorLevel explicitly you could also use cmd /C exit /B 1. Perhaps this line is more legible. For sure it is more flexible because you can state any (signed 32-bit) number, including 0 to clear it (omitting the number clears it as well). It might be a bit worse in terms of performance though.
Application Example
The following batch file demonstrates how the above described work-around could be applied:
:DELETE
echo Deleting "%~1"...
rem this line resets ErrorLevel initially:
cmd /C exit /B
rem this line constitutes the work-around:
for /F "tokens=*" %%# in ('del /F /Q "C:\Users\newuser\Desktop\%~1" 2^>^&1 1^> nul') do (2> nul set =)
rem this is the corrected ErrorLevel query:
if not ErrorLevel 1 echo Deleted "%~1" succesfully.
goto :EOF
Presetting ErrorLevel
Besides the above mentioned command cmd /C exit /B, you can also use > nul ver to reset the ErrorLevel. This can be combined with the for /F loop work-around like this:
> nul ver & for /F "tokens=*" %%# in ('del /F /Q "\path\to\the\file_s.txt" 2^>^&1 1^> nul') do (2> nul set =)
Alternative Method Without for /F
Instead of using for /F to capture the STDERR output of del, the find command could also be used like find /V "", which returns an ErrorLevel of 1 if an empty string comes in and 0 otherwise:
del "\path\to\the\file_s.ext" 2>&1 1> nul | find /V "" 1> nul 2>&1
However, this would return an ErrorLevel of 1 in case the deletion has been successful and 0 if not. To reverse that behaviour, an if/else clause could be appended like this:
del "\path\to\the\file_s.ext" 2>&1 1> nul | find /V "" 1> nul 2>&1 & if ErrorLevel 1 (1> nul ver) else (2> nul set =)
Different Approach: Checking File for Existence After del
A completely different approach is to check the file for existence after having tried to delete it (thanks to user Sasha for the hint!), like this, for example:
del /F /Q "\path\to\the\file_s.txt" 1> nul 2>&1
if exist "\path\to\the\file_s.txt" (2> nul set =) else (1> nul ver)
When using this syntax, instead of this
if errorlevel 0 echo successful
you can use this - because errorlevel 0 is always true.
if not errorlevel 1 echo successful
Just use rm from UnxUtils (or gow or cygwin). It sets the errorlevel correctly in case of a nonexistent file, or any errors deleting the file.
This was added as an edit by the original asker, I have converted it to a community wiki answer because it should be an answer, not an edit.
I found out how to do it... one way anyway.
echo Startup > results.txt
#echo off
call :filelog >> results.txt 2>&1
notepad results.txt
exit /b
:filelog
call :delete new.txt
call :delete newer.txt
call :delete newest.txt
call :remove c:\NoSuchDirectory
GOTO :EOF
:delete
echo deleting %1
dir c:\users\newuser\Desktop\%1 >NUL 2>&1
SET existed=%ERRORLEVEL%
del /f /q c:\Users\newuser\Desktop\%1
dir c:\users\newuser\Desktop\%1 2>NUL >NUL
if %existed% == 0 (if %ERRORLEVEL% == 1 echo "successful" )
GOTO :EOF
:remove
echo deleting directory %1
rmdir /q /s %1
GOTO :EOF
IF ERRORLEVEL 0 [cmd] will execute every time because IF ERRORLEVEL # checks to see if the value of ERRORLEVEL is greater than or equal to #. Therefore, every error code will cause execution of [cmd].
A great reference for this is: http://www.robvanderwoude.com/errorlevel.php
>IF /?
Performs conditional processing in batch programs.
IF [NOT] ERRORLEVEL number command
IF [NOT] string1==string2 command
IF [NOT] EXIST filename command
NOT Specifies that Windows should carry out
the command only if the condition is false.
ERRORLEVEL number Specifies a true condition if the last program run
returned an exit code equal to or greater than the number
specified.
I would recommend modifying your code to something like the following:
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 1 (
rem This block executes if ERRORLEVEL is a non-zero
echo failed
) else (
echo succesful
)
GOTO :EOF
If you need something that processes more than one ERRORLEVEL, you could do something like this:
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
if errorlevel 3 echo Cannot find path& GOTO :delete_errorcheck_done
if errorlevel 2 echo Cannot find file& GOTO :delete_errorcheck_done
if errorlevel 1 echo Unknown error& GOTO :delete_errorcheck_done
echo succesful
:delete_errorcheck_done
GOTO :EOF
OR
:delete
echo deleting %1
del /f /q c:\Users\newuser\Desktop\%1
goto :delete_error%ERRORLEVEL% || goto :delete_errorOTHER
:delete_errorOTHER
echo Unknown error: %ERRORLEVEL%
GOTO :delete_errorcheck_done
:delete_error3
echo Cannot find path
GOTO :delete_errorcheck_done
:delete_error2
echo Cannot find file
GOTO :delete_errorcheck_done
:delete_error0
echo succesful
:delete_errorcheck_done
GOTO :EOF
The answer of aschipfl is great (thanks, helped me a lot!) using the code under Presetting ErrorLevel you get a nice standard function:
Take care to use %~1 instead of %1 in the del statement, or you will get errors if you use a quoted filename.
::######################################################################
::call :DELETE "file.txt"
::call :DELETE "file.txt" "error message"
:DELETE
>nul ver && for /F "tokens=*" %%# in ('del /F /Q "%~1" 2^>^&1 1^> nul') do (2>nul set =) || (
if NOT .%2==. echo %~2
)
goto :EOF
BTW 1: You can give a nifty error message as a second parameter
BTW 2: Using :: instead of REM for comments makes the code even more readable.
Code:
Error Code: (What you did)
if errorlevel 0 echo succesful
The problem here is that you aren't calling errorlevel as a variable and plus you didn't add in the operator to the statement as well.
Correct Code: (Here is what it should actually be.)
if %ERRORLEVEL% EQU 0 echo succesful
Definitions:
EQU: The EQU stands for Equal. This kind of operator is also called a relational operator. Here is the documentation link to operators if you wanna know more, there are other ones but this helped me.
ERRORLEVEL: is declared as a variable and usually get the error level of the last command run usually. Variables are usually called when they are between percent signs like this
%foo%
For some more help on variables, go to cmd (Which you can go to by searching it on windows 10) and type in "set /?", without the quotes. the set command is the command you use to set variables
I want that my batch script only shows the filename without any path or extension in a specific directory of *.exe files. My code so far is this:
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~ne"
echo "%EXENAME%"
)
But this code does not work as expected. Let's assume, I have two files in that directory: tomcat7.exe and tomcat7w.exe. But when processing the script, I get as an answer this:
"tomcat7w"
"tomcat7w"
Why is that?
You ran into the delayed expansion trap as so many batch file coding newbies as Noodles hinted.
You could see the expected result by using echo %%~ne instead of echo "%EXENAME%".
By opening a command prompt window, running in this window set /? and reading the output help you get delayed environment variable expansion explained on an IF and a FOR example.
The batch file producing the expected output:
#echo off
setlocal EnableDelayedExpansion
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~ne"
echo !EXENAME!
)
endlocal
If you want to see with extensions try like this way :
#echo off
setlocal EnableDelayedExpansion
for /R "%cd%" %%e in (*.exe) do (
set "EXENAME=%%~nxe"
echo !EXENAME!
)
endlocal
pause
#echo off
set instance=%username:~2%
setlocal enabledelayedexpansion
for /f "delims=" %%i in (servers.txt) do (
set server=%%i
echo server is !server!
pushd \\%%i\D$\%instance%\Hyperion\EPMSystem11R1\OPatch
echo current directory is %CD%
)
Where servers.txt contains servers names.
Here if i include onlu pushd command inside for loop then it perfectly changing to remote directory but if multiple commands it doesn't.
pushd \\%%i\D$\%instance%\Hyperion\EPMSystem11R1\OPatch
echo current directory is !CD!
POPD
If you continue pushing then eventually you'll run out of stack.
You need to use !CD! in place of %CD% because %cd% will be evaluated and replaced at parse time, showing misleading results. !cd! will show the run-time value.
You could also CALL echo current directory is %%CD%%
It can always occur that one of the servers to connect is not available currently in network. Command PUSHD exits with value 1 on error instead of 0 on success. This can be used to execute all other commands only if drive mapping to network share was really successful.
The code below demonstrates that:
#echo off
if not exist servers.txt goto :EOF
setlocal EnableDelayedExpansion
set "instance=%username:~2%"
for /f "delims=" %%i in (servers.txt) do (
echo Server is %%~i
pushd "\\%%~i\D$\%instance%\Hyperion\EPMSystem11R1\OPatch" 2>nul
if not errorlevel 1 (
echo Current directory is !CD!
rem Other commands.
popd
) else (
echo Not found: \\%%~i\D$\%instance%\Hyperion\EPMSystem11R1\OPatch
)
)
endlocal
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
endlocal /?
for /?
goto /?
if /?
popd /?
pushd /?
rem /?
set /?
setlocal /?