This batch script is supposed to detect removable drives, and loop though the following process for each drive: format, wrtie files to. Problem is that it only does this for one drive. For example, I can put two drives in, it will detect them both and process the loop on on of them two times. The issue has something to do with the variable of the drive name being updated, but I am not all that savy with batch script. If there is another answer already on this site, I wouldn't know what it looks like because I don't know everything about the commands (i have look at all of the suggestions given). I appologize if this is a repeat question. Just point me in the right direction and I will be on my way!
#echo off
setlocal enabledelayedexpansion
for /F "tokens=1*" %%a in ('fsutil fsinfo drives') do (
for %%c in (%%b) do (
echo Starting loop with %yyy%
for /F "tokens=3" %%d in ('fsutil fsinfo drivetype %%c') do (
if %%d equ Removable (
echo Drive written to is %yyy%
format /y %yyy:~0,2% /fs:FAT32 /v:LCM2014 /q
)
set yyy=%%c
echo Ending loop with %yyy%
)
)
)
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.
You are obviously having a problem with yyy from your posted code. Try replacing %yyy% with !yyy! and see the difference.
Related
This question already has an answer here:
Variables are not behaving as expected
(1 answer)
Closed 3 years ago.
I am writing simple script to check if somefolder exists in available drives.
however %%G always shows empty string when concat with ":\somefolder"
if I just echo %%G it shows all the available drives.
I am new to batch scripting , not sure what am I missing here.
Thanks in advance.
#echo off
::parse the VER command
FOR /F "tokens=*" %%G IN ('wmic logicaldisk get caption') DO (
IF [%%G]==[] (
echo "empty string"
) ELSE (
SET var="%%G\somefolderpath"
IF EXIST %var% (
echo %var% found
) ELSE (
echo %var% not found
)
)
)
It's normal practice to have a setlocal command directly after the initial #echo off. This discards any changes made to the environment when the batch ends, so variables established by one batch file do not affect any further batches that may be run in the same session.
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 (outside of the block or more usually after the initial #echo off) 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.
In your case, setting var is not required - if exist "%%G\somefolderpath" would suffice.
Note also that assigning quoted strings to variables make the variables hard to combine logically. Inserting quotes as needed is far simpler. The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
Here is another way to do it. The WMIC command is deprecated.
https://itknowledgeexchange.techtarget.com/powershell/wmic-deprecated/
As others have mentioned, setting and using a variable inside a FOR loop requires ENABLEDELAYED EXPANSION or CALL.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "delims=" %%G IN ('powershell -NoLogo -NoProfile -Command ^
"(Get-CIMInstance -ClassName Win32_LogicalDisk).DeviceID"') DO (
SET "VAR=%%G\somefolderpath"
IF EXIST "!VAR!" (
echo "!VAR!" found
) ELSE (
echo "!VAR!" not found
)
)
You can't set a variable and read it's contents in the loop unless you use delayed expansion or do a call echo.
Also, note that [%%G] will never = [] only a very specific scenario where you impropperly set up your For loop tokens so rhat they have ranges that overlap can create empty variolables.
In your loop it is not possible as you just select alll token, perfectly valid. For loops do not match lines that have no characters other than those that are considered delims (whitespace by default).
In any case,we can safely drop that portion.
Lets show some examples of how you might do this instead
To check directly:
#echo off
Set "_Dir=Some\Folder\Path"
FOR /F "tokens=*" %%G IN ('
wmic logicaldisk get caption
') DO (
IF EXIST "%%G\%_Dir%" (
ECHO."%%G\%_Dir%" Exists!
) ELSE (
ECHO."%%G\%_Dir%" Does Not Exist!
)
)
Set that varable and use it in the loop:
#(
SetLocal EnableDelayedExpansion
echo off
)
Set "_Dir=Some\Folder\Path"
FOR /F "tokens=*" %%G IN ('
wmic logicaldisk get caption
') DO (
SET "_Path=%%G\%_Dir%"
IF EXIST "!_Path!" (
ECHO."!_Path!" Exists!
) ELSE (
ECHO."!_Path!" Does Not Exist!
)
)
Notice that in order to use rhe variable in the loop we need to use ! instead of %
I have a batch with the following code:
SET CI=MySubDir
SET CIDIR=SomePath\..\..\%CI%
SET OutDir=MyOutDir
for /f %%G in ('dir /b %CIDIR%') do (
SET SCHEMADIR=%CIDIR%\%%G\schema
SET CATDIR=%CIDIR%\%%G\catalog
echo %%G
echo %SCHEMADIR%
echo %CATDIR%
if exist %SCHEMADIR% (
echo copy "%SCHEMADIR%" to "%OutDir%\..\Schema"
XCOPY /E /Y /I /Q /D %SCHEMADIR% "%OutDir%\..\Schema"
)
if exist %CATDIR% (
echo copy "%CATDIR%" to "%Outdir%\..\Catalog"
XCOPY /E /Y /I /Q /D %CATDIR% "%OutDir%\..\Catalog"
)
)
This program should copy all files within any sub-directory of SCHEMADIR or CATDIR to my OutDir (of course the files within my OutDir might be overridden several times depending on the existing ones within the source-directories).
When I echo the current file-name with echo %%G I get the sub-directory as wanted, curiously echoing either SCHEMADIR or CATDIR results in only the very last sub-directory found within CIDIR. So while %%Gresults in e.g. BE, SCHEMADIR results to SomePath\..\..\MySubDir\TH (where TH is the last sub-directory within MySubDir). What is whrong here?
In batch files, each line or block of lines (code inside parenthesis) is parsed, executed and the process repeated for the next line/block. During the parse phase, all variable reads are removed, being replaced with the value in the variable before the code starts to execute. If a variable changes its value inside the line/block, this changed value can not be retrieved from inside the same line/block as the variable read operation does not exist.
The usual way to solve it is to use delayed expansion. When enabled, you can change (where needed) the syntax from %var% to !var!, indicating to the parser that the read operation must be delayed until the command that uses the value starts to execute.
You can try this code to see it in action
#echo off
setlocal enabledelayedexpansion
set "test=initial value"
for %%a in (a b c) do (
rem Variable changed inside the block of code
set "test=%%a"
rem Retrieve data with normal expansion
echo Normal expansion: [%%a] [%test%]
rem Retrieve data with delayed expansion
echo Delayed expansion: [%%a] [!test!]
)
In your case, since SCHEMADIR and CATDIR are changed inside the block of code, to retrieve the changed value inside the same block you will need delayed expansion.
So you know , when people write code they do it neat, best look and most understandable. but Is there an optimizer for batch? Could someone make one?
example-
it takes existing variables and replaces them with the shortest possible variants
set whatever=whatever
echo question
set /p answer=:
if %answer%=%whatever% whatever
and turns it into
set a=whatever
echo question
set /p b=:
if %b%=%a% whatever
so it basically shortens the variables , flags (or tags or whatever like :top) and does other things I cannot thing of to basically optimize everything.
There are certain constructs in Batch programs that slow down the execution. The sole construct that have a major impact for this point is assemble a loop via a GOTO instead of any type of FOR command. If a large program with many GOTO's is rewritten with FOR commands, an important time saving is expected. Another aspect that affect this point is the number of commands/lines a program have, that is, a program that get the same result than another one with less lines, will run faster. The way to achieve the same things with less commands is making good use of Batch file capabilities.
For example, this code:
set /A b=a+8
set /A c=b*2
set /A d=c+e
... run slower than this one:
set /A b=a+8, c=b*2, d=c+e
This code:
command-that-return-errorlevel
if %errorlevel% equ 1 goto label-1
if %errorlevel% equ 2 goto label-2
if %errorlevel% equ 3 goto label-3
... run slower than this one:
command-that-return-errorlevel
for %%e in (1 2 3) do if %errorlevel% equ %%e goto label-%%e
... and previous one run slower than this one:
command-that-return-errorlevel
goto label-%errorlevel%
Shorten the variable names have a very little impact in the execution speed.
This way, the best option is to write Batch files using these techniques from the very beginning. There is no easy way to develop a program that read a Batch file and perform the previous changes, that is, replace GOTO's with FOR and "compress" several lines in less ones.
The program below is just for fun!
#echo off
setlocal EnableDelayedExpansion
rem Get a list of current variable names
(for /F "delims==" %%v in ('set') do (
echo %%v
)) > varnames.txt
rem Call the Batch file, so it creates its variables
call "%~1"
rem Create an array with new variable names
set "_numVars="
set "_nextVar="
< varnames.txt (for /F "delims==" %%v in ('set') do (
if not defined _nextVar set /P _nextVar=
if "%%v" neq "!_nextVar!" (
set /A _numVars+=1
set "var[!_numVars!]=%%v"
) else (
set "_nextVar="
)
))
del varnames.txt
rem Rename the variables in a new file
setlocal DisableDelayedExpansion
(for /F "delims=" %%a in ('findstr /N "^" "%~1"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:*:=!"
if defined line (
for /L %%i in (1,1,%_numVars%) do (
for /F "delims=" %%v in ("!var[%%i]!") do set "line=!line:%%v=v%%i!"
)
)
echo(!line!
endlocal
)) > "%~1.new"
This program get all variables from the Batch file given in the parameter and renames they as "v#" with a sequential number. To use this program you must remove any setlocal command from the Batch file before pass it to this program. Of course, this detail may cause several pitfalls: although the called program have Delayed Expansion already Enabled, there is no way to activate any Disable/Enable delayed expansion that the Batch file may require in order to correctly run. Besides, the program changes all strings in the Batch file that are equal to any variable name, but even if this point could be fixed (and this is a big IF, because it requires to emulate the cmd.exe Batch file parser!) this program would still be useless: as I said in my other answer, renaming the variables in a Batch file have a very little effect on its performance. I just wrote it for fun!
There are more than 10 html files with image tags. Every time we deploy our build onto test site we need to change the img source. for eg <img src=/live/Content/xyz.png />
to <img src=/test/Content/xyz.png />.
After looking around and reading for sometime, i have come up with the following batch script, however i cant figure out how do i go further from here :
for /r %%i in (*.html) do echo %%i
for %%f in (*.html) do (
FOR /F %%L IN (%%f) DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "x= <--------------------WHAT DO I SET HERE?
echo %x%
ENDLOCAL )) pause
This is my first batch script, could anyone please guide me in the right direction?
#ECHO OFF
SETLOCAL enabledelayedexpansion
for /r U:\ %%i in (*.html) do (
echo found %%i
SET outfile="%%~dpni.lmth"
(
SETLOCAL disabledelayedexpansion
FOR /F "usebackq delims=" %%L IN ("%%i") DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "line=!line:/live/=/test/!
echo !line!
ENDLOCAL
)
ENDLOCAL
)>!outfile!
)
pause
GOTO :EOF
How about this development?
Notes:
I've modified your FOR/R to ECHO the HTML file being processed and use %%i rather than switching to %%f. U: is my RAMDRIVE; you'd need to modify that to suit.
outfile is set to generate a filename which matches the HTML filename, but with a .lmth extension (can't update in-place) - it gets that from the ~dpn prefixing the i, which means the drive, path and name of the file %%i. It's quoted to take care of potential spaces in the filename or pathname.
The next logical statement is (for /f...[lines] )>!outfile! which sends any echoed text to a NEW file !outfile!. The enabledelayedexpansion in the second physical line of the batch makes !outfile! the RUN-TIME value - as it is changed within the FOR r outer loop.
Since the actual HTML filename in %%i may contain spaces, it needs to be quoted, hence the 'usebackq' clause in the FOR/F. The delims= clause ensures that the ENTIRE line from the file "%%i" is applied to %%L - not just the first token (well, actually, makes the entire line the first token).
The SET command substitutes the string "/test/" for any occurrence of "/live/" in the RUN-TIME value of the variable lineand assigns the result to line. The resultant value is then ECHOd - which is redirected to outfile
Note that in your original, you would be assigning x in the set x= but echo %x% would have reproduced x as it stood when the line was PARSED because batch substitutes the value of any variable for %var% as part of the parsing phase. Consequently, the line would have become simply ECHO (since x would likely be unassigned) and bizarrely would have reported the echo state (Echo is OFF)
A couple of gatchas here. First, % and some other characters are notoriously hard to process with batch, so be careful. Next, FOR/F will bypass empty lines. This can be overcome if required. Third, this will replace ANY occurrence of /live/ in any case with /test/
Good luck!
Edit to support exclamation marks: 20130711T0624Z
Added SETLOCAL enabledelayedexpansion line and ENDLOCAL just before )>!outfile! to match
I need to check for certain processes (its a list of 20ish processes, so wont list them all), and record which ones are running. Then I need to kill them, run some other code, and finally reopen them... The code will only run successfully if all the processes have been ended.
I'd welcome any suggestions, or just someone to explain why my code doesn't work.
What I've been doing, is using tasklist to check for each process, and write the results to a file, then I'm trying to remove the lines of the file where the process is not running (i.e. I get an "INFO:..." message)
I know that I have done something similar before, where I've taken each line of a file one at a time (in a for loop), replaced a string of text within it, and sent the edited line to another file (which is probably not the most efficient way of doing it, but it worked).
For some reason, I can't replicate it now...
The code I've got (whch is failing) is
SETLOCAL ENABLEDELAYEDEXPANSION
cd\
for /f "tokens=* delims= " %%a in (somefile.txt) do (
set q=INFO: No tasks are running which match the specified criteria.
set r=%%a
set s=!r:%q%=!
echo %s% >>test.txt
)
If anyone knows of a better way to do what I need, I'm happy to change my plan, but it does need to be done through batch (CMD), or I'd be happy with some one fixing the above code at least.
Thanks
Try this code. Though I'm not sure if the output file matches the one you want. Make sure to surround the parameter with double quotes if it contains any space.
taskchk.cmd:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
if "%1" == "" (
echo Please specify criteria.
echo e.g.: taskchk myprocess.exe
goto :eof
)
cd\
type nul>test.txt
set found=0
for /f "tokens=*" %%a in ('tasklist /nh') do (
for /f "tokens=1" %%b in ("%%a") do (
if /i "%%b" == "%~1" (
set found=1
) else (
echo %%a>>test.txt
)
)
)
if %found% == 0 echo INFO: No tasks are running which match the specified criteria.>>test.txt