So I am having trouble with this line of code, which is meant to check if user input matches the given options.
set /p "myVar=---> "
echo %myVar%|findstr /ix "red:Red:blue:Blue">nul && (
echo %myVar% matched
) || (
echo %myVar% not matched
)
Is there a way I can go it like the above or any other way?
You could make use of the fact that for /F returns an exit code of 1 in case of zero iterations:
set /P VAR="Enter something: " || ((echo Empty input!) & exit /B)
(for /F "delims=AaEeIiOoUuYy eol=y" %%K in ("%VAR%") do rem/) && (
echo No match found!
) || (
echo Match encountered.
)
If the input consists only of characters listed after delims=, the for /F loop does not iterate and returns an exit code of 1; the command behind && only executes in case the exit code is 0, the command behind || only executes in case the exit code is not 0.
If no input is provided, set /P sets the exit code to 1.
Here is a basic example of what you want to do. I have not tested in DOS since I do not have any VM (or very old computer) right here, but IIRC this should work in plain DOS as well.
#echo off
if %var% == A GOTO :anything
if %var% == E GOTO :anything
if %var% == I GOTO :anything
if %var% == O GOTO :anything
if %var% == U GOTO :anything
if %var% == Y GOTO :anything
goto end
:anything
echo anything
:end
I think, choice is your best friend here. But if you want to do it without choice (it isn't available in all Windows versions):
set /p "myVar=---> "
echo %myVar%|findstr /ix "a e i o u y">nul && (
echo %myVar% matched
) || (
echo %myVar% not matched
)
It asks for input and checks if it is exactly one of the characters from the list, ignoring capitalization.
My question is related to this question. I have several bunch of actions that need to be executed from a batch file and I would like to model them as functions and call from a master sequence. From the above question, it is clear that I can do this with the call syntax
call:myDosFunc
My question is that can I place all these functions in a seperate batch file (functions.bat) and somehow 'include' that in the main batch file and call them? Another option would be to utilize the possibility to invoke functions.bat from main.bat with the call syntaxt, but I'm not sure if I can invoke that with a specific function instead of executing the whole batch file.
In short, I'm looking for something similar to the C programming world where my functions reside in a DLL and the main program contains only the high-level logic and calls the functions from the DLL.
I think a routing function in the beginning of a batch file is not that ugly.
You can use something like this at the beginning of a "libbatch.cmd"
call:%*
exit/b
:func1
[do something]
exit/b
:func2
[do something else]
exit/b
Now you can call func2 from another batch with:
call libbatch.cmd func2 params1 param2 ... paramN
this also preserves the errorlevel "thrown" by func2 (exit/b hands over the current errorlevel).
With the second call instead of a goto you ensure that "%1"=="param1" and not func2.
And call will not terminate the batch file if the label does not exist, it simply sets the errorlevel to 1 and puts an error message to 2 (errorout), which could be redirected to nul.
Explanation: %* contains all parameters, so in the example the first line translates to:
call:func2 params1 param2 ... paramN
Here is a simple example of how it might be done.
The function script is called with the name of the function as the first argument, and function arguments as arg2, arg3, ...
Assuming it is called properly, the script shifts the arguments and performs GOTO to the original arg1. Then the function has its arguments starting with the new arg1. This means you can take already written routines and plop them in the utility without having to worry about adjusting the parameter numbers.
The script gives an error if the function argument is not supplied, or if the function argument does not match a valid label within the script.
#echo off
if "%~1" neq "" (
2>nul >nul findstr /rc:"^ *:%~1\>" "%~f0" && (
shift /1
goto %1
) || (
>&2 echo ERROR: routine %~1 not found
)
) else >&2 echo ERROR: missing routine
exit /b
:test1
echo executing :test1
echo arg1 = %1
exit /b
:test2
echo executing :test2
echo arg1 = %1
echo arg2 = %2
exit /b
:test3
echo executing :test3
echo arg1 = %1
echo arg2 = %2
echo arg3 = %3
exit /b
I prefer the GOTO approach that I used above. Another option is to use CALL instead, as Thomas did in his answer.
For a working example of a usefull library of batch functions that uses the CALL technique, see CHARLIB.BAT, a library of routines for processing characters and strings within a batch file. A thread showing development of the library is available here
I wrote CharLib.bat a few years ago. Were I to write it today, I would probably use GOTO instead of CALL.
The problem with introducing a CALL is that it creates issues when passing string literals as parameters. The extra CALL means that a string literal containing % must have the percents doubled an extra time. It also means unquoted poison characters like & and | would need to be escaped an extra time. Those two issues can be addressed by the caller. But the real problem is that each CALL doubles up quoted carets: "^" becomes "^^". There isn't a good way to work around the caret doubling problem.
The problems with the extra CALL don't impact CharLib.bat because string values are passed by reference (variable name) and not as string literals.
The only down side to using GOTO with SHIFT /1 is that you cannot use %0 to get the name of the currently executing routine. I could have used SHIFT without the /1, but then you wouldn't be able to use %~f0 within a routine to get the full path to the executing batch file.
You can use this format - and launch it like this:
call mybat :function4 parameternumber2 parameternumber3
this would be one way of using a library
#echo off
goto %1
:function1
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function2
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function3
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function4
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
You may use an interesting trick that avoids most of the problems that other methods have when they try to make the library functions available to the main program and it is much faster. The only requisites to use this trick are:
The library functions must be called from inside a code block in the main file, and
In that code block no main file functions are called.
The trick consist in "switch the context" of the running Batch file in a way that the library file becomes the running Batch file; this way, all the functions in the library file becomes available to the main code block with no additional processing. Of course, the "context" of the running Batch file must be switched back to the main file before the code block ends.
The way to "switch the context" is renaming the library file with the same name of the running main file (and renaming the main file to another name). For example:
(
rem Switch the context to the library file
ren main.bat orig-main.bat
ren library.bat main.bat
rem From this point on, any library function can be called
. . . .
rem Switch back the context to the original one
ren main.bat library.bat
ren orig-main.bat main.bat
)
EDIT: Working example added
I copied the example below from the screen. Tested in Windows 8, but I also used this method in Win XP:
C:\Users\Antonio\Documents\test
>type main.bat
#echo off
(
rem Switch the context to the library file
ren main.bat orig-main.bat
ren library.bat main.bat
rem From this point on, any library function can be called, for example:
echo I am Main, calling libFunc:
call :libFunc param1
echo Back in Main
rem Switch back the context to the original one
ren main.bat library.bat
ren orig-main.bat main.bat
)
C:\Users\Antonio\Documents\test
>type library.bat
:libFunc
echo I am libFunc function in library.bat file
echo My parameter: %1
exit /B
C:\Users\Antonio\Documents\test
>main
I am Main, calling libFunc:
I am libFunc function in library.bat file
My parameter: param1
Back in Main
I'm not sure of the context of the original question, but this might be a case where switching to something like WSH with VBScript or WPS, or any other console-capable scripting other than batch files. I will answer the original question, but first.. a little background and understanding..
The command line/console mode of DOS and Windows is usually either COMMAND.COM or CMD.EXE, which isn't well-geared towards scripting/programming logic. Rather, they're geared towards executing commands and programs, and batch files were added to commonly used sequences of commands to wrapped up in a single typed command. For example, you may have an old DOS game you play that needs the following commands every time, so it's packaged as a batch file:
#EHO OFF
#REM Load the VESA driver fix..
VESAFIX.EXE
#REM Load the joystick driver..
JOYSTICK.COM
#REM Now run the game
RUNGAME.EXE
Many people tend to view the entire batch file as one atomic unit--But it's not. The command interpreter (COMMAND.COM or CMD.EXE) will merely act like you manually typed those lines, one by one, each time you run the batch file. It really has no solid concept of lexica and scoping like a regular programming/scripting language would--that is, it doesn't maintain much extra meta-data like a call stack, et cetera. What little it does maintain is more added like it's an afterthought rather than built-in to batch file from the beginning.
Once you shift the gears in your thinking, however, you can often overcome this limitation using various tricks and techniques to emulate more powerful scripting/programming languages; But you still have to remember that batch files are still going to be limited, regardless.
Anyhow, one technique of using a batch file library is to create a batch file where the first parameter is used to indicate which function is being called:
CALL BATLIB.BAT FunctionName Parameter1 Parameter2 ...
This works well enough when the library is written with this in mind, so it'll know to skip the first argument, et cetera.
Using more modern version of CMD.EXE in Windows' systems allows the use of ":labels" in the CALL syntax, which can be useful if you want to limit the parameter scope (which allows you to use %* for "all arguments", for example), like this:
CALL :LABEL Parameter1 Paramater2 ...
(from within the same batch file or ...)
CALL BATLIB.BAT :LABEL Parameter1 Parameter2 ...
A few notes about that, though.. In the first form, the :LABEL must be already within the current batch file. It will create a new "batch context" within CMD.EXE where %*, %1, %2, et cetera are matched to the parameters. But you'll also have to provide some kind of return/exit logic to return/exit from that context back to the calling context.
In the second form, CMD.EXE does not really recognize that you are passing it a label, so your batch file library will have to expect it and handle it:
#ECHO OFF
CALL %*
This works because the command interpreter replaces the %* before it even attempts to parse the CALL command, so after variable expansion, the CALL command would see the :LABEL as if it were hard-coded. This also creates a situation where CMD.EXE creates yet another batch context, so you'll have to make sure to return/exit from that context twice: Once for the current library context, again to get back to the original CALL.
There are still other ways to do a batch file library, mixing and matching the above techniques, or using even more complex logic, using GOTO's, et cetera. This is actually such a complex topic that there are entire sections of books written on the topic, much more than I want to type in a simple answer here!
And so far, I've mostly ignored other issues you will encounter: What if the CALL label does not exist? How will it be handled? What about environment variable expansion? when does it happen? How can you prevent it from happening too soon? What about using special DOS characters in the arguments/parameters? For example, how does the interpreter see a line like: CALL :ProcessPath %PATH%? (The answer to that is that CMD.EXE replaces the entire %PATH% before it even processes the CALL command. This can create issues if your path has spaces in it which can trip up how CALL processes the entire thing, as many Windows' %PATH% variables do.. C:\Program Files.. for example..)
As you can see, things are getting complicated and messy very quickly.. And you have to stop thinking like a programmer and start thinking like COMMAND.COM/CMD.EXE, which pretty much only sees one single line at a time, not the whole batch file as an atomic unit. In fact, here's an example to help you really grasp the way it works..
Create a folder, C:\testing, and put the following batch, file called "oops.bat", in it:
#ECHO OFF
ECHO Look mom, no brain!
PAUSE
ECHO Bye mom!
Now open a console window and run it, but let it sit there at the PAUSE:
C:\testing>oops.bat
Look mom, no brain!
Press any key to continue . . .
While it's sitting at the PAUSE, open oops.bat in your text editor and change it to:
#ECHO OFF
ECHO Look mom, no brain!?
ECHO Oops!
PAUSE
ECHO Bye mom!
Save it, then switch back to your console window and press any key to continue running the batch file:
'ops!' is not recognized as an internal or external command,
operable program or batch file.
Press any key to continue . . .
Bye mom!
c:\testing>
Whoa.. see that error there? That happened because we edited the batch file while it was still being run by CMD.EXE, but our edits changed where in the batch file CMD.COM thought it was. Internally, CMD.EXE maintains a file pointer indicating the start of the next character to process, which in this case would have been the byte right after the line with the PAUSE (and the CRLF) on it. But when we edited it, it changed the location of the next command in the batch file, but CMD.EXE's pointer was still in the same place. In this case, it was pointing to the byte position right in the middle of the "ECHO Oops!" line, so it tried to process "ops!" as a command after the PAUSE.
I hope this makes it clear that COMMAND.COM/CMD.EXE will always see your batch files as a stream of bytes, not as a logical block, subroutines, et cetera, like a script language or compiler would. This is why batch file libraries are so limited. It makes it impossible to "import" a library in to a currently running batch file.
Oh, and I just had another thought.. In modern Windows' CMD.EXE, you can always create a batch file which creates a temporary batch file on the fly, then calls it:
#ECHO OFF
SET TEMPBAT=%TEMP%\TMP%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%.BAT
ECHO #ECHO OFF > %TEMPBAT%
ECHO ECHO Hi Mom! I'm %TEMPBAT%! >> %TEMPBAT%
ECHO Hello, world, I'm %~dpnx0!
CALL %TEMPBAT%
DEL %TEMPBAT%
This effectively creates a temporary batch file in your temporary directory, named TMP####.BAT (where the #'s are replaced by random numbers; The %RANDOM:~0,1% means take the first digit of the number returned by %RANDOM%--we only wanted one single digit here, not the full number that RANDOM returns..), then ECHO's "Hello, World," followed by it's own full name (the %~dpnx0 part), CALLs the temporary batch file, which in turn ECHO's "Hi Mom!" followed by it's own [random] name, then returns to the original batch file so it can do whatever cleanup is needs, such as deleting the temporary batch file in this case.
Anyhow, as you can see by the length of this post, this topic really is not a simple one. There's dozens or more web pages out on the web with tons of batch file tips, tricks, et cetera, many of which go in to depth about how to work with them, create batch file libraries, what to watch out for, how to pass arguments by reference vs. by value, how to manage when and where variables get expanded, and so on.
Do a quick Google search for "BATCH FILE PROGRAMMING" to find many of them, and you can also check out Wiki and WikiBooks, SS64.com, robvanderwoude.com and even DMOZ's directory with more resources.
Good luck!
Here's a cmd batch script, which imports files or files in folders (recursivelly) into the main script:
#echo off
REM IMPORT - a .cmd utility for importing subroutines into the main script
REM !!! IN ORDER TO FUNCTION CORRECTLY: !!!
REM !!! IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT) !!!
rem \\// Define import file mask here:
rem If mask is not defined outside "import.cmd":
if not defined mask (
set "mask=*.cmd; *.bat"
)
rem //\\ Define import file mask here:
rem Detect if script was started from command line:
call :DetectCommandLine _not_started_from_command_line
if "%~1" == "/install" (
set "import_path=%~dp0"
call :EscapePathString import_path import_path_escaped
)
if not "%~1" == "" (
if /i not "%~1" == "end" (
if "%~1" == "/?" (
call :DisplayHelp
) else (
if "%~1" == "/install" (
echo Installing
set "_first_time="
rem This should get into the Autorun registry key: path %path%;"...\import.cmd"
rem If you want, other commands can be added to the left or to the right of this command, unified as a block with a "&" command operator
REG ADD "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "path %%path%%;"""%import_path_escaped%""||(
echo ERROR: Cannot install import: cannot write to the registry^!
echo You can try to manually add the "import.cmd" path in the "PATH" variable or use pushd ^(see help^).
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
echo.
echo Done. The install directory was set to:
echo "%import_path%"
echo and was added to the PATH environment variable. You can add other desired programs into this directory.
echo.
echo Please note that the console needs to be closed and reopened in order for the changes to take effect^!
echo.
) else (
if not defined _first_time (
set _first_time=defined
set /a count=0
if "%_DISPLAY_WARNING%" == "true" (
echo.
echo WARNING: CMD_LIBRARY was reset to "", because previously it gave an error^!
echo.
)
echo Loading list to import...
)
REM build import files list
set /a count+=1
call set "_import_list_%%count%%=%%~1"
)
)
)
) else (
call :DisplayHelp
)
if /i "%~1" == "end" (
set "_first_time="
echo Done.
echo Analyzing...
rem set "_main_program=%~dpnx2"
if not exist "%~dpnx2" (
echo ERROR: Second parameter, after "import end", must be a valid file path - see help^!>>&2
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
)
if /i "%~1" == "end" (
set "_main_batch_script=%~dpnx2"
rem \\// Define output filename here:
rem set "_output_filename=tmp0001_%~n2.cmd"
set "_output_filename=tmp0001.cmd"
rem //\\ Define output filename here:
)
if /i "%~1" == "end" (
rem Check all paths not to be UNC:
setlocal EnableDelayedExpansion
set "_error=false"
call :TestIfPathIsUNC _main_batch_script _result
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: Second parameter, after "import end", must not be a UNC path^^^! Currently it is: "!_main_batch_script!">>&2
)
set "_CMD_LIBRARY_error=false"
call :TestIfPathIsUNC CMD_LIBRARY _result
if "!_result!" == "true" (
set "_error=true"
set "_CMD_LIBRARY_error=true"
echo.
echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY!".>>&2
)
for /l %%i in (1,1,!count!) do (
call :TestIfPathIsUNC _import_list_%%i _result
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: The import path: "!_import_list_%%i!" is a UNC path^^^!>>&2
)
)
if "!_error!" == "true" (
echo.
echo Errors were ecountered^^^!
if "!_CMD_LIBRARY_error!" == "true" (
endlocal
set "_CMD_LIBRARY_error=true"
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
)
if /i "%~1" == "end" (
rem Check all paths not to contain "*" and "?" wildcards:
set "_asterisk=*"
set "_qm=?"
setlocal EnableDelayedExpansion
set "_error=false"
call :TestIfStringContains _main_batch_script _asterisk _result1
call :TestIfStringContains _main_batch_script _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: Second parameter, after "import end", must not contain "*" or "?" wildcards^^^! Currently it is: "!_main_batch_script!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
set "_CMD_LIBRARY_error=false"
call :TestIfStringContains CMD_LIBRARY _asterisk _result1
call :TestIfStringContains CMD_LIBRARY _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
set "_error=true"
set "_CMD_LIBRARY_error=true"
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: CMD_LIBRARY variable must not contain "*" or "?" wildcards^^^! Currently, it is set to: "!CMD_LIBRARY!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
for /l %%i in (1,1,!count!) do (
call :TestIfStringContains _import_list_%%i _asterisk _result1
call :TestIfStringContains _import_list_%%i _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
set "_error=true"
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: The import path: "!_import_list_%%i!" must not contain "*" or "?" wildcards^^^! Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
)
if "!_error!" == "true" (
echo.
echo Errors were ecountered^^^!
if "!_CMD_LIBRARY_error!" == "true" (
endlocal
set "_CMD_LIBRARY_error=true"
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
)
if /i "%~1" == "end" (
pushd "%~dp2"
call set "_output_dir=%%CD%%"
popd
)
if /i "%~1" == "end" (
if not defined CMD_LIBRARY (
set CMD_LIBRARY_CASE=IMPORT.CMD
set "BASE=%~dpnx0\.."
pushd "%~dpnx0"\..\
REM \\// Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):
REM if CMD_LIBRARY is not defined outside import.cmd, "." (used here) is related to import.cmd parent directory:
set "CMD_LIBRARY=."
REM //\\ Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):
) else (
set CMD_LIBRARY_CASE=MAIN.CMD
set "BASE=%~dpnx2\.."
REM if CMD_LIBRARY is defined outside the "import.cmd" script, "." (used in CMD_LIBRARY) is related to "main program" parent directory
pushd "%~dpnx2"\..
)
)
if /i "%~1" == "end" (
call :DeQuoteOnce CMD_LIBRARY CMD_LIBRARY
call set "CMD_LIBRARY_ORIGINAL=%%CMD_LIBRARY%%"
call :TestIfPathIsUNC CMD_LIBRARY_ORIGINAL _result
setlocal EnableDelayedExpansion
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY_ORIGINAL!".>>&2
echo.
echo Errors were ecountered^^^!
endlocal
set "_CMD_LIBRARY_error=true"
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
call pushd "%%CMD_LIBRARY%%" >nul 2>nul&&(
call set "CMD_LIBRARY=%%CD%%"
)||(
call echo ERROR: Could not access directory CMD_LIBRARY=^"%%CMD_LIBRARY%%^"^!>>&2
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
popd
exit /b 1
)
)
if /i "%~1" == "end" (
setlocal EnableDelayedExpansion
set _error=false
pushd "!BASE!"
echo.
if "!CMD_LIBRARY_CASE!" == "IMPORT.CMD" (
echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" in the file "import.cmd" and was expanded to: "!CMD_LIBRARY!"
) else (
if "!CMD_LIBRARY_CASE!" == "MAIN.CMD" (
echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" outside "import.cmd" file "%~nx2" and was expanded to: "!CMD_LIBRARY!"
)
)
for /l %%i in (1,1,!count!) do (
if not exist "!_import_list_%%i!" (
if not exist "!CMD_LIBRARY!\!_import_list_%%i!" (
rem if first time:
if not "!_error!" == "true" (
echo.
echo Directory of "!CMD_LIBRARY!":
)
echo.
echo ERROR: element "!_import_list_%%i!" does not exist or is not accessible as a standalone file/dir or as a file/dir in the directory contained by "CMD_LIBRARY" variable^^^!>>&2
set _error=true
)
)
)
popd
if "!_error!" == "true" (
endlocal
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
)
echo OK
echo.
)
set "_error=false"
if /i "%~1" == "end" (
echo Output file is: "%_output_dir%\%_output_filename%"
echo.
echo Importing...
echo.
(
type nul>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not write to file: "%_output_dir%\%_output_filename%"^!>>&2
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
echo Importing main script "%_main_batch_script%"
(
echo #set _import=defined
echo #REM Timestamp %date% %time%
echo.
)>>"%_output_dir%\%_output_filename%"
(
(
type "%_main_batch_script%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(echo ERROR: Could not read file^!&set "_error=true">>&2)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
echo.
echo Directory of "%CMD_LIBRARY%":
if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
pushd "%BASE%"
)
if not defined mask (
rem If mask is not defined, import all file types:
set "mask=*"
)
for /l %%i in (1,1,%count%) do (
call set "_import_list_i=%%_import_list_%%i%%"
call :ProcedureImportCurrentFile
)
if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
popd
)
)
if "%~1" == "end" (
if "%_error%" == "true" (
echo.
echo Errors were ecountered^!
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
echo Done^!
)
call popd
popd
rem Clean up
call :CleanUp
rem Detect if script was started from command line:
call :DetectCommandLine _not_started_from_command_line
)
if "%~1" == "end" (
if "%_not_started_from_command_line%" == "0" (
set "_import="
) else (
echo.
echo Starting program...
echo.
rem Start "resulting" program:
"%_output_dir%\%_output_filename%"
)
)
goto :eof
REM \\\/// Next subroutines use jeb's syntax for working with delayed expansion: \\\///
:CleanUp
(
setlocal EnableDelayedExpansion
)
(
endlocal
if "%_CMD_LIBRARY_error%" == "true" (
set "CMD_LIBRARY="
set "_DISPLAY_WARNING=true"
) else (
set "_DISPLAY_WARNING=false"
)
set "_first_time="
for /l %%i in (1,1,%count%) do (
set "_import_list_%%i="
)
rem optional:
set "count="
set "import_path="
rem set "_output_dir="
set "_error="
set "_main_batch_script="
rem set "_output_filename="
rem set "_import="
set "mask="
exit /b
)
:GetStrLen - by jeb - adaptation
(
setlocal EnableDelayedExpansion
set "s=!%~1!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~2=%len%"
exit /b
)
:EscapePathString
(
setlocal EnableDelayedExpansion
set "string=!%~1!"
call :GetStrLen string string_len
set /a string_len-=1
for /l %%i in (0,1,!string_len!) do (
rem escape "^", "(", ")", "!", "&"
if "!string:~%%i,1!" == "^" (
set "result=!result!^^^^"
) else (
if "!string:~%%i,1!" == "(" (
set "result=!result!^^^("
) else (
if "!string:~%%i,1!" == ")" (
set "result=!result!^^^)"
) else (
if "!string:~%%i,1!" == "^!" (
set "result=!result!^^^!"
) else (
if "!string:~%%i,1!" == "&" (
set "result=!result!^^^&"
) else (
if "!string:~%%i,1!" == "%%" (
set "result=!result!%%"
) else (
set "result=!result!!string:~%%i,1!"
)
)
)
)
)
)
)
)
(
endlocal
set "%~2=%result%"
exit /b
)
:PressAnyKey
set /p=%*<nul
pause>nul
goto :eof
:DeQuoteOnce
(
setlocal EnableDelayedExpansion
set "string=!%~1!"
if "!string!" == """" (
endlocal
set "%~2=%string%"
exit /b
)
rem In order to work with " we replace it with a special character like < > | that is not allowed in file paths:
set "string=!string:"=^<!"
if "!string:~0,1!" == "<" (
if "!string:~-1,1!" == "<" (
set "string=!string:~1,-1!"
)
)
rem restore " in string (replace < with "):
set "string=!string:<="!"
)
(
endlocal
set "%~2=%string%"
exit /b
)
:TestIfPathIsUNC
(
setlocal EnableDelayedExpansion
set "_current_path=!%~1!"
set "_is_unc_path=true"
if defined _current_path (
if "!_current_path:\\=!" == "!_current_path!" (
set "_is_unc_path=false"
)
) else (
set "_is_unc_path=false"
)
)
(
endlocal
set "%~2=%_is_unc_path%"
exit /b
)
:TestIfStringContains
(
setlocal EnableDelayedExpansion
echo "!%~1!"|find "!%~2!">nul 2>nul
set "_error_code=!ERRORLEVEL!"
)
(
endlocal
if "%_error_code%" == "0" (
set "%~3=true"
) else (
set "%~3=false"
)
exit /b
)
REM ///\\\ The subroutines above use jeb's syntax for working with delayed expansion: ///\\\
:DetectCommandLine
setlocal
rem Windows: XP, 7
for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd /c """ /c') do (
set "_not_started_from_command_line=%%~c"
)
if "%_not_started_from_command_line%" == "0" (
rem Windows: 10
for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd.exe /c """ /c') do (
set "_not_started_from_command_line=%%~c"
)
)
endlocal & (
set "%~1=%_not_started_from_command_line%"
)
goto :eof
:ProcedureImportCurrentFile
setlocal
set "cc="
if not exist "%_import_list_i%" (
set "_not_a_dir=false"
pushd "%CMD_LIBRARY%\%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
call :GetStrLen CD _CD_len
)
if "%_not_a_dir%" == "false" (
setlocal EnableDelayedExpansion
if not "!CD:~-1,1!" == "\" (
endlocal
set /a _CD_len+=1
) else (
endlocal
)
popd
)
if not exist "%_import_list_i%" (
if "%_not_a_dir%" == "true" (
echo Importing file "%CMD_LIBRARY%\%_import_list_i%"
(
type "%CMD_LIBRARY%\%_import_list_i%">>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
if not "%%i" == "%count%" (
echo.
echo.
) else (
echo.
)
)>>"%_output_dir%\%_output_filename%"
) else (
echo Importing dir "%_import_list_i%"
rem
pushd "%CMD_LIBRARY%\%_import_list_i%\"
set /a cc=0
for /r %%f in (%mask%); do (
set "_current_file=%%~dpnxf"
call set "r=%%_current_file:~%_CD_len%%%"
call echo Importing subfile "%%_import_list_i%%\%%r%%"
(
(
call type "%%_current_file%%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
set /a cc+=1
)
popd
)
) else (
set "_not_a_dir=false"
pushd "%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
call :GetStrLen CD _CD_len
)
if "%_not_a_dir%" == "false" (
setlocal EnableDelayedExpansion
if not "!CD:~-1,1!" == "\" (
endlocal
set /a _CD_len+=1
) else (
endlocal
)
popd
)
if exist "%_import_list_i%" (
if "%_not_a_dir%" == "true" (
echo Importing file "%_import_list_i%"
(
type "%_import_list_i%">>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
if not "%%i" == "%count%" (
echo.
echo.
) else (
echo.
)
)>>"%_output_dir%\%_output_filename%"
) else (
rem
echo Importing dir "%_import_list_i%"
pushd "%_import_list_i%\"
set /a cc=0
for /r %%f in (%mask%); do (
set "_current_file=%%~dpnxf"
call set "r=%%_current_file:~%_CD_len%%%"
call echo Importing subfile "%%_import_list_i%%\%%r%%"
(
(
call type "%%_current_file%%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
set /a cc+=1
)
popd
)
)
if "%cc%" == "0" (
echo No match^!
)
endlocal & (
set "_error=%_error%"
)
goto :eof
:DisplayHelp
echo IMPORT - a .cmd utility for importing subroutines into the main script
echo.
echo NOTES: 1. This utility assumes that command extensions are enabled (default) and that delayed expansion can be enabled;
echo ALSO IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT);
echo These are necessary in order for it to function correctly.
echo 2. The use of UNC paths is not supported by import. As a workarround, you can mount a UNC path to a temporary drive using "pushd".
echo The use of "*" or "?" wildcards is not supported by import. Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat"
echo When the "mask" variable is set, only the filenames having the extensions contained by it are matched at import.
echo.
echo Description:
echo import organizes your batch programs on common libraries of subroutines, that you can use in the future for other programs that you build; it also makes code editing and debugging easier.
echo.
echo Usage [1]:
echo import [flags]
echo.
echo [flags] can be:
echo /install - installs import into the registry, in the Command Processor AutoRun registry key ^(adds the current location of import into the PATH variable^).
echo /? - displays help ^(how to use import^)
echo.
echo Usage [2]:
echo What it does:
echo Concatenates ^(appends^) files content containing subroutines to the main program content using the following SYNTAX:
echo REM \\//Place this in the upper part of your script ^(main program)^ \\//:
echo.
echo #echo off
echo.
echo if not defined _import ^(
echo rem OPTIONAL ^(before the "import" calls^):
echo set "CMD_LIBRARY=^<library_directory_path^>"
echo.
echo import "[FILE_PATH1]filename1" / "DIR_PATH1"
echo ...
echo import "[FILE_PATHn]filenamen" / "DIR_PATHn"
echo import end "%%~0"
echo ^)
echo.
echo REM //\\Place this in the upper part of your script ^(main program)^ //\\:
echo.
echo "filename1" .. "filenamen" represent the filenames that contain the subroutines that the user wants to import in the current ^(main^) program. The paths of these files are relative to the directory contained in the CMD_LIBRARY variable.
echo.
echo "FILE_PATH1" .. "FILE_PATHn" represent the paths of these files.
echo.
echo "DIR_PATH1" .. "DIR_PATHn" represent directories paths in which to recursivelly search and import all the files of the type defined in the variable "mask"
echo.
echo CMD_LIBRARY is a variable that contains the directory path where your library of files ^(containing subroutines^) is found.
echo.
echo We denote the script that calls "import" as "the main script".
echo.
echo By default, if not modified in outside the import.cmd script, in the import.cmd script - CMD_LIBRARY is set to "." directory and is relative to the "import.cmd" parent directory.
echo If CMD_LIBRARY directory is modified outside the import.cmd script, CMD_LIBRARY is relative to the main script parent directory.
echo.
echo Note that only the last value of "CMD_LIBRARY" encountered before `import end "%%~0"` is taken into consideration.
echo.
echo import end "%%~0" - marks the ending of importing files and the start of building of the new batch file ^(named by default tmp0001.cmd, and located in the directory in which the main script resides^).
echo.
echo "%%~0" represents the full path of the main script.
goto :eof
To use it:
save it as import.cmd
call it with the /install flag in order to install it (does not require admin)
add a header like this at the begining of your main script that calls subroutines that are stored in other files - files that are going to be imported:
if not defined _import (
rem OPTIONAL (before the "import" calls):
set "CMD_LIBRARY=<library_directory_path>"
import "[FILE_PATH1]filename1" / "DIR_PATH1"
...
import "[FILE_PATHn]filenamen" / "DIR_PATHn"
import end "%~0"
)
To find out how to use it, simply call it with the /? flag.
I'm experimenting with a Windows batch file to perform a simple operation which requires the user to enter a non-negative integer. I'm using simple batch-file techniques to get user input:
#ECHO OFF
SET /P UserInput=Please Enter a Number:
The user can enter any text they want here, so I would like to add some routine to make sure what the user entered was a valid number. That is... they entered at least one character, and every character is a number from 0 to 9. I'd like something I can feed the UserInput into. At the end of the routine would be like an if/then that would run different statements based on whether or not it was actually a valid number.
I've experimented with loops and substrings and such, but my knowledge and understanding is still slim... so any help would be appreciated.
I could build an executable, and I know there are nicer ways to do things than batch files, but at least for this task I'm trying to keep it simple by using a batch file.
You're probably not doing this in a DOS batch file. Or at least, support for set /p is unheard of for me in DOS :-)
You could use substrings. In fact I have written a parser for a specific regular language that way once, but it's cumbersome. The easiest way would probably be to assign the contents of %userinput% to another variable, using set /a. If the result comes out as 0 you need to check whether the input itself was 0, otherwise you can conclude it was a non-number:
#echo off
setlocal enableextensions enabledelayedexpansion
set /p UserInput=Enter a number:
set /a Test=UserInput
if !Test! EQU 0 (
if !UserInput! EQU 0 (
echo Number
) else (
echo Not a number
)
) else (
echo Number
)
However, this works only for numbers in the range of Int32. If you just care for any number (possibly floating-point as well) then you need to resort to the loop-based approach of dissecting it.
NOTE: Updated to solve the space issues. However, there is still a problem lurking: Entering 123/5 yields "number", since set /a can evaluate this ...
Thanks all. I was trying to make it harder for myself looking at loops and string manipulation. I used your tips on math evaluation and comparison. Here's what I finally came up with as my concept script:
:Top
#ECHO OFF
ECHO.
ECHO ---------------------------------------
SET /P UserInput=Please Enter a Number:
ECHO.
ECHO UserInput = %UserInput%
ECHO.
SET /A Evaluated=UserInput
ECHO Math-Evaluated UserInput = %Evaluated%
if %Evaluated% EQU %UserInput% (
ECHO Integer
IF %UserInput% GTR 0 ( ECHO Positive )
IF %UserInput% LSS 0 ( ECHO Negative )
IF %UserInput% EQU 0 ( ECHO Zero )
REM - Other Comparison operators for numbers
REM - LEQ - Less Than or Equal To
REM - GEQ - Greater Than or Equal To
REM - NEQ - Not Equal To
) ELSE (
REM - Non-numbers and decimal numbers get kicked out here
ECHO Non-Integer
)
GOTO Top
This method catches all numbers and can detect whether it's positive, negative, or zero. Any decimal or string will be detected as non-integers. The only edge case I've found is a string with spaces. For example, the text "Number 1" will cause the script to crash/close when the user input is evaluated as math. But in my situation, this is fine. I don't want my script to go on with invalid input.
You can also use a quite simple trick:
echo %userinput%|findstr /r /c:"^[0-9][0-9]*$" >nul
if errorlevel 1 (echo not a number) else (echo number)
This uses findstr's regular expression matching capabilities. They aren't very impressive but useful at times.
This is the same idea as that of Johannes..
SET /A sets a numeric value. If the input is not a number, it changes it to 0.
That's what you can exploit here to do your check.
#ECHO OFF
SET /P UserInput=Please Enter a Number:
IF %UserInput% EQU 0 GOTO E_INVALIDINPUT
SET /A UserInputVal="%UserInput%"*1
IF %UserInputVal% GTR 0 ECHO UserInput "%UserInputVal%" is a number
IF %UserInputVal% EQU 0 ECHO UserInput "%UserInputVal%" is not a number
GOTO EOF
:E_INVALIDINPUT
ECHO Invalid user input
:EOF
As an alternative, you could always create a little javascript file and call it from your batchfile. With parseInt() you could force the input to be an integer, or you could roll your own function to test the input.
Writing the javascript is just as fast as the batchfile, but it's much more powerful. No IDE or compiler required; notepad will do. Runs on every windows box, just like your batchfiles. So why not make use of it?
You can even mix batchfiles and javascript. Example:
contents of sleep.js:
var SleepSecs=WScript.Arguments.Item(0);
WScript.Sleep(SleepSecs*1000)
contents of sleep.cmd:
cscript /nologo sleep.js %1
You can now call this from a batchfile to make your script sleep for 10 seconds. Something like that is difficult to do with just a plain batchfile.
sleep 10
As pointed out by ghostdog74, the answers posted by Joey Mar 26 '09 (score 10) and Wouter van Nifterick Mar 26 '09 (score 5) don't work.
The answer posted by Joey Mar 25 '10 (score 2) does work, except that redirection symbols and '&' cause syntax errors.
I think the best and simplest solution is the one posted by Sager Oct 8 '14 (score 0). Unfortunately, it has a typo: ‘"%a"’ should be ‘"%a%"’.
Here's a batch file based on Sager's answer. Redirection symbols and '&' in the input don't cause problems. The only problems I could find were caused by strings containing double quotes.
#echo off & setlocal enableextensions & echo.
set /p input=Enter a string:
SET "x=" & for /f "delims=0123456789" %%i in ("%input%") do set x=%%i
if defined x (echo Non-numeral: "%x:~0,1%") else (echo No non-numerals)
In addition to the remark about the error that occures when spaces are part of the users input. You can use errorlevel errorlevel=9165. It can be used for the spaces in a string or for the error handling of 'no' input.
Kind Regards,
Egbert
You might also like this one - it's short and easy. This one use the multiplication trick to set TestVal. Comparing TestVal against UserInput allows all numeric values to get through including zeroes, only non-numerics will trigger the else statement. You could aslo set ErrorLevel or other variables to indicate a failed entry
#ECHO OFF
SET TestVal=0
SET /P UserInput=Please Enter a Number:
SET /A TestVal="%UserInput%"*1
If %TestVal%==%UserInput% (
ECHO You entered the number %TestVal%
) else ECHO UserInput "%UserInput%" is not a number
GOTO EOF
:EOF
I know this is years old, but just to share my solution.
set /p inp=Int Only :
:: Check for multiple zeros eg : 00000 ::
set ch2=%inp%-0
if %inp% EQU 0 goto :pass
if [%inp%]==[] echo Missing value && goto :eof
if %inp:~0,1%==- echo No negative integers! && goto :eof
set /a chk=%inp%-10>nul
if %chk%==-10 echo Integers only! && goto :eof
:pass
echo You shall pass
:eof
Tested and working on Windows 8.
you can reinvent the wheel and grow a few white hairs doing string validation in batch, or you can use vbscript
strInput = WScript.Arguments.Item(0)
If IsNumeric(strInput) Then
WScript.Echo "1"
Else
WScript.Echo "0"
End If
save it as checkdigit.vbs and in your batch
#echo off
for /F %%A in ('cscript //nologo checkdigit.vbs 100') do (
echo %%A
rem use if to check whether its 1 or 0 and carry on from here
)
You can validate any variable if its number:
SET "var="&for /f "delims=0123456789" %i in ("%a") do set var=%i
if defined var (echo."NIC">nul) else (echo."number")
If you want some sort of a loop and default set up for that particular question, then here's my method for doing this.
Notes on the code within.
#echo off
setlocal EnableDelayedExpansion
set "ans1_Def=2"
:Q1
set /p "ans1=Opt 1 of 1 [Value 1-5 / Default !ans1_Def!]: "
:: If not defined section. This will use the default once the ENTER key has been
:: pressed and then go to :Q2.
if not defined ans1 (
echo/ & echo ENTER hit and the default used. Default is still: !ans1_Def! & echo/
set "ans1=!ans1_Def!" && goto :Q2 )
:: This section will check the validity of the answer. The "^[1-5]$" will work
:: for only numbers between one and five in this example but this can be changed
:: to pretty much suit the majority of cases. This section will also undefine
:: the ans1 variable again so that hitting the ENTER key at the question
:: will work.
echo %ans1%|findstr /r /c:"^[1-5]$" >nul
if errorlevel 1 (
echo/ & echo At errorlevel 1. Wrong format used. Default is still: !ans1_Def! & echo/
set "ans1=" && goto Q1
) else ( echo Correct format has been used. %ans1% is the one. && goto :Q2 )
:Q2
echo/
echo -----------------------------
echo/
echo Now at the next question
echo !ans1!
echo/
pause
exit
Try this:
set /p numeric=enter a number
(
(if errorlevel %numeric% break ) 2>nul
)&&(
echo %numeric% is numeric
)||(
echo %numeric% is NOT numeric
)
Just try this
#echo off
SET constNum=100
:LOOP
Set /p input=Please input a number less than %constNum% :
if "%input%" == "" echo Blank is not allowed & goto LOOP
SET "notNumChar="
for /f "delims=0123456789" %%i in ("%input%") do set notNumChar=%%i
if defined notNumChar (
echo %input% is a string
goto LOOP
) else (
REM Remove leading 0 if it has. eg: 08→8
FOR /F "tokens=* delims=0" %%A IN ("%input%") DO SET inputNum=%%A
)
REM Compare
if defined inputNum (
echo %inputNum%
if %inputNum% equ %constNum% & goto LOOP
if %inputNum% gtr %constNum% & goto LOOP
if %inputNum% lss %constNum% & goto CONTINUE
)
:CONTINUE
:: Your code here
:ASK
SET /P number= Choose a number [1 or 2]:
IF %number% EQU 1 GOTO ONE
IF %number% NEQ 1 (
IF %number% EQU 2 GOTO TWO
IF %number% NEQ 2 (
CLS
ECHO You need to choose a NUMBER: 1 OR 2.
ECHO.
GOTO ASK
)
)
It works fine to me. If he chooses numbers less or greater, strings, floating number etc, he wil receive a message ("You need to choose a NUMBER: 1 OR 2.") and the INPUT will be asked again.
#echo off
setlocal enableextensions enabledelayedexpansion
set /p UserInput=Enter a number:
set /a Test=UserInput
if !Test! EQU 0 (
if !UserInput! EQU 0 (
echo Number
) else (
echo Not a number
)
) else (
echo Number
)
yeaph everthing is great
but you forget about one little thing
0 also is a digit
;(
This is more of a user friendly way.
if %userinput%==0 (
cls
goto (put place here)
)
if %userinput%==1 (
cls
goto (put place here)
)
if %userinput%==2 (
cls
goto (put place here)
)
if %userinput%==3 (
cls
goto (put place here)
)
if %userinput%==4 (
cls
goto (put place here)
)
if %userinput%==5 (
cls
goto (put place here)
)if %userinput%==6 (
cls
goto (put place here)
)if %userinput%==7 (
cls
goto (put place here)
)
if %userinput%==8 (
cls
goto (put place here)
)
if %userinput%==9 (
cls
goto (put place here)
)
This can be used for any type of user input.
for me this is working for all non-zero values ..should i be cautious of some rare cases?
set /a var = %1
if %var% neq 0 echo "it is number"
pause