Executing batch file breaks when checking if variable has value - batch-file

To chcek if file is loaded or not, I load its contents using:
set /p filevar=file.txt
and check if var is empty:
if "%filevar%"="" exit
When script chceks file with multiple lines, execution of script stops, so i suppose that chcecking fails. Why script fails? How to perform such check properely?

Firstly, you've got the syntax of your set /p command messed up. As it is, it will prompt the user with the text "file.txt". I think what you mean is
set /p "filevar=" <file.txt
You should also use the /b switch with exit to prevent the console window from closing if your script is run from the command line.
But yeah, as jeb states, to check whether left equals right, either use == or equ. Or, as dbenham reminds me, if you're checking for an empty value, you can also use if not defined.
if "%filevar%"=="" exit /b
if "%filevar%" equ "" exit /b
if not defined filevar exit /b
All three statements will have the same result1.
In a console window, enter help if for more information.
1 It's worth mentioning that, while those three if statements have the same result outside of a parenthetical code block, only the third one will work reliably within parentheses (such as in a for loop). The first two would need delayed expansion to work properly if used within the same code block as %filevar% is set.

Related

IF command not working when using 'NOT or '==' to use CALL and use SET for setting a variable

I'm creating a simple program that will echo variables from SET /p strings into a nice, neat list. However, I am having trouble creating 2 working IF statements, one using NOT, and one using == to detect if one of my variables, %pwad%, is empty, or contains values. I want to use what the IF statement returns to set variable %finalpwad% to either "No pwad detected" or %pwad%.
How should I properly write this statement? Where might I need corrections, fixing the IF statements or maybe even the part where it sets %pwad% to %finalpwad%?
I have already tried fixing my call part and what they call from, but to no avail. I'm almost sure this is an IF statement issue, as I'm not too good with them, and always struggle reading the notes about the command from IF /?.
Here's a snippet of my code and the source of the problem I am having:
set /p pwad=Set a pwad (or none):
if %pwad% NOT [] call :yespwad & pause
if %pwad% == [] :nopwad & pause
:nopwad
set finalpwad=No pwad detected
goto :printout
:yespwad
set finalpwad=%pwad%
goto :printout
I expect the output to continue onto :printout, where it echoes all the variables the user enters, but it instead exits the program, and makes it so I can't find out whether it properly read my IF NOT or IF == statements. I rudimentarily added pauses to snuff out the problem and see where the source was, and I concluded it must the IF statements.
The help file clearly shows the proper syntax for comparing strings.
IF [NOT] string1==string2 command
It is recommended that you use quotes as well when comparing strings.
IF "string1"=="string2" command
IF comparisons are literal. Each side of the comparison has to match. Using brackets does not check for an empty string.
There also is an option to check if a variable is defined.
IF DEFINED VAR command
Looking at your logic you could essentially do this:
#echo off
set /p "pwad=Set a pwad (or none): "
IF DEFINED pwad (
set "finalpwad=%pwad%"
) ELSE (
set "finalpwad=No pwad detected"
)

Handling of variables in for-loop of a Windows batch file

