I have a problem.
set /p command=
if %command% == "display (i want the second part of the variable here)" echo (second part of the variable)
For example, i type:
display hello
I want it to simply:
echo hello
I want to use this for custom commands in my game.
Firstly, you can split the text on firstword|notfirstword with a for /f loop using "tokens=1*". See help for in a console window for full details.
Next, you could use attempt to call :label where :label is whatever the first word was. In essence, you're creating batch functions and letting the user choose which function is executed. If the function label doesn't exist, then errorlevel will be non-zero and you can handle appropriately using conditional execution. This makes it easy to expand your script without having to add an if /i statement for each choice or synonym you add. (It might be a good idea to hide the error message for attempting to call a non-existent label by redirecting 2>NUL.) Here's a full example:
#echo off & setlocal
:entry
set /P "command=Command? "
for /f "tokens=1*" %%I in ("%command%") do (
2>NUL call :%%I %%J || (
if errorlevel 1000 (exit /b 0) else call :unsupported %%I
)
goto :entry
)
:display
:echo
:say
:show
echo(%*
exit /b 0
:ping
ping %~1
exit /b 0
:exit
:quit
:bye
:die
echo OK, toodles.
exit /b 1000
:unsupported <command>
1>&2 echo %~1: unrecognized command
exit /b 0
Related
I have made a list of names of which I wish to choose with several numbers, and the do loop confirms these numbers. But for some reason I cannot get these chosen numbers aligned to the GOTO echo statement. Instead they just list them all? I have to admit I am new to batch scripting but I'm sure there is an abundance of experts who can see where I am going wrong, pls pls help me?
The code i have is here along with the output I get in command prompt
:START
setlocal EnableDelayedExpansion
#echo OFF
echo. [RUN BVTS]
ECHO 1.RS_CommonDataSet
ECHO 2.RS03_PackageManager
ECHO 3.RS04_SequencerCalendars
ECHO 4.RS06_EditorTestsPerformance
ECHO 5.RS06_NewEditorTests
ECHO OFF
set /p BVTSUITE="What BVT suite do you wish to run? "
rem This lists all the BVT's that you want to run with spaces
set n=0
for %%a in (%BVTSUITE%) do (
set /A n+=1
set "BVTSUITE[!n!]=%%~a"
)
rem List the file names
for /L %%i in (1,1,%n%) do (
echo %%i- !BVTSUITE[%%i]!
)
IF %errorlevel%==1 set goto 1
:1
ECHO "You have chosen number RS_CommonDataSet"
IF %errorlevel%==2 set goto 2
:2
ECHO "You have chosen number RS03_PackageManager"
IF %errorlevel%==3 set goto 3
:3
ECHO "You have chosen number RS04_SequencerCalendars"
IF %errorlevel%==4 set goto 4
:4
ECHO "You have chosen number RS06_EditorTestsPerformance"
IF %errorlevel%==5 set goto 5
:5
ECHO "You have chosen number RS06_NewEditorTests"
The output I get in command prompt is:
[RUN BVTS]
1.RS_CommonDataSet
2.RS03_PackageManager
3.RS04_SequencerCalendars
4.RS06_EditorTestsPerformance
5.RS06_NewEditorTests
What BVT suite do you wish to run? 1 3 (I chose these numbers)
1- 1
2- 3
"You have chosen number RS_CommonDataSet"
"You have chosen number RS03_PackageManager"
"You have chosen number RS04_SequencerCalendars"
"You have chosen number RS06_EditorTestsPerformance"
"You have chosen number RS06_NewEditorTests"
As you can see my 1 and 3 was chosen but it is not in the ouput, it just shows the entire list?
I've added comments to the parts that I've added, but basically, your entire script runs because you never told it not to. Batch scripts are just a list of commands and the order to run them in, so unless you use a goto, a call, or an exit, each line will run one after the next.
If you use subroutines, you'll be able to control which parts of the script get executed. I've also added super rudimentary input validation because sooner or later, the user will enter something incorrectly. It's not foolproof, but it's good enough that I'm comfortable posting it.
#echo off
setlocal EnableDelayedExpansion
echo. [RUN BVTS]
echo 1.RS_CommonDataSet
echo 2.RS03_PackageManager
echo 3.RS04_SequencerCalendars
echo 4.RS06_EditorTestsPerformance
echo 5.RS06_NewEditorTests
:get_suite_list
set /p "BVTSUITE=What BVT suite do you wish to run? "
rem This lists all the BVTs that you want to run with spaces
if not defined BVTSUITE goto :get_suite_list
set n=0
for %%a in (%BVTSUITE%) do (
set /A n+=1
set "BVTSUITE[!n!]=%%~a"
)
REM List the file names
for /L %%i in (1,1,%n%) do (
echo %%i - !BVTSUITE[%%i]!
REM Double-check that all given BVT suites are valid and the user didn't
REM provide an invalid value like 7 or Q or something
find ":suite_!BVTSUITE[%%i]!" "%~f0" >nul 2>nul || (
echo Invalid suite !BVTSUITE[%%i]! given.
goto :get_suite_list
)
)
REM Actually call the different suites.
for /L %%i in (1,1,%n%) do (
call :suite_!BVTSUITE[%%i]!
)
REM At this point, the main script ends and returns to the command prompt
exit /b
REM These are subroutines. You'll never be able to reach them unless you use
REM the CALL command to tell cmd to jump to here, and then once the exit /b
REM command is executed, flow returns to where it was when CALL was executed.
REM Also, I've prepended the string suite_ in front of each label because
REM having a single number as a label is a terrible idea since it's absolutely
REM meaningless.
:suite_1
echo You have chosen number RS_CommonDataSet
exit /b
:suite_2
echo You have chosen number RS03_PackageManager
exit /b
:suite_3
echo You have chosen number RS04_SequencerCalendars
exit /b
:suite_4
echo You have chosen number RS06_EditorTestsPerformance
exit /b
:suite_5
echo You have chosen number RS06_NewEditorTests
exit /b
You don't need to identify each file name via the given suite individually. Just define an array of file names and select the appropiate element via the given value:
#echo OFF
setlocal EnableDelayedExpansion
rem Define array with the names of the suites
set numSuites=0
for %%a in (RS_CommonDataSet RS03_PackageManager RS04_SequencerCalendars RS06_EditorTestsPerformance RS06_NewEditorTests) do (
set /A numSuites+=1
set "BVTNAME[!numSuites!]=%%a"
)
rem Show the menu
echo [RUN BVTS]
echo/
for /L %%i in (1,1,%numSuites%) do (
echo %%i.!BVTNAME[%%i]!
)
echo/
set /p "BVTSUITE=What BVT suite do you wish to run? "
rem This lists all the BVT's that you want to run with spaces (and file names)
set n=0
for %%i in (%BVTSUITE%) do (
set /A n+=1
if defined BVTNAME[%%i] (
echo !n!- %%i.!BVTNAME[%%i]!
) else (
echo !n!- Invalid suite: %%i
)
)
For searches in Windows Registry using batch scripting, I have to loop through a few keys, make a comparison to determine which is the right one, and then update the key.
Iteration in a for loop seems impossible to break out of. I have seen that others are facing similar issues but there does not seem to be a simple solution. Here is a snippet that demonstrates the issue.
#echo off
echo.
echo Diet Favorites
set favorite="bananas"
for %%a in (apples, bananas, chocolates) do call :reviewList %%a
echo.
echo Processing completed.
goto end
:reviewList item
set foundFavorite="false"
call :chooseFavorite "%~1"
if /I "%foundFavorite%"=="true" (
echo found the favorite - %~1
exit /b 0
) else (
echo skip %~1
)
endlocal & goto :eof
:chooseFavorite item
if "%~1"==%favorite% set "foundFavorite=true"
endlocal & goto :eof
:end
chooseFavorite returns the favorite in an environment variable. The output shows that reviewList continues looping after the favorite is identified.
Diet Favorites
skip apples
found the favorite - bananas
skip chocolates
Processing completed.
The comparison is working, but if the exit worked as expected, chocolates diet option should not be listed at all. How do I neatly break out of the loop iteration?
#ECHO Off
SETLOCAL
echo.
echo Diet Favorites
set "favorite=bananas"
for %%a in (apples, bananas, chocolates) do call :reviewList %%a&IF DEFINED foundFavorite GOTO foundit
:foundit
echo.
echo Processing completed.
goto end
:reviewList param
set "foundFavorite="
call :chooseFavorite "%~1"
if DEFINED foundFavorite (
echo found the favorite - %~1
) else (
echo skip %~1
)
goto :eof
:chooseFavorite param
if "%~1"=="%favorite%" set "foundFavorite=%~1"
goto :eof
:end
GOTO :EOF
Since you don't use any setlocal commands in your posted code, the endlocals are superfluous.
This version sets foundFavorite to empty, and sets it to something (can be anything you like - I just chose the thing that was found, which is often convenient) when a match is found. if defined variable interprets the run-time status of the variable, so it can be used within a for loop.
BTW- try running your original code with echo ON. You would see that your if statements are actually executing if /i ""false""=="true" ...
This is why it's convention on SO to use the set "var=value" syntax for string-assignments. You can then apply quotes as required without worrying about where or whether to apply or remove quotes that may be in variables.
Check the return code of :reviewList in your loop, and break accordingly. You'll need to call properly exit /b 0 on non-breaker calls, and exit /b 1 (or any non-zero value) for breaker calls.
Try that:
#echo off
echo.
echo Diet Favorites
set favorite="bananas"
for %%a in (apples, bananas, chocolates) do (
call :reviewList %%a || goto :break_loop
)
:break_loop
echo.
echo Processing completed.
goto end
:reviewList item
set foundFavorite="false"
call :chooseFavorite "%~1"
if /I "%foundFavorite%"=="true" (
echo found the favorite - %~1
exit /b 1
) else (
echo skip %~1
)
endlocal
exit /b 0
:chooseFavorite item
if "%~1"==%favorite% set "foundFavorite=true"
endlocal & goto :eof
:end
Output is:
S:\Temp>test
Diet Favorites
skip apples
found the favorite - bananas
Processing completed.
(This is my first post here, so bear with me)
Can you show the last user-input in a batch file? I'm gonna try to keep it simple here.
#echo off
:menu
echo Type 1 to proceed.
set /p example=
if "%example%" == "1" GOTO :proceed
GOTO :error
:proceed
pause
:error
cls
echo You wrote (last user input), that's not correct.
timeout 30
GOTO :menu
I know that I could replace the (last user input) with %example%, but then I'd have to make custom error messages for every category, and there are about 50 of them. It'd be easier with a last input command.
By the way, I've taught myself everything that I know about batch, so my example probably has major issues right now, but it works somehow.
You could centralize all user input into a function (user_input)
:menu1
echo Type 1 to proceed.
call :userInput example
if "%example%" == "1" GOTO :proceed
GOTO :error
:menu2
echo Type 42 to proceed.
call :userInput answer
if "%answer%" == "42" GOTO :proceed
GOTO :error
:userInput
set /p LAST_INPUT=
set "%1=%LAST_INPUT%"
exit /b
:proceed
pause
:error
cls
echo You wrote "%LAST_INPUT%", that's not correct.
timeout 30
GOTO :menu
I don't know how to do it without temp file. TO get the things written int the console you need the doskey /history (this will skip the running of the script itself):
#echo off
setlocal enableDelayedExpansion
set "last="
set "but_last="
doskey /history > log.txt
for /f "tokens=* delims=" %%# in (log.txt) do (
set "but_last=!last!"
set "last=%%#"
)
echo "%but_last%"
del /s /q log.txt >nul 2>nul
If I want to call :foo in foo.bat from bar.bat I do:
::foo.bat
echo.wont be executed
exit /b 1
:foo
echo foo from foo.bat
exit /b 0
and
::bar.bat
call :foo
exit /b %errorlevel%
:foo
foo.bat
echo.will also not be executed
But if I don't know the label name but get it passed as a parameter I'm stuck
::bar.bat
:: calling a dynamic label is no problem
call :%~1
exit /b %errorlevel%
::don't know how to "catch-all" or set context of "current-label"
:%~1
foo.bat
You can use a batch parser trick.
You don't need to do anything in foo.bat for this to work
::foo.bat
echo.wont be executed
exit /b 1
:func1
echo foo from foo.bat
exit /b 0
:unknown
echo Hello from %0 Func :unknown
exit /b
You only need to add any labels into bar.bat that you want to call in foo.bat
#echo off
::bar.bat
call :unknown
echo Back from Foo
call :func1
echo Back from Foo
exit /b %errorlevel%
:unknown
:func1
foo.bat
echo NEVER COMES BACK HERE
The trick is that after calling a label in bar.bat and then start foo.bat without calling it (only foo.bat), the foo.bat is loaded and the last called label is jumped to.
Labels are searched by the parser literally before resolving environment variables or argument references, so you cannot use such in labels.
However, hereby I want to provide an approach that allows to use dynamic labels, although I do not understand what is the purpose of that, so this is more kind of an academic answer...
The batch file parser of cmd does not cache a batch file, it reads and executes it line by line, or correctly spoken, it reads and executes each command line/block individually. So we can make use of that and let the batch file modify itself during its execution, given that the modified part lies beyond the currently executed code portion. The following script accomplishes that:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Check whether a label name has been delivered:
if "%~1"=="" (
echo ERROR: No label name specified! 1>&2
exit /B 1
)
rem /* Call sub-routine to replace the literal label string `:%~1`
rem within this batch file by the given dynamic label name: */
call :REPLACE_LINE "%~f0" ":%%%%~1" ":%~1" || (
rem /* In case the given label is `:REPLACE_TEXT`, display error
rem message, clean up temporary file and quit script: */
>&2 echo ERROR: Label ":%~1" is already defined!
2> nul del "%~f0.tmp"
exit /B 1
)
rem // Perform call of the sub-routine with the dynamic label name:
call :%~1
rem /* Call sub-routine to replace the given dynamic label name
rem within this batch file by the literal label string `:%~1`: */
call :REPLACE_LINE "%~f0" ":%~1" ":%%%%~1"
endlocal
exit /B
:REPLACE_LINE val_file_path val_line_LOLD val_line_LNEW
::This sub-routine searches a file for a certain line
::case-insensitively and replaces it by another line.
::ARGUMENTS:
:: val_file_path path to the file;
:: val_line_LOLD line to search for;
:: val_line_LNEW line to replace the found line;
setlocal DisableDelayedExpansion
rem // Store provided arguments:
set "FILE=%~1" & rem // (path of the file to replace lines)
set "LOLD=%~2" & rem // (line string to search for)
set "LNEW=%~3" & rem // (line string to replace the found line)
set "LLOC=%~0" & rem // (label of this sub-routine)
rem // Write output to temporary file:
> "%FILE%.tmp" (
rem /* Read the file line by line; precede each line by a
rem line number and `:`, so empty lines do not appear as
rem empty to `for /F`, as this would ignore them: */
for /F "delims=" %%L in ('findstr /N "^" "%FILE%"') do (
rem // Store current line with the line number prefix:
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Check current line against search string:
if /I "!LINE:*:=!"=="!LOLD!" (
rem // Current line equals search string, so replace:
echo(!LNEW!
) else if /I not "!LNEW!"=="!LLOC!" (
rem // Current line is different, so keep it:
echo(!LINE:*:=!
) else (
rem /* Current line equals label of this sub-routine,
rem so terminate this and return with error: */
exit /B 1
)
endlocal
)
)
rem /* Searching and replacement finished, so move temporary file
rem onto original one, thus overwriting it: */
> nul move /Y "%FILE%.tmp" "%FILE%"
endlocal
exit /B
:%~1
::This is the sub-routine with the dynamic label.
::Note that it must be placed after all the other code!
echo Sub-routine.
exit /B
Basically, it first replaces the literal label string (line) :%~1 by the string provided as the first command line argument, then it calls that section by call :%~1, and finally, it restores the original literal label string. The replacement is managed by the sub-routine :REPLACE_LINE.
foo.bat (secondary):
#echo off
echo this is foo.bat
REM check, if the label is defined in this script:
findstr /xi "%~1" %~f0 >nul 2>&1 || goto :error
goto %~1
:foo
echo reached foo.bat, label :foo
exit /b 0
:error
echo wrong or missing label: "%~1"
exit /b 1
bar.bat (primary)
#echo off
echo this is bar.bat
call foo.bat :foo
echo back to bar.bat - %errorlevel%
call foo.bat :foe
echo back to bar.bat - %errorlevel%
call foo.bat
echo back to bar.bat - %errorlevel%
exit /b
As aschipfl already correctly stated you can't have a dynamic label, but you can dynamically call present labels, but you should check for the presence prior calling it like Stephan does but in the primary batch.
So this is a combination of Stephans and jebs batches.
:: bar.bat
#echo off
REM check, if the label is defined in this script:
If "%~1" neq "" findstr /xi "%~1" %~f0 >nul 2>&1||goto :error&&Call :%~1
call :Func1
echo Back from Foo
call :func2
echo Back from Foo
exit /b %errorlevel%
:error
echo wrong or missing label: "%~1"
exit /b 1
:func1
:func2
foo.bat
echo NEVER COMES BACK HERE
:: foo.bat
#Goto :Eof
:func1
echo reached foo.bat, label :func1
exit /b 0
:func2
echo reached foo.bat, label :func2
exit /b 0
Sample output:
> bar
reached foo.bat, label :func1
Back from Foo
reached foo.bat, label :func2
Back from Foo
> bar fx
wrong or missing label: "fx"
A while ago I made a function that you can call from the command prompt or any batch file (it was just for fun, I don't see how it could be useful). It basically just makes your (Microsoft) computer speak whatever you wrote in as the parameter.
I recently got some inspiration to add a switch to it where it would read the contents of a file. My standalone script worked, but when I added it to my function, it didn't work as I would have liked.
Here's the code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
if "%~2 X" equ "/f X" (
if not exist %~1 (
echo File does not exist or cannot be found.
endlocal
exit /b
)
set cont=
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
echo a.speak "%cont%" >> "Talk.vbs"
start /WAIT Talk.vbs
del Talk.vbs
endlocal
exit /b
)
set text=%~1
echo set speech = Wscript.CreateObject("SAPI.spVoice") > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
start /WAIT talk.vbs
del Talk.vbs
endlocal
exit /b
Unfortunately I don't have working function code (before I added the /f switch).
This is a last resort for me as I've edited it heavily and scoured the code for any give away as to what the problem might be.
Another bad thing is that I didn't take note of what I changed, so I can't exactly tell you what I've tried. I can tell you what the outputs are though.
The first time I tried, it gave the output The syntax of the command is incorrect.
It's now at the point where the original function (just converting text to speech) doesn't work anymore. The contents of the file Talk.vbs (which was made during the process) is a.speak "".
I'll keep updating my attempts, but knowing me it's something really simple that I've overlooked.
--EDIT--
At the suggestion of someone, I put carats before the square brackets in the syntax section. Nothing changed.
Along with escaping the parenthesis you also had to surround if exist %~1 in quotes in case of a argument of "some words I want it to say". Also cleaned it up a bit. Code at the bottom, but first an explanation.
If you looked at talk.vbs before it was deleted you would see this:
a.speak "!cont! contents of the file here"
This is because of this code:
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
If you turned echo on and watched the code you would see the last unescaped ) was taking the contents of the for loop and including it in the redirect.
Corrected and cleaned code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
set text=
if [%2]==[/f] (
if exist "%~1" (
for /f "usebackq delims=" %%i in (%1) do set text=!text! %%i
) else (
endlocal
exit /B
)
)
if [%2]==[] set text=%~1
echo set speech = Wscript.CreateObject^("SAPI.spVoice"^) > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
cscript //NoLogo //B talk.vbs
del Talk.vbs
endlocal
exit /b
Edit: fixed the for statement pointed out by Andriy M
In your echo statements that contain parentheses, try escaping the parentheses with carats. I suspect especially the echo within the if statement is partially getting evaluated literally.
One other minor suggestion, I would also replace
start /WAIT Talk.vbs
with
cscript /nologo Talk.vbs
It's not that I think the start /wait is causing the error, but it does cause a second console window to appear temporarily for no good reason -- or it will whenever your script executes that far, anyway.
I made a few other suggested changes here, such as eliminating the need for a /f switch. If "%1" is the name of a file that exists, read it. Otherwise, treat it as text to read. And instead of having a separate subroutine for reading a file versus getting text from input, all that needs to happen is a variable has a different value.
#echo off & setlocal enabledelayedexpansion
if "%1"=="/?" ( goto usage )
if "%1"=="" ( goto usage )
if "%1"=="--help" ( goto usage )
if exist "%1" (
set txt=
for /f "usebackq tokens=*" %%i in (%1) do set txt=!txt! %%i
) else (
set txt=%1
)
echo Set a = Wscript.CreateObject^("SAPI.SpVoice"^) > "talk.vbs"
echo a.speak "%txt%" >> "talk.vbs"
cscript /nologo talk.vbs
del talk.vbs
endlocal
goto :EOF
:usage
echo.
echo TALK ["text"^|filename]
echo.
echo talk filename -- speaks the contents of filename
echo talk "text" -- speaks the supplied text
endlocal
goto :EOF