In a Windows 7 logon script first I want to test, if a certain file exists. If (and only if) that is the case, I want to test, if a certain registry key exists. If that is not the case, I want to do some stuff. So here's what I came up with:
IF EXIST %SOME_FILE% (
REG QUERY "HKCU\..." /v some_key
IF %ERRORLEVEL%=="1" (
do_some_stuff
)
)
The problem I'm encountering is the fact, that %ERRORLEVEL% is equal to 0, if the file exists - not if the registry key exists. If I don't do the file test, everything is fine. Now, I could use labels and goto, but I'm not really a friend of that. Is there a (simple) alternative?
Simple solution? Sure. %variable% in a code block is calculated before the code block. What you need is delayed expansion, aka command by command settings of variable contents.
Simply add this to the top of your file,
setlocal enableDelayedExpansion
and use exclamation points around variables; !errorlevel! instead of percent signs.
Although, I agree with Simon Catlin's answer completely.
Nested IFs in Windows shell script are nasty. I'd go with:
IF EXIST %SOME_FILE% (
%SystemRoot%\System32\reg.exe query hklm\system\currentcontrolset\services\wuauserv /v type 2>nul || (
echo Do something
echo Do something else
)
)
Have you considered switching to PowerShell? Note the explicit pathing for REG.EXE. This removes the risk of someone shoving a pay-loaded REG.EXE in your path.
Related
I try and avoid using batch if at all possible, as I don't like the syntax, or the lack of an inbuilt IDE, so I am pretty much a beginner with it. What I am trying to do is write an installation program, mainly in python but this part has to be done in batch, and i have written all the installation instructions, but i am now stuck on checking a user's input. What I want it to do is to get a users input (done) and then tell if it is yes, Yes, y or Y and if so, then install that part of the program. I have decided to try and use lists because that seems the simplest way, but i honestly have never used them before in batch, and so have no idea how they work. So basically I want to check if a variable exists within a list.
I know this has probably been asked before, but i couldn't find an answer that satisfied my question (admittedly i could have looked harder, but I'm tired).
Thanks in advance for any help.
choice /c YN
If errorlevel 2 echo No Selected
If errorlevel 1 echo Yes Selected
I don't like people who post they don't like batch then ask a question.
Although choice (as suggested in Noodles' answer) is probably the best way for such user input queries, I still want to show you another method (using built-in commands only):
set /P QUERY="Say yes or no: "
if /I not "%QUERY%"=="y" if /I not "%QUERY%"=="yes" goto :NO
:YES
echo You said "yes".
goto :SKIP
:NO
echo You said "no" or something else.
:SKIP
rem Continue script here...
The set /P command prompts you for input. The concatenated if commands constitute a kind of logical AND; together with the not keyworks, the condition is fulfilled if you typed anything else than y or yes; the /I switch makes the comparison case-insensitive.
Instead of the echo-s you can put any command(s) of course, or you goto somewhere else in the code.
Notice that the command extensions need to be enabled (as per Windows' default) for this to work (type set /? and also cmd /? in a command prompt window for details).
In one of my scripts, I need to use variables that contain parenthesis inside IF statements, but either the string is missing a closing parenthesis or the script exits prematurely with * was unexpected at this time (not actually an asterisk), depending on the scenario.
Example
#echo off
SET path=%programFiles(x86)%
echo Perfect output: %path%
IF NOT "%path%" == "" (
REM Variable is defined
echo Broken output: %path%
)
pause >nul
Output
Perfect output: C:\Program Files (x86)
Broken output: C:\Program Files (x86
I think/know that this is because it thinks the closing parenthesis in C:\Program Files (x86) is the end of the IF statement and it exits before the echo is complete.
Is there a simple way to cirumvent this? Preferably without resorting to
single-line IF statements, as I need to run more than one line of code within them,
copious amounts of GOTOs, as it's not practical,
SETLOCAL EnableDelayedExpansion and using !path! instead of %path%, as I recall reading somewhere that that method doesn't work consistently across OSs.
If not, I'll happily accept the most reliable solution offered, whatever it is.
(The scenario isn't up for debate. This is just a refined, concentrated example of the problem. The structure needs to be like this, as it is in my actual script, for reasons I won't go into. It's besides the point and it'll just confuse things and distract from the actual issue.)
First off - you should never use the PATH variable for your own use. It is a reserved environment variable. Using it for your own purposes can break your scripts.
The simplest solution really is to use delayed expansion. As long as your platform uses CMD.EXE then you have access to delayed expansion.
But there is a relatively easy way to make it work without delayed expansion. You can use disappearing quotes. The quote exists at parse time as the name of a FOR variable while the command is parsed. It expands to nothing before execution time.
#echo off
SET mypath=%programFiles(x86)%
echo Perfect output: %mypath%
IF NOT "%mypath%" == "" (
REM Variable is defined
for %%^" in ("") do echo fixed output: %%~"%mypath%%%~"
)
pause >nul
EDIT - When to use delayed expansion: Response to comment
I generally only use delayed expansion when it is needed (or more precisely, when it is advantageous). That being said, I usually find it advantageous in some portion of my batch code.
Major Advantages
Inside a code block in order to see changes to a variable within the block
When dereferencing the name of a variable. If a variable name is passed in as a parameter, the value of the variable can be gotten via delayed expansion: echo !%1!
When using variables as arguments to search and replace or substring operations: echo !var:%search%=%replace%!, echo !var:%start%,%len%!.
Whenever I need to expand the value and not worry about special characters within it needing escaping or quoting: set "var=A&B" & echo !var!
There are other methods to do the above (except the last), but delayed expansion is the easiest, most efficient (fastest to execute), and most reliable option.
Major Disadvantage
Any FOR variable that contains ! in its value will be corrupted when it is expanded if delayed expansion is enabled. I frequently toggle delayed expansion on and off within a FOR loop to get around the problem.
It is not good for executing a "macro" (executing code contained within a variable value) because many important phases of command parsing take place prior to the delayed expansion. So many batch features are unavailable to "macros" that are executed via delayed expansion.
my suggestion is :
if (condition_TRUE) goto goodbye_parenthesis_BEGIN
goto goodbye_parenthesis_END ----- line when previous condition is FALSE ----
:goodbye_parenthesis_BEGIN ----- line when previous condition is TRUE ----
...
variable treatment
...
:goodbye_parenthesis_END
The ) from the resolved variable in your echo statement is prematurely closing the IF block.
Ordinarily, you could fix that by escaping the ) with ^), but you can't modify the environment variable to resolve to C:\Program Files (x86^).
You can prevent this issue by surrounding the variable with quotes.
As a simpler example:
> SET bad=a)b
> IF 1 == 1 ( ECHO %bad% )
b was unexpected at this time.
> IF 1 == 1 ( ECHO "%bad%" )
"a)b"
As others already pointed out, the unescaped and unquoted closing parenthesis ) unintentionally ends the parenthesised if block.
Besides escaping, quotation, delayed expansion and "disappearing quotes", there are the following further options:
Use a for meta-variable on the quoted value and remove the quotes by the ~-modifier:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
for %%P in ("%PATH%") do echo Unbroken output: %%~P
)
pause > nul
Use the call command to initiate another variable expansion phase, together with doubled (escaped) %-symbols:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
call echo Unbroken output: %%PATH%%
)
pause > nul
Do escaping by sub-string substitution, which happens before ^-escaping is detected:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
echo Unbroken output: %PATH:)=^)%
)
pause > nul
Forgive me if I'm reading this wrong, but isn't the "NOT" causing control to enter the bracketed if and run the broken output?
what about:
#echo off
echo Perfect output: %programFiles(x86)%
IF NOT "%programFiles(x86^)%" == "" (
REM Variable is defined
echo Broken output: %programFiles(x86)%
)
pause >nul
?
i am working on a batch script.
i want to store the count of row's in variable.
like
set var = mysql -uroot -proot -e"select count(*) from table";
i also tried to do it other way like
set var= mysql -uroot -proot -e "select count(*) from table into outfile 'F:\count.txt'";
for /f %%a in ("F:\count.txt") do (
set output = %%a
echo %output%
pause
)
In above code the variable "output" shows nothing(empty).
please help me out.
I can see at least two issues in your script:
A string in double quotes inside IN( ) is treated as a literal, not as a file path/name, unless you specify the usebackq option, which enforces different semantics, whereby either double-quoted string or non-quoted one is treated as a file name.
You are storing <space>%%a into the output<space> variable, not %%a into output.
After you've fixed those two, there will remain one (probably, just one) more issue. You are assigning a value to a variable and then evaluating the variable in the same bracketed block (which is your loop body) using immediate variable expansion (%var%). This cannot work as expected. The thing is, a bracketed block is parsed entirely as a single unit, i.e. all its commands are parsed before the first one executes. As you can guess, your %output% expression will in this case evaluate to nothing, because output is not yet assigned a value at the time of parsing. (And when it is assigned a value, it will change nothing, because the previous (empty) value will already have replaced the expression.)
You can solve this using delayed variable expansion, which, as can be guessed, uses a different timing for evaluation. First, you should enable delayed expansion by issuing the SETLOCAL EnableDelayedExpansion command, then use a slightly different syntax: !var! instead of %var%.
So, if we address all the issues mentioned above, the loop may look like this:
…
SETLOCAL EnableDelayedExpansion
FOR /F "usebackq" IN ("F:\count.txt") DO (
SET output=%%a
ECHO !output!
)
You just define var with the content mysql -uroot ... but you don't execute it!
So there shouldn't be a F:\count.txt file.
Your set-syntax is wrong, remove the spaces ( from set output = %%a), else you create a variable output<space> instead of output
Your code could look like
mysql -uroot -proot -e "select count(*) from table into outfile 'F:\count.txt'";
setlocal EnableDelayedExpansion
for /f "delims=" %%a in (F:\count.txt) do (
set "output=%%a"
echo !output%!
)
You are on the right track, but the FOR /F command you show is not proper syntax - it would give an error (I'm ignoring the IN() clause because I know it is intentionally incomplete).
The "tokens=" option is incomplete, it must be followed by at least one number or an asterisk - see the documentation for more info (type HELP FOR from the command line). In your case you don't need a tokens option - the "delims=" is all you need.
There must be at least one space between DO and the (.
If you make those fixes and complete your IN() clause, then it should work. If it doesn't then something may be wrong with your IN() clause. You should post the entire command if you want help diagnosing the problem.
I like to have a final PAUSE in my *.bat scripts so I can just double click on them in Windows explorer and have the chance to read the output. However, the final PAUSE is an annoyance when I run the same script from the command line.
Is there any way to detect whether we are running the script from a command prompt (or not) and insert the PAUSE (or not) accordingly?
(Target environment is Windows XP and greater.)
Update
I've managed to compose this from Anders's answer:
(((echo.%cmdcmdline%)|find /I "%~0")>nul)
if %errorlevel% equ 0 (
set GUI=1
) else (
set CLI=1
)
Then, I can do stuff like this:
if defined GUI pause
#echo off
echo.Hello World
(((echo.%cmdcmdline%)|find /I "%~0")>nul)&&pause
...NT+ only, no %cmdcmdline% in Win9x probably.
As pointed out by E M in the comments, putting all of this on one line opens you up to some edge cases where %cmdcmdline% will escape out of the parenthesis. The workaround is to use two lines:
#echo off
echo.Hello World
echo.%cmdcmdline% | find /I "%~0" >nul
if not errorlevel 1 pause
I doubt that there's a distinction, because I think it just starts a command prompt and then runs the bat when you double click on it.
However, if you make shortcuts to the bat files and go to Properties and add in an extra argument (something like "/p") in the "Target" field, then you could check for the presence of that argument at the end of the script and pause if it is set. Then, running from the shortcut would cause it to end in a pause and running from command line wouldn't.
I was hoping the answer by #Anders would work in its own .bat file. Unfortunately, it does not for me. Based on #DarinH's comment, perhaps it does for some. The script below should work for all, but requires an extra parameter.
The key lies in the %CmdCmdLine% environment variable, which I imagine might be a bit different for a few edge cases.
PauseIfGui.bat
#echo off
if "%~1" == "" ((echo.%CmdCmdLine%)|"%WinDir%\System32\find.exe" /I "%~0")>nul && pause & exit /b
((echo.%CmdCmdLine%)|"%WinDir%\System32\find.exe" /I "%~1")>nul && pause
This accepts one optional parameter: the full path of calling script. If no params are passed, it runs the same as #Anders script.
AnyOtherFile.bat
#echo off
call PauseIfGui.bat %~f0
If opened from Explorer (i.e. double-clicking) , AnyOtherFile.bat will pause. If called from a command prompt, it will not.
In my pursuit of a solution to another environment-variable/batch-file related problem, I have once again come across a problem I have visited before (but cannot for the life of me remember how, or even if I solved it).
Say you have two BAT files (or one batch file and the command line). How can one pass an environment variable name to the other so that it can read the variable? The following example does not work:
A.BAT:
#call b.bat path
B.BAT:
#echo %%1%
> A.BAT
> %1
> B.BAT path
> %1
It is easy enough to pass the environment variable name, but the callee cannot seem to use it. (I don’t remember if or how I dealt with this the last time it came up, but I suspect it required the less-than-ideal use of redirecting temporary BAT files and calling them and such.)
Any ideas? Thanks.
You can use a little trick which unfortunately is nowhere documented:
call echo %%%1%%
Then you can use delayed expansion:
setlocal enabledelayedexpansion
echo !%1!
Delayed expansion helps here mostly because it uses other delimiters for the variable and evaluates them directly prior to running the command, while normally the evaluation might clash with normal parameter expansion.
Another way of overdoing this would be a subroutine:
call :meh "echo %%%1%%"
...
:meh
%~1
goto :eof
All examples, including the other answer, have one thing in common here: They all force cmd to evaluate variables/parameters twice. It won't work otherwise, since the first evaluation must produce %VariableName%, while the second will expand that to the variable's contents.
You can find the code also on my SVN.
B.BAT:
FOR /F "delims=" %%a IN ('echo.%%%1%%') DO set inputvar=%%a
echo %inputvar%
That is one way of doing it.
If all you want to do is echo it, you can do: echo.%%%1%%|more or echo %%%1%%|find /v ""