Problem
I have 2 folders (each contain one file), and I am trying to copy the files from one to the other. The CHOICE command I have written works properly; however, after an answer is chosen the program closes immediately (no matter what choice is selected). A line was displayed "1 was unexpected at this time" as it was closing.
My Code
ECHO. & CHOICE /C:123 /N /M "Copy? (1. Copy 2. Exit 3. No Choice)" & ECHO.
::assigns choice values for user
IF /i %errorlevel1% EQU 1 GOTO copy
IF /i %errorlevel2% EQU 2 GOTO end
IF /i %errorlevel3% EQU 3 GOTO no_choice
::based on selection, redirects to logic
:copy
ECHO.
ECHO.
ECHO You chose to COPY the files... Hit any key to START or ctrl-z to CANCEL.
PAUSE > NUL
SET src_folder=d:\batch
SET dst_folder=d:\newBatch
FOR /F "tokens=*" %%i in (batch.txt) DO (
xcopy /S/E/U "%src_folder%\%%i" "%dst_folder%")
GOTO end
::offers cancel; if continue, copies files from batch to newBatch
:end
EXIT
::ends program
:no_choice
ECHO No action has been performed...
PAUSE
EXIT
::print message; then ends program
Research
I have searched all over the web, as well as some of this site's posts, and it helped me construe what I currently have here. The most recent Stack Overflow post I reviewed over this topic was found here (Error: 1 was unexpected at this time). I implemented the "/i" fix that was mentioned in this post, but the individual asking the question was simply validating ECHO statements rather than CHOICE.
Question
I am confused as to where this error is occurring since there are no debugging features that I'm aware of in Batch programming, and the program closes immediately at the point of error. (is the CHOICE statement wrong, or is the subsequent logic causing this problem?)
Should I avoid the CHOICE method and handle this process similarly to the post I referenced? (is validating ECHO better than using CHOICE in this case?)
Thanks!
You made a mistake in the name of ERRORLEVEL variable.
IF %errorlevel% EQU 1 GOTO copy
IF %errorlevel% EQU 2 GOTO end
IF %errorlevel% EQU 3 GOTO no_choice
The /i option is not necessary in this case...
Related
I need to create a command that allows me to insert a check of a text file for a very specific symbol (’) and I am having trouble. It is a single quotation mark and it occasionally is found on some folders that need to be zipped and when my batch zipper encounters the folder with the symbol in it's name, it just starts having a lot of problems and creates weird files. I am not going into a lot of detail, but I just need a way to (in plain terms) check if a text file contains the symbol (’) and if it does, send the script to an error line (just something to indicate the symbol was found, like "echo error found"). And if not, then just send it to the rest of the script...
Like FINDSTR "’" dirlist.txt
if found goto err else goto resume
I know that is very incorrect but you get the idea.
Here is what I have so far and I still have made no progress getting it to work:
findstr /i /c:"’" C:\ACFZ\FORZIP\dirlist.txt >2
if %errorlevel% EQU 0 (goto LABEL0) else (goto LABEL1)
:LABEL0
msg %username& "An invalid symbol has been found. Remove any single quotation marks (’) from the folder names and try again. If unsure, simply remove anything that looks like an apostrophe."
pause
goto ERROR
:LABEL1
echo No errors found, continuing
pause
goto ZIPSTART
:ERROR
echo an error was found, exiting...
pause
goto EXIT
It always ends up saying no errors, even though the file has the symbol in it.
Here is the text file I need to search (dirlist)
2082708 Amboy Bank
2082712 Cavender’s
2082736 Elizabeth Board of Education
2082763 Tri-Valley Developmental Services LLC
2082773 Vector Management
OK, so I finally got it working right... Thanks to Harvey, I used the method of outputting any results to a separate file, and then checking that file for contents. Which actually works great, because if it finds an issue, it will show you the full name of the problem folder(s) so you can easily fix it.
Here is the snippet of the working part:
findstr "'" C:\ACFZ\FORZIP\dirlist.txt > error.txt
findstr "." error.txt >nul
IF %ERRORLEVEL% EQU 0 GOTO POPUP
IF %ERRORLEVEL% EQU 1 GOTO ALLCLEAR
and here it is with a bit more detail:
CD C:\ACFZ\FORZIP
DIR /AD /B /ON >dirlist.txt
Echo Checking for errors in folder names...
ping -n 3 localhost >nul
REM that is not an apostrophe!
findstr "'" C:\ACFZ\FORZIP\dirlist.txt > error.txt
findstr "." error.txt >nul
IF %ERRORLEVEL% EQU 0 GOTO POPUP
IF %ERRORLEVEL% EQU 1 GOTO ALLCLEAR
REM Errorlevel 0= Something found, 1= nothing found
:POPUP
color cf
msg %username% "An invalid symbol has been found. Remove any single quotation marks (’) from the folder names and try again. If unsure, simply remove anything that looks like an apostrophe."
goto ERROR
:ALLCLEAR
echo No errors found, continuing...
ping -n 3 localhost >nul
ping -n 3 localhost >nul
goto ZIPSTART
:ERROR
echo An error was found in the following folder name(s) below:^
findstr "." error.txt
echo.
Echo Remove any symbols from the above folder name(s)
echo within your completed folder and try again.
Echo This program will now exit.
pause
goto EXIT
:ZIPSTART
REM Zip contents of each directory
for /f "tokens=*" %%a in (dirlist.txt) do (
CD "%%a"
wzzip "C:\ACFZ\ZIPPED\%%a.zip"
CD..
)
Glad I was able to fix this. I guess WinZip goes really crazy from that quotation mark. The reason I needed this was I wrote this batch script (there is more to it than what I have above, as this was the part I needed to work on) to automate the zipping and backup process at my work, so that the folders for the month's jobs are zipped up and then copied onto the server for archive. It was a pain to manually do it, so with this I can just do it all in one step.
Oh and yeah the errorlevel issue was I did not have it entered correctly. I did not space them over to the right.
Thanks to all who helped.
%error_level% indicates the status of the execution which always successful (0) unless you pass in wrong arguments (e.g. try run findstr without argument or with a wrong file name).
In your case, you need to examine the output (messages printed on the screen) of findstr. One approach is to rely on the fact that nothing is printed on the screen if findstr finds no string matched the search. For example:
set found=""
findstr "'" C:\ACFZ\FORZIP\dirlist.txt > findresult.txt
call:CheckEmptyFile findresult.txt found
if "%found%" EQU "FOUND" (
echo An invalid symbol has been found
) else (
echo No errors found, continuing
)
REM your execution goes here
REM Clean up
del findresult.txt
goto :eof
:CheckEmptyFile
if %~z1 == 0 (
set "%~2=NOTFOUND"
) else (
set "%~2=FOUND"
)
goto :eof
(Reference: Windows BAT : test if a specific file is empty)
I'm a pretty inexperienced coder and I love batch because its one of the more straight forward coding languages but I want to save multiple variables to a txt file or other format then "load" those same variables to the batch file not to type so the player can see but so that I can use it
This is what I have so far for loading:
:nick
cls
color 02
set /p nick= Enter your nickname:
if exist ((savegame%nick%.txt) goto load)
goto instructions
:load
for /f "blevel=" %%a in (savegame%nick%.txt) do set blevel=%%a&goto loadexist
for /f "mlevel=" %%a in (savegame%nick%.txt) do set mlevel=%%a&goto loadexist
for /f "alevel=" %%a in (savegame%nick%.txt) do set alevel=%%a&goto loadexist
this is what I have for saving:
:save
cls
echo Saving please wait...
(echo blevel=%blevel%)> savegame%nick%.txt
(echo mlevel=%mlevel%)>> savegame%nick%.txt
(echo alevel=%alevel%)>> savegame%nick%.txt
timeout /t 5 /nobreak >nul
exit
Like I said I'm very inexperienced and my goal is to create a game please feel free to point out any flaws. In my game I have 3 characters Brutus, Mediana, and Achilles. blevel, mlevel, and alevel refers to the place in the game where you are and the character you chose to play as so you don't have to play the whole game to get to where you were.
As this is obviously still a work in progress I only have one instance where you can save and its in the first Brutus promt:
:brutus 1
set blevel=1
cls
echo.
echo.
echo.
echo You chose brutus good choice but are you sure this is who you want?
echo 1) go back and choose
echo 2) continue
echo 0) save
set /p c=C:\
if "%c%" == 1 goto choose character
if "%c%" == 2 goto brutus 2
if "%c%" == 0 goto save
goto brutus 1
So when you get to a choice like this your respective level goes up. Every time I reopen the game it either closes cmd when I type the nickname I used to save the file or it doesn't work and skips over it like the file or the variables in it don't exist. My intention is to do this with all three characters at every crucial choice so in the end I will probably have at least 100 per character.
your "save" logic is ok.
For loading, you need just a single line:
for /f "delims=" %%i in (savegame%nick%.txt) do set "%%i"
When you run it with echo on, you see that it processes every line and sets your variables.
This code is part of a chat program that I am currently working on. The 'else' part of my program is the one that doesn't work. The program quits instead of going to :home
:join
cls
if not exist "C:/Users/Public/room.cmd" (
echo No room has been found.
echo.
set /p choiceretry=Do you want to retry? y/n
if "%choiceretry%"=="y" goto join
if "%choiceretry%"=="n" goto home
) else (
cls
"C:/Users/Public/room.cmd"
echo A room has been found.
pause >nul
echo Joining
set roomjoined=1
echo %roomjoined%
goto home
)
:home
echo this finally works
pause
I have tried changing the code several times starting from 'echo Joining'
Anyone know why cmd quits?...
:) :) :)
Thanks in advance
The problem is the way you run room.cmd; you must use call to return from it:
call "C:/Users/Public/room.cmd"
Otherwise, execution will not return from room.cmd to the original batch file that ran it.
Hint: Consider to use choice instead of set /P for Y/N decisions.
Firstly, please don't left justify your code blocks. It's much easier to read code that's properly indented.
Secondly, when retrieving values within a code block, you need delayed expansion. See setlocal /? in a cmd prompt for more information. This is the reason for the unexpected behavior. Your variables retrieved within the same parenthetical code block in which they were set won't contain the values you expect unless you retrieve them with delayed expansion syntax. As an alternative, you could use the choice command and if errorlevel, which would result in a bit nicer user experience I think.
Thirdly, when testing user input, you should use the /i switch in your if statements for case-insensitivity. This isn't relevant if using choice / if errorlevel though.
Fourthly, Windows paths use backslashes, not forward slashes.
I'd fix it this way:
#echo off
setlocal
:join
cls
if errorlevel 1 set /P "=Retrying... "<NUL
if not exist "C:\Users\Public\room.cmd" (
echo No room has been found.
echo.
choice /c yn /n /m "Do you want to retry? [y/n] "
if errorlevel 2 goto home
goto join
) else (
"C:\Users\Public\room.cmd"
echo A room has been found.
pause >nul
echo Joining
set roomjoined=1
)
:home
echo this finally works
pause
I'm trying to re-use the batch file code in order to perform a similar tasks in a menu pages.
The main menu consists of 10+ options.
When I go inside the each menu items, I need to display a following in text
Press [C] to Continue or [X] to exit [C/X]: _
I created labels in each menu time and re-direct to the code which is responsible for prompting the message and do necessary actions.
How can I use this following code as a subroutine, so that I don't have to re-write the code several times.At the moment I hard code it in each menu item. It would have been easy to call it as a sub routine.
:MiniMenu1
SET INPUT1=
SET /P INPUT1=Press [Y] to Continue Installation or [N] to go back [Y/N]:
IF /I '%INPUT1%'=='y' GOTO Mini_cont1
IF /I '%INPUT1%'=='n' GOTO Mini_back1
ECHO ============INVALID INPUT============
ECHO Please select a number from the Menu Options
ECHO -------------------------------------
ECHO ======PRESS ANY KEY TO CONTINUE======
PAUSE > NUL
GOTO MiniMenu1
Where as my code for main menu item pages are
:Selection1
:: MAin menu item 1
GOTO MiniMenu1
:Mini_cont1
:: xCopy update.zip C:\python27\ /y
#echo Update Completed.
pause
:Mini_back1
:: end
GOTO MENU
Ah - thinking along the right lines. Very good.
#echo off
setlocal
call :ask Question number one
if errorlevel 2 goto Q1X
call :ask Question number two
if errorlevel 2 goto Q2X
::get here for Q1Q2 responses both C
goto :eof
:ask
choice /c CX /N /M "%*"
goto :eof
Here's a basic template. From the prompt, type choice /? for instructions about options.
Hint: set "choices=wqzk" then in the subroutine choice /c %choices% /N /M "%*" would allow you to change the choices available. /n prompts with the available choices, so you've no need to specify that in the text, just make it obvious - Whatever, Quit, Zap, Kill should be obvious for wqzk for instance.
The return in %errorlevel% will the the sequence-number of the character chosen. W==>1, Q==>2..K==>4. In the traditional construct, if errorlevel n the comparison is true if errorlevel is n or greater than n so it would be traditional to use
if errorlevel 4 goto QnA4
if errorlevel 3 goto QnA3
if errorlevel 2 goto QnA2
:: if it gets here, errorlevel is 1 hence choice was first character.
which is shorter than the "modern" way
if %errorlevel%==1 goto QnA1
if %errorlevel%==2 goto QnA2
if %errorlevel%==3 goto QnA3
:: if it gets here, errorlevel is 4 or more hence choice was fourth or later character.
Note: %* means all of the arguments passed to the subroutine so /m "%*" neatly shows the arguments passed as a prompt. There's no voodoo about that. But be careful - text only and a few symbols if you like. Symbols with a special meaning to cmd may cause unexpected results
Variables created/changed/deleted after a setlocal will be deleted/restored/resurrected when a matching endlocal is encountered. Consequently, setlocal is often used as the first "action statement" in a batch - the environment is restored to pristine when the batch ends.
To remove variables within a batch using a subroutine, you could use
call :zap we dont want these variables
:zap
if "%1" neq "" set "%1="&shift&goto zap
goto :eof
(to delete variables we dont want these and variables
or :zap version 2
:zap
for %%a in (%*) do set "%%a="
goto :eof
To remove variables which all start with an identical character-pattern, use
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
(which will remove all variables starting $. $ isn't holy - you could substitute xyz for $ here and zap xyz123 xyz789 and xyzylofone for instance)
Naturally, you could also combine the techniques...
But - it's not expensive to ask a new question on SO. Not expensive at all. Cheap even. Asking a new question rather than tagging more issues onto an existing one makes finding a solution easier (like.. someone wanting to know how to delete variables possibly wouldn't expect to find it under a question titled "batch file sub routine" for instance. It also prevents the question from becoming a saga.
I wonder is there any way to check that if a label exist in a batch file?
If %input%=ABC (
If Label ABC Exists (
Goto ABC
)
)
How can I do this?
Any help will be appreciated.
findstr /i /r /c:"^[ ]*:%input%\>" "%~f0" >nul 2>nul && goto %input%
Search the label in the current batch file and if no errorlevel, label exists
EDITED - I realized there was an error on the way i was handling the end of the label and was going to edit the answer (it has been edited anyway) and i see the dbenham aclarations. He saw the error and corrected it. Thank you. Nice answer as always, BUT this is worse than what you have exposed.
In this moment i have only a XP to test, but this is what works for me. If anyone can test on later windows versions, please.
First problem: the start of the label. As usual dbenham is correct, and any character in the set [;=,<space><tab>0xFF] can precede, single or repeated, the colon of the label. But, as far as it is the first character on the line, and it does not repeat, almost any character can precede the colon of the label (one exception is other colon). So, the following will work without problems
call :test
goto :test
echo this will not be echoed
X=;=:test
echo Hello
NO, this it not a valid line, if the parser try to execute the label line, a "command not recognized" error will happen, BUT is a valid label to call or goto.
Second problem: end of the label. As dbenham identified, most of us place a space and the list of arguments when the label is used to define a function/procedure. This was the error i realized and what has been corrected in my original answer. BUT, a space (and obviously the end of line) is not the only allowed characters after the label name. So, In the previous sample, any of the following labels will work
:test arguments
:test:arguments
:test>arguments
:test<arguments
:test&arguments
And yes,in this case they are valid commands to the parser and are valid labels
AND, of course, the two "problems" can happen at the same time
call :test
goto :test
echo this will not be echoed
< ;;:test:;; > This WORKS
echo Hello
POST EDIT 1 - It seems all this work was done years ago at dostips.com. Thanks to all who compiled the exaustive list referenced in comments. Next time, i'll search first.
POST EDIT 2 - I've been trying to deal with the limitations of findstr to include all cases. Well, there is no way. There are too many limitations, starting with the impossibility of include the 0xff character in a regular expression.
For a robust and simple solution, the answer from dbenham is the best option.
For a more robust, but STILL INCOMPLETE, no bulletproof version, and more complex than dbenham's answer
#echo off
for /l %%i in (1 1 10) do (
call :testLabelExist "test%%i" && echo Label [test%%i] exist || echo Label [test%%i] does not exist
)
exit /b
:test1
:test2
:test3
x:test4
::test5
:test6:
:test7#
:test8 parameters
:test9 parameters
:test10:myData
:testLabelExist
for /f "delims=" %%t in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /d /c #echo 0x09"'
) do (
findstr /i /m /r /c:"^[^:]*[ %%t]*:%~1[ %%t:;,=+]" /c:"^[^:]*[ %%t]*:%~1$" "%~f0" >nul 2>nul
)
exit /b %errorlevel%
And it still leaves out quoted label names, just to name one failure point.
Here is a refined, more robust version of the MC ND answer. (The original answer, his edit addresses many of these same points).
Labels are case insensitive, so the search should be case insensitive.
A valid label may have additional text after the label, so there are two searches required. The additional text is frequently used as documentation. For example: :label documentation is still a valid label.
findstr /ri /c:"^ *:%input% " /c:"^ *:%input%$" "%~f0" >nul 2>nul && goto %input%
The above should work in most situations, but there are a few unlikely conditions that could cause it to fail.
Any of the following characters can appear before the label - , ; = <space> <tab> <0x255>. They all are treated as spaces when they precede a label. But the search above only allows for <space>. A [class] expression could be used, but including <tab> and <0x255> can be awkward.
In a similar fashion, the label can be terminated by some characters other than <space> (a different list).
The label could contain regular expression meta-characters.
The FINDSTR $ anchor only recognizes <CR><LF> as end of line, so the search can fail if the script uses Unix style <LF> line endings.
The search could be refined to handle most of the above conditions. But it is simpler to simply avoid those conditions in your code. I don't think it is possible to define a bullet proof search using a single FINDSTR. A bullet proof search would require at least two FINDSTRs, and one would have to use the /G:file option - yuck.
Try the following code:
echo off
set "label=sub"
REM next line to reset errorlevel to zero:
(call )
call :%label% 2>nul || (echo %label% not found & exit /b 1)
echo back from %label%
Exit /b 0
:sab
echo here we are
first I would just rethink everything and every time I created a label, I'd go to the top in a "constants or variables area" and predefine each of the labels I had in the file... so after writing, I'd go and manually check all the labels and just do a
for %%l in ( :label1 :label2 :label3 :label4 :EOF ) do set x_labelexistconst_%%l=.
where :label1 :label2 :label3 are all eyeballed and manually entered -- think of it all as if CMD required items to be declared before use like many languages do, but if really lazy you can automate this with findstr as they have with the subroutine examples and have a line like this instead
for /F "tokens=1" %%l in ( 'findstr /i /r /c:"^[ ]*:[a-z0-9]*" "%~f0"' ) do set x_labelexistconst_%%l=.
in the code either of those loaded array of variables would it would be used like
if defined x_labelexistconst_%input% goto %input%
if defined x_labelexistconst_%input% call %input%
… the advantages of this methodology would be that there wouldn't be a CALL and then new findstr "EACH AND EVERYTIME" you did a "if exist label", but instead you would just do it "ONCE" at the beginning to preload the list/array/construct of labels.
I think this option also has the advantage of being able to do && || after the goto call because error levels aren't used to check for labels so
if defined x_labelexistconst_%input% (call %input% && echo call ok || echo call bad)
Ultimately searching file with findstr and CALL and GOTO (which reread the original bat file or something) are both very slow commands and avoiding their use or limiting their use to once or never is the best option for bat files -- especially over slow network connections where the bat file may be large. And it makes the entire script smaller simpler and easier to understand.
I didn't debug this or test this lots but it just seems like a better idea.
If you add this to the top of your function:
if "%~1" == "test" goto:eof
You can use this to check if your function exists:
call :myFunction test 2>nul
if %errorlevel% == 0 call :myFunction
Example:
call :myFunction test 2>nul
if "%errorlevel%" == "0" (call :myFunction) else (echo function does not exist)
goto:eof
:myFunction
if "%~1" == "test" goto:eof
echo Function exists
goto:eof