I've been investigating this a lot with threads on StackOverflow and the like, but although I feel I'm close to the solution, this problem is giving me headaches.
What I'm trying to do: When a specific external hard drive is connected (distinguished via VolumeSerialNumber over WMIC), the drive letter is found out, and mirroring is done via robocopy. The script is executed via double-click. This is what I have so far:
FOR /F "skip=1" %%i in ('wmic logicaldisk where VolumeSerialNumber^="XXXXXXXX" get deviceid 2^>nul') DO (
SET y=%%i
IF [%y%]==[] GOTO hdmissing
SET "backuphd=%%i"
GOTO endfor
)
:endfor
robocopy "C:\Users\Herbert\Documents" "%backuphd%\Backup\Documents" /MIR
ECHO Backup done
ECHO end
:hdmissing
ECHO Couldn't find external drive
:end
PAUSE
This way, the external HD is never detected (%y% is always an empty string). However, if I execute the script twice in the same console session, everything works as expected. But I want it to work at the first execution.
This is what I've tried so far:
Put SET y=dummy at the beginning of the script. The HD is always found, triggering a backup to C: if the HD is not actually connected (apparently SET y=%%i doesn't alter y?)
Change %y% to !y! - The HD is always found, again
Generation 3,576 of the delayed expansion problem, compounded by a contaminated environment.
There's no setlocal apparent, so y remains set in the environment after the first run - hence the 'later run characteristics different from first run' phenomenon.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
The key in your case appears to be no setlocal enabledelayeexpansion and !y! - because !y! is just that - a literal string !y! unless delayedexpansion is invoked by the setlocal command.
(having said that,
IF [%%i]==[] GOTO hdmissing
would work just as well, as would
SET "y=%%i"
IF not defined y GOTO hdmissing
because if [not] defined var operates on the run-time value of var. "quoting the set arguments" ensures that any stray trailing spaces on the line are not included in the value assigned
)
As Magoo already pointed out, !y! doesn't work, since I forgot to enable Delayed Expansion. However, enabling it requires you to escape certain characters, which seemed quite irritating and tedious to me. It could be possible to just enclose the command in the FOR-loop with double quotes, as done in the two edits of this question, but I found out after solving it differently.
What I did was moving the block
IF [%y%]==[] GOTO hdmissing
SET "backuphd=%%i"
after :endfor. This way, it's outside of the for loop and %y% gets expanded accordingly.
Mind that this solution works only because I need just one item, and because of the programming flow.
Other useful approaches might be found in this question.
However, if you want to do the same thing: easier (though not as robust) solutions without WMIC might include
Putting a file with a specific name on the hard drive and checking for it with IF EXIST before backing up, hoping the user will not delete it, and the HD is connected to the same drive letter.
Putting the batch script on the drive itself and work with relative path names (optional: put a shortcut on the Desktop and hope again the drive is always under the same letter)

Storing multi-word strings to a file

I've recently been trying to make a program to simply store text to a file for later viewing, storing it as a .rar file for security against those who don't understand how to extract the text from the .rar (i.e. the less "techy" people)...
I have, however, encountered an error in the program that results in the <word> not expected at this time followed by the .exe closing when I input add/<word> <word>... (i.e. any multi-word string with spaces in between the words [add/<word>, however, does function properly]).
Is there a special rule that must be followed for storing multi-word strings to a .rar or a file in general (I do, however, know that there is a rule for creating/renaming folders/directories)?
The Program Segment:
:command
cls
set /p journal=<journal.rar
echo %journal%
echo.
set /p command=What would you like to do?
cls
if %command%==exit exit
if %command%==help goto help
if %command%==delete echo START; > journal.rar
if %command:~0,4%==add/ echo "%journal%""%command:~4%;" > journal.rar
if %command:~0,5%==edit/ echo %journal:%command:~5%=%;" > journal.rar
goto command
Excuse me. Your question is not clear. There are several confusing points in it, like "followed by the .exe closing" (which .exe is closing?), and the fact that your question is NOT related to .rar files in any way, nor to "storing multi-word strings". However, I can see the following points in it:
When a variable value is expanded with percent signs this way: %command% you must be aware that the variable is first expanded and then the resulting line is parsed. This mean that the value of the variable may introduce errors in the line. For example, in this line: if %command%==exit exit, if the value of command variable is add/one two three, then the line that is parsed is this: if add/one two three==exit exit that, of course, issue an error! (type if /? for further details).
The way to avoid this problem is enclosing both the variable and the comparison value in quotes; this way, if the value have several words with spaces, the whole value is grouped in the IF command for comparison purposes: if "%command%" == "exit" exit. This must be done in every IF command that use the value of the variable.
In the following line:
if %command:~0,5%==edit/ echo %journal:%command:~5%=%;" > journal.rar
you must be aware that the line is parsed from left to right; this means that you can not nest a %variable% expansion inside another one. The way to solve this problem is first complete a %normal% variable expansion, and then a !delayed! variable expansion that will take the previous expanded value. To do that, insert this line at beginning of your program:
setlocal EnableDelayedExpansion
and change previous line by this one:
if "%command:~0,5%" == "edit/" echo !journal:%command:~5%=!;" > journal.rar
For further details, type set /? and carefully read the sections about "delayed expansion".
Here is a sample that can accept multiple words:
set "command="
set /p "command=What would you like to do? "
cls
if /i "%command%"=="have lunch" goto :food

To "Call" or "Not to Call" a batch file?

If from inside a bat file you called another batch file but still had a few remaining operations to complete, how can you make sure that the call to first bat file will after completion or error, will return to the file that called it in the first instance?
Example:
CD:\MyFolder\MyFiles
Mybatfile.bat
Copy afile toHere
or
CD:\MyFolder\MyFiles
CALL Mybatfile.bat
COPY afile toHere
What is the difference between using CALL or START or none of them at all? Would this have any impact on whether it would return for the results of the copy command or not?
As others have said, CALL is the normal way to call another bat file within a .bat and return to the caller.
However, all batch file processing will cease (control will not return to the caller) if the CALLed batch file has a fatal syntax error, or if the CALLed script terminates with EXIT without the /B option.
You can guarantee control will return to the caller (as long as the console window remains open of course) if you execute the 2nd script via the CMD command.
cmd /c "calledFile.bat"
But this has a limitation that the environment variables set by the called batch will not be preserved upon return.
I'm not aware of a good solution to guarantee return in all cases and preserve environment changes.
If you really need to preserve variables while using CMD, then you can have the "called" script write the variable changes to a temp file, and then have the caller read the temp file and re-establish the variables.
call is necessary for .bat or .cmd files, else the control will not return to the caller.
For exe files it isn't required.
Start isn't the same as call, it creates a new cmd.exe instance, so it can run a called batch file asynchronosly
The `CALL' statement was introduced in MS-DOS 3.3
It is used to call other batch files within a batch file, without aborting the execution of the calling batch file, and using the same environment for both batch files.
So in your case the solution is to use CALL
Okay, I actually didn't even really think about the fact that if you call a batch (regardless of the 'type', i.e. '.bat', or '.cmd') that it won't return if you don't use call.
I've been using call myself though for a different reason that I am actually pretty surprised that no one else has brought up. Maybe I missed it. MAYBE I'M THE ONLY ONE IN THE WORLD WHO KNOWS!! :O
Probably not, but I'm going to drop this knowledge off here because it's super useful.
If you use call you can use binary logic operators to decide how to proceed based on the ERRORLEVEL result. In fact, I always was flabbergasted on how && and || existed in DOS and COULDN'T be used this way. Well, that's why.
The easiest way to test this is to create a return.cmd with notepad, or from the command prompt like so:
c:\> type con >return.cmd
You will now notice the cursor goes down to the next line and hangs. Enter:
#exit /B %1
And then hit ENTER, and then CTRL-Z and that file will be created. Good! You may now feel free to try the following two examples:
call return.cmd 0 && echo Huzzah! A Complete Success! (Or cover up...)
call return.cmd 123 || echo Oops! Something happened. You can check ERRORLEVEL if you want the tinest amount of additional information possible.
So what? Well, run them again with the 0 and the 123 swapped and you should see that the messages DON'T print.
Maybe this multi-line example will make more sense. I use this all the time:
call return.cmd 0 && #(
echo Batch says it completed successfully^^!
) || #(
echo Batch completed, but returned a 'falsey' value of sort.
call echo The specific value returned was: %ERRORLEVEL%
)
(Note the 'call' in the || section before the second 'echo'. I believe this is how people got around not having delayed expansion back in the day. If you DO have delayed expansion enabled (via. setlocal EnableDelayedExpansion inside a batch OR launch a command prompt with cmd /v:on then you can just do !ERRORLEVEL!.)
... This is where I have to apologize and say if you have if ERRORLEVEL trauma in your past you should stop reading. I get it. Trust me. I thought about paying someone on fiverr to remotely type this for me, but for completeness sake I'm just going to take one for the team and mention that you can also do the following to check errorlevel:
if ERRORLEVEL 123 #echo QUICK! MOTHERS, COVER YOUR CHILDREN'S EYES! FINGERS ARE BEING UNDONE! :'(
If you've never typed that before then GOOD! You will live longer without having to read up why exactly you aren't getting the results you expect. Cruel is the word you're looking for, not 'quirky'.
The important part that I really want to get across however is that if you try this and DON'T use 'call' it will ALWAYS execute the 'true' branch. Try it for yourself!
If I'm missing something, or you know a better way to do this, please let me know. I love learning stuff like this!
Additional information I mentioned:
I have known for quite some time that you can put redirects BEFORE commands like so:
>nul echo. This won't be displayed!
But I accidentally discovered the other day by being a dumdum that you can apparently also do:
echo A B>file.txt C
And was REALLY surprised to find a file.txt which consisted of "A B C". It appears yo can place them ANYWHERE, even inside the command. I've never seen anyone do this, nor mention it, but I HAVE seen people mention that you can prefix a line with them.
Maybe it's a bug exclusive to Windows 10 or something. If you have another version and wanna try it out and let me know I'd be interested in what you find out.
Stay nerdy!

Batch Files third "If NOT" statement not reached

Hello stackoverflow community,
I am writing a batch file to do some automatic computer maintenance and have included several antivirus applications. For some reason, the third "if not" statement is never reached.
:AV
REM MSE
if not '%MSE%'=='' (
Echo Scanning for viruses using Microsoft Security Essentials.
Echo.
%MSE% -Scan -ScanType 1
Echo.
GOTO Defrag
)
REM AVG
if not '%AVG%'=='' (
Echo Scanning for viruses using AVG.
Echo.
%AVG% /COMP /QT /TRASH
Echo.
GOTO Defrag
)
REM NOD32
if not '%NOD32%'==''(
Echo Scanning for viruses using NOD32.
Echo.
if '%NOD32%'=='' GOTO NOD32NotFound
%NOD32% /aind /auto /log-file="%userprofile%\Desktop\Scan_Results.txt"
Echo.
GOTO Defrag
)
REM If all else fails...
GOTO AVNotFound
Currently, there are three blocks of codes, one for each antivirus program. Each block of code is executed only when the variable %AVG% %MSE% or %NOD32% is not empty, meaning they point to a valid file. I assign the variables using the code:
if exist "%programfiles(x86)%\AVG\AVG2012\avgscana.exe" set AVG="%programfiles(x86)%\AVG\AVG2012\avgscana.exe"
All three blocks of code works perfectly, nothing is wrong with the coding. The problem is that whatever the third block is, it never executes. So in the current example, the blocks of code go in the order of MSE, AVG, and NOD32. NOD32's block of code does not execute because it's the third block. Conversely, if I cut and paste the blocks into another order with AVG's block of code being the third block, it would not execute.
Any ideas?
Any suggestions?
Edited for clarification.
You're missing a space in the line:
if not '%NOD32%'==''(
Try:
if not '%NOD32%'=='' (
When I tried the script this line caused a failure. After changing the line, it worked.
Are the variables %MSE%, %AVG% or %NOD32% batch files? If yes, you will need to invoke them using "call" instead (for example call %AVG%)
If you call a batch file from another, the first one will exit after executing the 2nd one, unless it is called with "call".
Do any of your %AVG%, %NOD32%, or %MSE% variables have brackets in them? Could they be in the C:\Program Files (x86)\ path? A bracket will close the branch prematurely.
Place quotes around the executable part of the commands, for example:
"%MSE%" -Scan -ScanType 1

Resources