I am trying to create a script that will create another batch script. However this code will not run properly, immediately exiting as soon as the if statement is evaluated.
set yes=yes
pause
IF /I %yes% == yes (
ECHO REM Music>>mf.bat
ECHO FOR /f %%i in (C:\CopyToRoot\MusicFileAndLocation.txt) do set MusicFile=%%i>>mf.bat
)`
However if the second line in the if statement is removed the code executes without issue.
What am I doing wrong? is there something I am missing about the echo statement?
Perhaps:
#Echo Off
Set "yes=yes"
Pause
If /I "%yes%"=="yes" (
>"mf.bat" (
Echo #Echo Off
Echo Rem Music
Echo For /F "UseBackQ Delims=" %%%%A In ("C:\CopyToRoot\MusicFileAndLocation.txt"^) Do Set "MusicFile=%%%%A"
)
)
Double the percents and escape any internal closing parentheses.
You need to format the code block with a bit of effort, however, it is much simpler to get rid of the block and simply call a label. Also, the batch file will consume the % so you need to double them in order to redirect the string correctly to file.
#echo off
set yes=yes
pause
IF /I "%yes%"=="yes" call :final
goto :eof
:final
echo REM Music>mf.bat
echo FOR /f %%%%i in (C:\CopyToRoot\MusicFileAndLocation.txt) do set MusicFile=%%%%i>>mf.bat
Note I use single redirect on the first rem line to overwrite the file, else it will append each time you run the code, if that was the intention, simply do double redirect >>
Related
I have written below script:
#echo off
setlocal EnableDelayedExpansion
REM Collect source filenames from C:\Files and load into C:\doc.txt
dir C:\sources\Sourcefiles /b /a-d > C:\sourcefilenames.txt
REM fetch count of source files and store into variable count
For /F %%I in ('call C:\count.bat ') Do Set count=%%I
REM loop "count" number of times and echo temp.txt value
FOR /L %%A IN (1,1,%count%) DO (
REM call line.bat to fetch line 1,line 2 and so on of sourcefilenames.txt for each loop
call line.bat %%A>C:\temp.txt
set /p var=<C:\temp.txt
echo var:%var% ----------> returns previous run value
type C:\temp.txt ----------. returns current value of temp.txt
)
Basically what i am trying to do out of the above script is:
I am creating a variable(var) from the content of temp.txt(data in temp.txt will change for each time loop runs) to be used in multiple loops.
But the problem i am facing is :
Echo var is:%var% command returning me previous run value not temp.txt current content.whereas command "type C:\temp.txt" returning me temp.txt current content.
(Note: if i have called/created variable "var" from some other script it returns me that previous value else it returns Null)
Your help/guidance on above issue is really appreciated.
Thanks
I suppose the variable remains in memory, without being re-read.Attempts to limit the validity of the variable.
setlocal
echo something.....
endlocal
or #echo off & setlocal
When CMD.exe encounters a block of code in parentheses, it reads and parses the entire block before executing. This can cause unintuitive behavior. In this case, your echo var:%var% line is being parsed once at the beginning of the loop and never again.
The easiest fix for this is to change that line to
echo var:!var!
The !var! syntax is parsed every time through the loop. This works because you have enabledelayedexpansion set in your script.
Another workaround to this type of problem is to remove the parentheses and instead call out to a subroutine.
FOR /L %%A IN (1,1,%count%) DO call :loopLineBat %%A
... rest of script
exit /b
:loopLineBat
>%temp%\temp.txt call line.bat %1
<%temp%\temp.txt set /p var=
echo var:%var%
type %temp%\temp.txt
exit /b
This loop does the same as above, but because it is not in a parenthesized block, all of the lines are parsed and executed in order.
So, I've been trying to create a simple spinning line thing that goes on for a set number of loops. I've encountered a problem: I can't find a way to add to a variable, or have a loop counter. This is my code so far (Other general criticisms are accepted too: I'm new to this and it all helps.)
#echo off
:1
echo
echo
echo
echo -
echo
cls
echo
echo
echo /
echo
cls
echo
echo
echo I
echo
cls
echo
echo
echo \
echo
cls
echo
echo
echo -
echo
IF %timer%
goto 1
pause
Really sorry if it's already been asked; I just can't seem to find what I'm looking for. Also, it's very possible this could just be a simple command, in which case i apologise again.
There's a couple of errors with your code as it stands.
The major one is
IF %timer%
goto 1
Batch is a ver old-fashioned language and is very particular about syntax. You can get a syntax description by typing
if /? |more
at the prompt. (replace if with the keyword you desire)
if requires
if string1==string2 thingtodoiftrue
Over the years, the syntax has been expanded, still maintaining the old version for compatibility so that the general form is now
if string1==string2 (thingtodoiftrue) else (thingtodoiffalse)
where == may now be a number of operators (equ, neq, ...); you can add a /i switch to make the strings case-insensitive, or a not keyword to reverse the sense. The parentheses are optional if the thingtodo is a single statement.
There are some quirky syntax requirements however. Either thingtodoiftrue or ( must be on the same physical line as the if keyword. The sequence ) else ( must all be on one physical line (if it's used)
As for performing some variety of count using your original structure, there are many ways. Here's one:
#echo off
set count=15
:1
....
set /a count=count-1
IF %count% neq 0 goto 1
pause
This may be what you are looking for. This runs the code 10000 times and it could be modified according to your need.
#echo off
for /l %%i in (1,1,10000) do (
echo -
cls
echo /
cls
echo I
cls
echo \
cls
)
Cheers, G
#echo off
setlocal enableextensions disabledelayedexpansion
rem Get a 0x13 character (carriage return) char inside a variable
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
rem Spin the line
setlocal enabledelayedexpansion
for /l %%a in (1 1 5000) do for %%b in (- \ ^| /) do (
set /a "pct=%%a / 50"
<nul set /p ".=%%b !pct!%% !cr!"
)
endlocal
The basic idea is to output the spinning element, followed by the percentage. To keep all the output in the same line
<nul set /p "var=outputtext"
is used. This echoes the indicated text to console, waits for input, that is readed from nul, ending the prompt wait and continues without moving the cursor to the next line. There is no line feed
Now, it is necessary overwrite this line with the new information each time. To do it, it is necessary to get a carriage return character into a variable. For it, the for /f is used, retrieving the needed carriage return character from the output of the copy command.
note: the disabledelayedexpansion is necessary in this step to avoid problems if the full path/filename of the batch file (%~f0) contains any exclamation character.
The remaining code just iterates painting the corresponding character in the list, calculating the percentage to output and printing all to the console, without the line feed (<nul set /p ...) but with an aditional carriage return that moves the cursor to the start of the line. So, the next output will overwrite the previous one
note: in this case, enabledelayedexpansion is needed to get access to the content of the changed percentage variable and to output the carriage return. Without delayed expansion active, the parser removes the carriage return from the output.
I'm writing a little program but I have a problem with an if else statement.
#echo off
set /p points=
echo %points%
echo 1x5=
set /p 15=
if %15%==5 (
echo well done!
set /a points=%points%+1
) else ( echo wrong! )
pause
echo %points%
pause
Even if I fill in a wrong answer, it still ads 1 point to my points and says "Well done!"
(BTW: got some problems with inputting the code, don't now if you will be able to read it)
When an argument is included in the command line when calling the batch file, it is referenced from code as %1, %2, ... for the first, second, ... parameters.
In your code, when you write if %15%==5 ..., %15% is interpreted as first parameter to batch file (%1) followed by a 5 and a non escaped non paired percent sign that is discarded by the parser. So, if the batch file has no input arguments and %1 is empty, your if code is executed as if 5==5 ...
As a general recomendation, variables in batch files "should" never start with a number, as they will be considered as input arguments.
#echo off
setlocal enableDelayedExpansion
set /p points=
echo %points%
echo 1x5=
set /p 15=
if !15!==5 (
echo well done!
set /a points=!points!+1
) else ( echo wrong! )
pause
echo !points!
endlocal
pause
MC MD explained why your code does not work.Here's a way to make it work.
I have a DATA file, which holds database-connection info in the following format (with | as delimiter):
DatabaseServerIp1|UserName1|Password1|DatabaseName1
DatabaseServerIp2|UserName2|Password2|DatabaseName2
DatabaseServerIp3|UserName3|Password3|DatabaseName3
And I have a batchfile which reads the contents of this file and uses the contents to execute a script on each of the databases in this file. This script works great in most cases, but runs into issues when one of the variables contains a special character like % or #.
I read online that I'm supposed to use "SETLOCAL EnableDelayedExpansion" and surround the variables with exclamation marks, but I can't get this to work. Some other posts also mentionned using quotes instead of exclamation marks, but I couldn't get this to work either.
I would like to avoid changing the contents of the DATA file (I don't want to add escape characters inside the DATA file).
The script in which I'd like to use this is:
rem ... more code here ...
FOR /F "eol== delims=| tokens=1,2,3,4" %%i IN (%DATABASEDATAFILE%) DO CALL :_ACTION %%i %%j %%k %%l
GOTO :_END
:_ACTION
IF "%1"=="" GOTO :EOF
SET IPSERVER=%1
SET USERNAME=%2
SET PASSWORD=%3
SET DATABASE=%4
sqlcmd -S%IPSERVER% -U%USERNAME% -P%PASSWORD% -d%DATABASE% -i%SCRIPTFILE% -o%RESULTFILE%
GOTO EOF
:_END
rem ... more code here ...
:EOF
How do I make this code handle special characters correctly?
Example: %-character in the password field.
As you might have guessed, I'm not the original creator of this batch file or the data file.
The most of your suggestions (delayed expansion, quotes) are correct, but the order is important.
setlocal DisableDelayedExpansion
FOR /F "eol== delims=| tokens=1,2,3,4" %%i IN (%DATABASEDATAFILE%) DO (
SET "IPSERVER=%%i
SET "USERNAME=%%j"
SET "PASSWORD=%%k"
SET "DATABASE=%%l"
CALL :ACTION
)
goto :EOF
:ACTION
setlocal EnableDelayedExpansion
sqlcmd -S!IPSERVER! -U!USERNAME! -P!PASSWORD! -d!DATABASE! -i!SCRIPTFILE! -o!RESULTFILE!
endlocal
goto :EOF
#ECHO OFF
SETLOCAL
FOR /f "tokens=1-4delims=|" %%a IN (%DATABASEDATAFILE%) DO ECHO sqlcmd -S%%a -U%%b -P%%c -d%%d -i%SCRIPTFILE% -o%RESULTFILE%
GOTO :EOF
should do the trick. I've just ECHOed the SQL line produced - after verification, you'd need to change ECHO sqlcmd to sqlcmd to execute the sqlcmd.
In a DOS Batch File subroutine, how can I turn off echo within the subroutine, but before returning, put it back to what it was before (either on or off)?
For example, if there was a command called echo restore, I would use it like this:
echo on
... do stuff with echoing ...
call :mySub
... continue to do stuff with echoing ...
exit /b
:mySub
#echo off
... do stuff with no echoing ...
echo restore
goto :EOF
My first attempt was an utter failure - thanks jeb for pointing out the errors. For those that are interested, the original answer is available in the edit history.
Aacini has a good solution if you don't mind putting your subroutine in a separate file.
Here is a solution that works without the need of a 2nd batch file. And it actually works this time! :)
(Edit 2 - optimized code as per jeb's suggestion in comment)
:mysub
::Silently get the echo state and turn echo off
#(
setlocal
call :getEchoState echoState
echo off
)
::Do whatever
set return=returnValue
::Restore the echo state, pass the return value across endlocal, and return
(
endlocal
echo %echoState%
set return=%return%
exit /b
)
:getEchoState echoStateVar
#setlocal
#set file=%time%
#set file="%temp%\getEchoState%file::=_%_%random%.tmp"
#(
for %%A in (dummy) do rem
) >%file%
#for %%A in (%file%) do #(
endlocal
if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
del %file%
exit /b
)
If you are willing to put up with the slight risk of two processes simultaneously trying to access the same file, the :getEchoState routine can be simplified without the need of SETLOCAL or a temp variable.
:getEchoState echoStateVar
#(
for %%A in (dummy) do rem
) >"%temp%\getEchoState.tmp"
#for %%A in ("%temp%\getEchoState.tmp") do #(
if %%~zA equ 0 (set %~1=OFF) else set %~1=ON
del "%temp%\getEchoState.tmp"
exit /b
)
The simplest way is to not turn echo off in the first place.
Instead, do what you currently do with the echo off line to the rest of your subroutine - prefix all commands in the subroutine with an # sign. This has the effect of turning off echo for that command, but keeps the echo state for future commands.
If you use commands that execute other commands, like IF or DO, you will also need to prefix the "subcommand" with an # to keep them from being printed when echo is otherwise on.
The easiest way is to extract the subroutine to another .bat file and call it via CMD /C instead of CALL this way:
echo on
... do stuff with echoing ...
cmd /C mySub
... continue to do stuff with echoing ...
exit /b
mySub.bat:
#echo off
... do stuff with no echoing ...
exit /b
This way the echo status will be automatically restored to the value it had when the CMD /C was executed; the only drawback of this method is a slightly slower execution...
Here is a straight forward solution that relies on a single temporary file (using %random% to avoid race conditions). It works and is at least localization resistant, i.e., it works for the two known cases stated by #JoelFan and #jeb.
#set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
#set __ME_echo=OFF
#echo > "%__ME_tempfile%"
#type "%__ME_tempfile%" | #"%SystemRoot%\System32\findstr" /i /r " [(]*on[)]*\.$" > nul
#if "%ERRORLEVEL%"=="0" (set __ME_echo=ON)
#erase "%__ME_tempfile%" > nul
#::echo __ME_echo=%__ME_echo%
#echo off
...
endlocal & echo %__ME_echo%
#goto :EOF
Add this preliminary code to increase the solution's robustness (although the odd's are high that it's not necessary):
#:: define TEMP path
#if NOT DEFINED temp ( #set "temp=%tmp%" )
#if NOT EXIST "%temp%" ( #set "temp=%tmp%" )
#if NOT EXIST "%temp%" ( #set "temp=%LocalAppData%\Temp" )
#if NOT EXIST "%temp%" ( #exit /b -1 )
:__ME_find_tempfile
#set __ME_tempfile=%temp%\%~nx0.echo-state.%random%.%random%.txt
#if EXIST "%__ME_tempfile%" ( goto :__ME_find_tempfile )
I wasn't really happy with the solution above specially because of the language issue and I found a very simple one just by comparing the result from current echo setting with the result when explicitly set OFF. This is how it works:
:: SaveEchoSetting
:: :::::::::::::::::::::::::::
:: Store current result
#echo> %temp%\SEScur.tmp
:: Store result when explicitly set OFF
#echo off
#echo> %temp%\SESoff.tmp
:: If results do not match, it must have been ON ... else it was already OFF
#for /f "tokens=*" %%r in (%temp%\SEScur.tmp) do (
#find "%%r" %temp%\SESoff.tmp > nul
#if errorlevel 1 (
#echo #echo on > %temp%\SESfix.bat
) else (
#echo #echo off > %temp%\SESfix.bat
)
)
::
:: Other code comes here
:: Do whatever you want with echo setting ...
::
:: Restore echo setting
#call %temp%\SESfix.bat
I was looking for the same solution to the same problem, and after reading your comments I had an idea (which is not the answer to the question, but for my problem is even better).
I wasn't satisfied with the cmd.exe /c mysub.cmd because it makes hard or even impossible to return variables (I didn't check) - (couldn't comment because it's the first time I post here :)
Instead noticed that all we want -in the end- is to suppress stdout:
echo on
rem call "mysub.cmd" >nul
call :mysub >nul
echo %mysub_return_value%
GOTO :eof
:mysub
setlocal
set mysub_return_value="ApplePie"
endlocal & set mysub_return_value=%mysub_return_value%
GOTO :eof
It works fine with labelled subroutines, with subroutines contained in .cmd files, and I suppose it would work fine even with the cmd.exe /c variant (or start).
It also has the plus that we can keep or discard the stderr, replacing >nul with >nul 2>&1
I note that ss64.com scares kids like me stating that with call "Redirection with & | <> also does not work as expected".
This simple test works as expected. He must have been thinking of more complex situations.