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
Related
This question already has an answer here:
Variables are not behaving as expected
(1 answer)
Closed 2 years ago.
I have a batchfile script that I wrote as an interface for connecting to my network shares. At this point it’s very simple. It has a list of my shares that I would like to compare to network shares in use and disable that share as option if it’s found. The following code snippet is part of a for loop that iterates over the number of shares in the list and lists them.
setlocal enabledelayedexpansion
set list[0]="\\xxx.xxx.x.xx\photo"
set list[1]="\\xxx.xxx.x.xx\photo 2"
for /l %%n in (0,1,2) do (
rem wmic netuse get remotename |findstr /C:!list[%%n]!
rem if %errorlevel% neq 0 do(command 1) else (command 2)
echo %%n !list[%%n]!
)
The rem above is removed for testing the problem.
The thought here is to use if %errorlevel% condition to catch the match. The shares are echoed with double quotes. If for example photo is mounted, both photo and photo 2 will be matched which is undesirable.
Since the shares have similar names and added number with space I need to compare the strings exactly so I have tried with findstr /x switch but this doesn’t work at all. Not sure if the entry with double quotes interferes. Removing the double quotes in the list yields an error that number after the space cannot be opened. Am I approaching this in a correct way?
May I suppose a slightly different approach?
#echo off
setlocal enabledelayedexpansion
set "list[0]=\\xxx.xxx.x.xx\photo"
set "list[1]=\\xxx.xxx.x.xx\photo 2"
for /l %%n in (0,1,2) do (
net use |findstr ilc:" !list[%%n]! " >nul && (
echo found "!list[%%n]!", do command1
) || (
echo no match for "!list[%%n]!" do command2
)
)
Changes to your code:
using net use instead of wmic, as wmic outputs Unicode, which we would have to handle. And net use is a lot faster.
using conditional operators && and || instead of errorlevel
made findstr more secure and
discarded findstr's output.
corrected the set syntax to not include the quotes to the values.
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 need to work two things into a .bat file I am working on for a little project. First things first, I have to know if any filename contained into the same folder (recursively) I launch my .bat in is any longer than 100 characters. If so, I need to make it 92 characters long and keep the extensions.
For example, I have this filename:
IncrediblyLongFileNameIAmSorryForThisItLooksLikeSomeDamnSpamJesusIAintEvenCloseTo100yetalmostwaitforitYEAH.omg
The above filename is 110 characters. I need to keep the extension, therefore the program should rename the file as this:
IncrediblyLongFileNameIAmSorryForThisItLooksLikeSomeDamnSpamJesusIAintEvenCloseTo100yetalmos.omg
So far, my main problem is that I don't know how to work with filename strings in batch. I used this code:
#echo off & setlocal enableextensions
FOR /R %%i IN (*.*) DO (
ECHO %%~nxi
FOR /f "delims=:" %%a in ('
^(echo."%%~nxi"^& echo.^)^|findstr /o .'
) DO set lenght=%%a-5
echo The length of "%%~nxi" is %lenght%
)
endlocal & goto :EOF
But I can't SET inside a FOR, and it can't do basic math either (i.e. it can't do the -5 operation).
The second thing, which I believe should be easier once the first one is done, is simply to compare all the filenames in the folder (recursive, once again) and make sure no filenames are the same. If the program finds any filenames that are the same, the second occurrence should be renamed to add something like l1l at the end. (I can't use parentheses here, therefore I use two ls instead to cover the number.) The only thing you need to take care of is the file extensions, because I can't add anything after the file extensions, lest they become unusable.
Can anyone offer explanations for how to accomplish this? I would really like to be able to work this out myself, but I simply lack experience in batch programming.
#ECHO OFF
SETLOCAL
SET "sourcedir=c:\sourcedir"
SET "tempfile=%temp%\##fn##.92"
ECHO ::>"%tempfile%"
FOR /f "delims=" %%a IN (
'dir /s /b /a-d "%sourcedir%\*" '
) DO (
SET "fullname=%%a"
SET "name=%%~na"
SET "ext=%%~xa"
CALL :chgname
)
del "%tempfile%"
GOTO :EOF
:chgname
:: Proposed new name part - first 92 characters of existing name
:: also prepare for adding modifier
SET "newname=%name:~0,92%"
SET /a modifier=0
:modl
:: See whether this name has already been found
ECHO %newname%%ext%|FINDSTR /b /e /i /g:"%tempfile%" >NUL
IF ERRORLEVEL 1 GOTO makechange
:: existing name - modify it
SET "newname=%name:~0,92%#%modifier%#"
SET /a modifier+=1
GOTO modl
:makechange
IF "%name%" NEQ "%newname%" ECHO REN "%fullname%" "%newname%%ext%"
>>"%tempfile%" ECHO %newname%%ext%
GOTO :eof
Reasonably simple problem.
Get a directory-list in basic form (full-filename only) and apply the full filename, name part and extension part to appropriately-named variables.
Manipulate the filename to a new name consisting of the first 92 characters of the original name part. Anticipate the need to modify this new name by establishing a modifier to optionally be applied.
See whether the proposed new name already exists in the temporary file of NEW names already processed. If not found on that file, safe to rename (if required) and record name used.
If the filename has already been used, modify it to the original first 92+ "#anumber#", increment the modifier in anticipation and try again.
Only two comments required further - first, I used # rather than ! because ! has a special meaning to batch. Second, writing :: to the tempfile (the name of the tempfile is irrelevant - I chose one that's unlikely to exist...) means that findstr doesn't complain because the file is empty, but :: can't possibly be a real filename.
The /b /e /i options to findstr mean that the name echoed in must exactly match a line (matches both /b - begin and /e - end) but /i - case is irrelevant.
I have the following in a BAT file:
#echo off
Set /P _environment = Please Enter Environment [d] for Development or [a] for Acceptance:
IF ((%_environment% EQU "a") OR (%_environment% EQU "d"))
(goto sub_write_files)
ELSE
(goto end)
:sub_write_files
xcopy script_temp\* \\CHU-%_environment%101\CHU\scripts /D /E /C /R /I /K /Y /S
:end
echo %_environment% Done
The logic seems well formed to me, but possibly it is not because the sub_write_files sub routine every time that I fire this command. I am assuming that the flaw is in the conditional logic.
You need to brush up on your batch syntax. Help is available for just about every command by typing either HELP command or command /? at a command prompt. For example, HELP IF will provide help on the IF command. Granted, the documentation is often incomplete and/or confusing, but it is a start.
You have lots of problems with your syntax as written. One of the most obvious is IF does not support any operators like AND, OR, XOR etc.
You can achieve the logic you were looking for with the following
#echo off
Set /P _environment = Please Enter Environment [d] for Development or [a] for Acceptance:
if "%_environment%" neq "a" if "%_environment%" neq "d" goto :end
:sub_write_files
echo xcopy script_temp\* \\CHU-%_environment%101\CHU\scripts /D /E /C /R /I /K /Y /S
:end
echo %_environment% Done
There are lots of potential improvements. For example, you might want to add the /I option to both IF statements so that case does not matter. Or you might want to loop back and try again instead of ending if an invalid value is entered.
The main problem is the complete wrong syntax of your code.
An IF-Statement only accepts one condition, you can not combine them with OR or AND.
It's not allowed to surrend the condition with parenthesis.
A beginning block has to start on the same line, also for the ELSE clause.
A set ... varname= with a space between varname and the equal sign creates a variable named varname<space>.
But the rest of your code should work ...
The logic seems well formed to me
Probably a simple IF /? would correct this.
My batch file terminates prematurely after I assign the first environmental variable (script output below). I've tried turning echo on, using errorlevels, sending the output to a text file, and checking syntax. I've spent several hours researching debugging batch scripts, but I have finally hit a brick wall.
Script's Goal: Search each directory name of the user's Program Files, looking for common antivirus programs. I realize that it would be easiest iterate through an array of antivirus names for this purpose, but I want to keep it simple for now.
#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
else echo "env. variable created successfully."
for /d %%f in (""%ProgramFiles%\*"") do (
{
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
echo "%%f"
if exist /i "*McAfee*" < %%f %AntiVirus1%="McAfee"
::find "Norton" < %%f
::find "Comodo" < %%f
::find "AVG" < %%f
}
echo %AntiVirus1%
#pause
Output of this script:
C:\Users\Matt\Desktop>set AntiVirus1="Initial Value"
C:\Users\Matt\Desktop>
Can someone point me to what I'm doing wrong?
UPDATE Corrected script, now working but returning incorrect results:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
if %%f=="*adobe*" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
First of all, ELSE must be on the same line with IF or on the same line with the closing parenthesis that pertains to IF. In you particular case you should change your first IF...ELSE command like this:
IF NOT ERRORLEVEL 0 (ECHO %ERRORLEVEL%) ELSE ECHO "env. variable created successfully."
or like this:
IF NOT ERRORLEVEL 0 (
ECHO %ERRORLEVEL%
) ELSE ECHO "env. variable created successfully."
(Capitalisation and indentation are perfectly optional.)
Other issues:
Duplicated quotation marks in the FOR loop header:
for /d %%f in (""%ProgramFiles%\*"") do (
should be
for /d %%f in ("%ProgramFiles%\*") do (
Braces ({, }) around the loop body. They are not part of the loop syntax (in fact, they are not part of batch scripting syntax at all), so should be dropped.
No closing parenthesis matching the opening one after DO. It should be added on a separate line after the loop body.
Incorrect use of ::-style comments in the loop body. They are not allowed inside bracketed blocks. Use REM instead.
UPDATE
In batch scripting, testing for a substring is done somewhat unusually. You'll need another environment variable and you'll also need to enable delayed expansion. The latter is not really connected with the comparison, but it is needed because the comparison is going to be performed within a bracketed block.
Here's your new script modified, with the changes highlighted:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
SETLOCAL EnableDelayedExpansion
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
SET "folder=%%f"
if /I NOT "!folder:adobe=!"=="!folder!" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
Here's a bit of explanation.
The ! syntax is a delayed expansion equivalent of % and is used with environment variables only, not with loop variables and not with command line parameters. Delayed expansion is needed because we are in a bracketed block. A bracketed block is parsed entirely before it starts executing, so all %var% expressions are expanded (evaluated) before the block starts and are not changed throughout the block's execution. That cannot suit us because we need to assign different values to a variable during the block's execution, and the values must be read within the block. Delayed expansion, as follows from the name, delays the expansion of a variable until the actual execution of every single command that references that variable. Because immediate expansion can still be used alongside delayed expansion, a different syntax is introduced, which is ! around variable names, instead of %.
!folder:adobe=! means evaluate folder replacing every occurrence of adobe with an empty string. The result of this expression is then compared to the (unchanged) value of folder. If there's a match, then the replacement didn't occur, which means there was no adobe in the value of folder in the first place. In this case we should do nothing. But if there was not a match, i.e. if the modified value didn't match the unmodified one, then we should set the AntiVirus1 variable. This is why there's NOT in front of the comparison.
The /I option simply means case-insensitive comparison.