Here is what I want, inside the BACKUPDIR, I want to execute cscript /nologo c:\deletefile.vbs %BACKUPDIR% until number of files inside the folder is greater than 21(countfiles holds it).
Here is my code:
#echo off
SET BACKUPDIR=C:\test
for /f %%x in ('dir %BACKUPDIR% /b ^| find /v /c "::"') do set countfiles=%%x
for %countfiles% GTR 21 (
cscript /nologo c:\deletefile.vbs %BACKUPDIR%
set /a countfiles-=%countfiles%
)
set /a countfiles-=%countfiles%
This will set countfiles to 0. I think you want to decrease it by 1, so use this instead:
set /a countfiles-=1
I'm not sure if the for loop will work, better try something like this:
:loop
cscript /nologo c:\deletefile.vbs %BACKUPDIR%
set /a countfiles-=1
if %countfiles% GTR 21 goto loop
A while loop can be simulated in cmd.exe with:
:still_more_files
if %countfiles% leq 21 (
rem change countfile here
goto :still_more_files
)
For example, the following script:
#echo off
setlocal enableextensions enabledelayedexpansion
set /a "x = 0"
:more_to_process
if %x% leq 5 (
echo %x%
set /a "x = x + 1"
goto :more_to_process
)
endlocal
outputs:
0
1
2
3
4
5
For your particular case, I would start with the following. Your initial description was a little confusing. I'm assuming you want to delete files in that directory until there's 20 or less:
#echo off
set backupdir=c:\test
:more_files_to_process
for /f %%x in ('dir %backupdir% /b ^| find /v /c "::"') do set num=%%x
if %num% gtr 20 (
cscript /nologo c:\deletefile.vbs %backupdir%
goto :more_files_to_process
)
I have a trick for doing this!
I came up with this method because while on the CLI, it's not possible to use the methods provided in the other answers here and it had always bugged me.
I call this the "Do Until Break" or "Infinite" Loop:
Basic Example
FOR /L %L IN (0,0,1) DO #(
ECHO. Counter always 0, See "%L" = "0" - Waiting a split second&ping -n 1 127.0.0.1>NUL )
This is truly an infinite loop!
This is useful for monitoring something in a CMD window, and allows you to use CTRL+C to break it when you're done.
Want to Have a counter?
Either use SET /A OR You can modify the FOR /L Loop to do the counting and still be infinite (Note, BOTH of these methods have a 32bit integer overflow)
SET /A Method:
FOR /L %L IN (0,0,1) DO #(
SET /A "#+=1"&ECHO. L Still equals 0, See "%L = 0"! - Waiting a split second &ping -n 1 127.0.0.1>NUL )
Native FOR /L Counter:
FOR /L %L IN (-2147483648,1,2147483648) DO #(
ECHO.Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL )
Counting Sets of 4294967295 and Showing Current Value of L:
FOR /L %L IN (1,1,2147483648) DO #(
(
IF %L EQU 0 SET /A "#+=1">NUL
)&SET /A "#+=0"&ECHO. Sets of 4294967295 - Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL )
However, what if:
You're interested in reviewing the output of the monitor, and concerned that I will pass the 9999 line buffer limit before I check it after it has completed.
You'd like to take some additional actions once the Thing I am monitoring is finished.
For this, I determined how to use a couple methods to break the FOR Loop prematurely effectively turning it into a "DO WHILE" or "DO UNTIL" Loop, which is otherwise sorely lacking in CMD.
NOTE: Most of the time a loop will continue to iterate past the condition you checked for, often this is a wanted behavior, but not in our case.
Turn the "infinite Loop" into a "DO WHILE" / "DO UNTIL" Loop
UPDATE: Due to wanting to use this code in CMD Scripts (and have them persist!) as well as CLI, and on thinking if there might be a "more Correct" method to achieve this I recommend using the New method!
New Method (Can be used inside CMD Scripts without exiting the script):
FOR /F %%A IN ('
CMD /C "FOR /L %%L IN (0,1,2147483648) DO #( ECHO.%%L & IF /I %%L EQU 10 ( exit /b ) )"
') DO #(
ECHO %%~A
)
At CLI:
FOR /F %A IN ('
CMD /C "FOR /L %L IN (0,1,2147483648) DO #( ECHO.%L & IF /I %L EQU 10 ( exit /b ) )"
') DO #(
ECHO %~A
)
Original Method (Will work on CLI just fine, but will kill a script.)
FOR /L %L IN (0,1,2147483648) DO #(
ECHO.Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL&(
IF /I %L EQU 10 (
ECHO.Breaking the Loop! Because We have matched the condition!&DIR >&0
)
)
) 2>NUL
Older Post Stuff
Through chance I had hit upon some ways to exit loops prematurely that did not close the CMD prompt when trying to do other things which gave me this Idea.
NOTE:
While ECHO.>&3 >NUL had worked for me in some scenarios, I have played with this off and on over the years and found that DIR >&0 >NUL was much more consistent.
I am re-writing this answer from here forward to use that method instead as I recently found the old note to myself to use this method instead.
Edited Post with better Method Follows:
DIR >&0 >NUL
The >NUL is optional, I just prefer not to have it output the error.
I prefer to match inLine when possible, as you can see in this sanitized example of a Command I use to monitor LUN Migrations on our VNX.
for /l %L IN (0,0,1) DO #(
ECHO.& ECHO.===========================================& (
[VNX CMD] | FINDSTR /R /C:"Source LU Name" /C:"State:" /C:"Time " || DIR >&0 >NUL
) & Ping -n 10 1.1.1.1 -w 1000>NUL )
Also, I have another method I found in that note to myself which I just re-tested to confirm works just as well at the CLI as the other method.
Apparently, when I first posted here I posted an older iteration I was playing with instead of the two newer ones which work better:
In this method, we use EXIT /B to exit the For Loop, but we don't want to exit the CLI so we wrap it in a CMD session:
FOR /F %A IN ('CMD /C "FOR /L %L IN (0,1,10000000) DO #( ECHO.%L & IF /I %L EQU 10 ( exit /b ) )" ') DO #(ECHO %~A)
Because the loop itself happens in the CMD session, we can use EXIT /B to exit the iteration of the loop without losing our CMD Session, and without waiting for the loop to complete, much the same as with the other method.
I would go so far as to say that this method is likely the "intended" method for the sort of scenario where you want to break a for loop at the CLI, as using CMD session is also the only way to get Delayed expansion working at the CLI for your loops, and the behavior and such behavior is clearly an intended workflow to leave a CMD session.
IE: Microsoft clearly made an intentional effort to have CMD Exit /B For loops behave this way, while the "Intended" way of doing this, as my other method, relies on having accidentally created just the right error to kick you out of the loop without letting the loop finish processing, which I only happenstantially discovered, and seems to only reliably work when using the DIR command which is fairly strange.
So that said, I think it's probably a better practice to use Method 2:
FOR /F %A IN ('CMD /C "FOR /L %L IN (0,1,10000000) DO #( ECHO.%L & IF /I %L EQU 10 ( exit /b ) )" ') DO #(ECHO %~A)
Although I suspect Method 1 is going to be slightly faster:
FOR /L %L IN (0,1,10000000) DO #( ECHO.%L & IF /I %L EQU 10 ( DIR >&) >NUL ) )
And in either case, both should allow DO-While loops as you need for your purposes.
#echo off
set countfiles=10
:loop
set /a countfiles -= 1
echo hi
if %countfiles% GTR 0 goto loop
pause
on the first "set countfiles" the 10 you see is the amount it will loop
the echo hi is the thing you want to loop
...i'm 5 years late
It was very useful for me i have used in the following way to add user in active directory:
:: This file is used to automatically add list of user to activedirectory
:: First ask for username,pwd,dc details and run in loop
:: dsadd user cn=jai,cn=users,dc=mandrac,dc=com -pwd `1q`1q`1q`1q
#echo off
setlocal enableextensions enabledelayedexpansion
set /a "x = 1"
set /p lent="Enter how many Users you want to create : "
set /p Uname="Enter the user name which will be rotated with number ex:ram then ram1 ..etc : "
set /p DcName="Enter the DC name ex:mandrac : "
set /p Paswd="Enter the password you want to give to all the users : "
cls
:while1
if %x% leq %lent% (
dsadd user cn=%Uname%%x%,cn=users,dc=%DcName%,dc=com -pwd %Paswd%
echo User %Uname%%x% with DC %DcName% is created
set /a "x = x + 1"
goto :while1
)
endlocal
Related
I am working on a script which iterates over every file in a specific folder and reads some information from, and numbers each.
So I am running over the files with a for-loop and that is working correctly. Now I added a variable i which should increment on each iteration of the loop.
I used set /a i=0 and inside the for-loop set /a i+=1 and this Set command does print the number to console. My problem now is that the set command prints the number, but when I echo the number with echo %i% it will always print 0 and not the increasing value. I also tried echo !i! but that does not work at all. It just prints !i! in the console.
I also added a pause command to the end of the script, but that gets ignored entirely.
This is my batch script:
#echo off
setlocal EnableDelayedExpansion
set /a i=0
for /r %%n in (Links\*.lnk) do (
set /a i+=1
echo.
echo [Button!i!Back]
get.bat "%%n"
)
pause
This is an example of the output:
45
[Button!i!Back]
###HudIcons\VLC media player.ico
D:\Programme\VideoLAN\VLC\vlc.exe
I also just realized, that for the first time the loop runs, the !i! does work correctly and prints the number, but not afterwards.
I know that I should probably not be calling the other batch file like this, but that is temporary.
Any ideas why this is behaving so weird?
Perhaps it would be easier for you without the Set /A incrementing method, and therefore no need for delayed expansion. The alternative methodology could involve using findstr.exe to provide the counting:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
For /F "Tokens=1,* Delims=:" %%G In ('Dir /B /S /A:-D "Links\*.lnk" ^
2^> NUL ^| %SystemRoot%\System32\findstr.exe /EILN ".lnk"') Do (Echo=
Echo [Button%%GBack]
Call "get.bat" "%%H")
Pause
You use percentages symbol to call a variable %Variable%
and to echo it echo %variable%
to set one set variable=value Hope this helps you.
I want to create an automated testing array in Windows Command using a single code statement as the VALUE for each testing array record. Here is what the array definition looks like for the first two records in the $code_test[xx] array:
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (echo BUG OFF)
I want to execute the VALUE of each $code_test[xx] array record (which is a test code statement) using a for /l loop like this:
for /l %%g in (0,1,1) do (
echo $code_test[0%%g]
!$code_test[0%%g]!
echo.
)
The complete code is:
#echo off
setlocal enabledelayedexpansion
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (echo BUG OFF)
for /l %%g in (0,1,1) do (
echo $code_test[0%%g]
!$code_test[0%%g]!
echo.
)
echo.
pause
When I execute the complete code I get the following error message when the !$code_test[0%%g]! line of code is executed:
'if' is not recognized as an internal or external command, operable
program or batch file
I've read the excellent article here about how the IF command is parsed, but I don't see anything that jumps out at me as to why my code is failing. Is it even possible to do what I'm trying to accomplish?
Any Help Is Appreciated!
UPDATE:
I discovered that #sst is correct when I used the set command in the set $code_test[01]=if 5 lss 10 (set /a $var1+=1)
line of code:
#echo off
setlocal enabledelayedexpansion
set $var1=0
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (set /a $var1+=1)
for /l %%g in (0,1,1) do (
echo $code_test[0%%g]
cmd /c !$code_test[0%%g]!
echo $var1 = !$var1!
echo.
)
echo.
pause
The set /a $var1+=1 command didn't increment the value of $var1 as expected. In fact, it places a "1" in front of the $var1 label when the line of code echo $var1 = !$var1! executes. I haven't tried the solution offered by #Magoo yet. I'd really like to use the cmd /c solution instead of calling a subroutine. Is there anything I can do to make the set command work with the cmd /c option?
It can not work the way you tried, because the IF command is detected and handeled in phase 2.
But with delayed expansion it is too late.
This problem occurs only for the commands IF, FOR and REM.
´CALL %%$code_test%%` works in many cases, but fails also for the three commands and for code blocks.
It's unclear why it fails in that case, as the commands are parsed in phase 2 but they are not recognized.
The solution of #stephan uses a new cmd instance, it has the drawback, to lose all variable modifications every time the cmd instance finishes.
The solution of #Magoo uses therefore percent expansion, that should work in nearly all cases without problems.
#echo off
setlocal enabledelayedexpansion
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (echo BUG OFF)
for /l %%g in (0,1,1) do (
SET "$code_test=!$code_test[0%%g]!
CALL :test_code
echo.
)
echo.
GOTO :EOF
:test_code
%$code_test%
GOTO :eof
Another approach.
you seem to need another layer of parsing:
#echo off
setlocal enabledelayedexpansion
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (echo BUG OFF)
for /l %%g in (0,1,1) do (
echo $code_test[0%%g]
cmd /c !$code_test[0%%g]!
echo.
)
Edit (just for completeness):
found another way to do it (without a subroutine):
#echo off
setlocal enabledelayedexpansion
set $code_test[00]=if 5 lss 10 (echo IT WORKED)
set $code_test[01]=if 5 lss 10 (echo BUG OFF)
for /l %%g in (0,1,1) do (
echo $code_test[0%%g]
for /f "delims=" %%a in ('%%$code_test[0%%g]%%') do #set "result=%%a"
echo !result!
echo.
)
The trick is: for executes the ('...') part in a separate cmd process (as cmd /c ...) but captures the output into the main process.
Works both with !$code_test[0%%g]! and %%$code_test[0%%g]%%.
Although it doesn't work with if 5 lss 10 (set result=IT WORKED) because there is no output to capture (the set is only done with the do clause).
I have no experience with command prompt whatsoever, but I'd like to make a batch script (for fun and learn) that would print a text file from a given location, line by line, with a 1 second delay.
I would also want it to be able to pause/unpause when I press a designated key (ex: space) and feed me an extra line (on top of those already programmed to run) when I press another key (ex: enter).
I know I can add a 1 second delay by pinging localhost ping -n 5 127.0.0.1 > nul
And I know I can see the content of a text file using more text.txt, but I don't know how to iterate through an entire text file until EOF is met and I don't know how to pause/resume and feed extra line.
Hope it doesn't sound stupid or out of scope in this context, but it's just something that interests me right know and I know a lot people here have the knowledge to do this.
1) If you have experience in programming, you will know using a for loop is the most common way to do things one by one, e.g. line by line.
2) You can simply use ping localhost -n 2 >nul for 1 second delay, the 2 in the ping is not indicating 2 seconds, but 1 second instead. (I have no idea about that, just get used to it)
3) You can't pause/unpause when cmd is pinging, I mean there's no way to force the program to pause/unpause because the delay process is executed in just a line of code! Or you can magically add some code into it like ping localhost -n 2 pause while(KeyDown(SPACE)) >nul (just kidding :) )
4) Extra lines? Hmm... Remember batch is not a powerful language so... Yeah
Here is a simple code to print text line by line each second in a .txt file
for /f %%a in (your_text.txt) do (
echo %%a
ping localhost -n 2 >nul
)
You could do it synchronously with choice /t 1 (for a 1-second timeout) and some key other than Spacebar. Perhaps P for Pause?
#echo off
setlocal
set "textfile=notes.txt"
echo Hit P to pause / resume, Q to quit.
echo;
for /f "tokens=1* delims=:" %%I in ('findstr /n "^" "%textfile%"') do (
echo(%%J
choice /t 1 /c €pq /d € >NUL
if errorlevel 3 exit /b 0
if errorlevel 2 (
pause >NUL
ping -n 1 -w 750 169.254.1.1 >NUL
)
)
exit /b 0
Unfortunately, choice only allows a-z, A-Z, 0-9, and extended characters 128-254. There's no way to make it listen for Enter or Space. And choice is the only Windows command of which I'm aware that'll accept a single keypress and do something meaningful based on which key was pressed.
I think you'll have to use some sort of compiled language (or possibly PowerShell with a .NET class?) to listen for keypress events on the console. You could probably do it in JavaScript, but you'd have to display your output in a web browser or HTA window.
A "scrolling editor"? It is a crazy idea, isn't it? I LIKE IT! ;-) I adopted your project and add some points...
#echo off
rem ScrollEditor.bat: "dynamic" very simple line editor
rem Antonio Perez Ayala aka Aacini
if "%~1" neq "" if "%~1" neq "/?" goto begin
echo ScrollEditor.bat filename.ext
echo/
echo File lines will be continually scrolling, one per second.
echo/
echo You may pause the scroll via P key. In the "paused" state, the last displayed
echo line is named "current line", and the following commands are active:
echo/
echo #L Return/advance the listing to line #; continue the scroll from there.
echo [#]D Delete [from previous line # up to] current line.
echo I Insert lines after current line; end insert with *two* empty lines.
echo P End "paused" state; continue the scroll from current line on.
echo E End edit and save file, keep original file with .bak extension.
echo Q Quit edit, not save file.
goto :EOF
:begin
if not exist %1 echo File not found & goto :EOF
rem Load file lines into "line" array
set /P "=Loading file... " < NUL
setlocal DisableDelayedExpansion
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" %1') do (
set "line[%%a]=%%b"
set "lastLine=%%a"
)
echo last line: %lastLine%
echo To pause scrolling, press: P
echo/
setlocal EnableDelayedExpansion
set "validCommands=LDIPEQ"
set currentLine=1
:command-P End "paused" state
:ScrollLine
if %currentLine% gtr %lastLine% (
set "currentLine=%lastLine%"
echo EOF
goto GetCommand
)
set "num= %currentLine%"
echo %num:~-4%: !line[%currentLine%]!
set /A currentLine+=1
choice /C PC /N /T 1 /D C >NUL
if errorlevel 2 goto ScrollLine
rem Enter paused state
set /A currentLine-=1
:GetCommand
echo/
set /P "command=Command [#L,#D,I,P,E,Q]? "
set "letter=%command:~-1%"
if "!validCommands:%letter%=!" equ "%validCommands%" goto GetCommand
goto command-%letter%
:command-L Go to line #; continue scrolling
set "currentLine=%command:~0,-1%"
goto ScrollLine
:command-D Delete from line # to current line
set "prevLine=%command:~0,-1%"
if not defined prevLine set "prevLine=%currentLine%"
rem Move lines after last deleted one into deleted lines
set /A currentLine+=1, newCurrent=prevLine-1, lines=currentLine-prevLine
for /L %%j in (%currentLine%,1,%lastLine%) do (
set "line[!prevLine!]=!line[%%j]!"
set /A prevLine+=1
)
set /A currentLine=newCurrent, lastLine=prevLine-1
if %currentLine% equ 0 set "currentLine=1"
echo %lines% line(s) deleted (current=%currentLine%, last=%lastLine%)
goto GetCommand
:command-I Insert lines after current one
echo End insert with *two* empty lines
echo/
rem Read new lines into "ins" array
set "newLine=%currentLine%"
:insertLine
set "line="
set /A newLine+=1
set "num= %newLine%"
set /P "line=+%num:~-3%: "
set "ins[%newLine%]=!line!"
rem The most complex part: end in two empty lines...
if not defined line (
set /A newLine+=1
set "num= !newLine!"
set /P "line=+!num:~-3!: "
if defined line (
set "ins[!newLine!]=!line!"
) else (
set /A newLine-=2
)
)
if defined line goto insertLine
rem Move old lines to new place to make room for new lines
set /A lines=newLine-currentLine, currentLine+=1, newLast=lastLine+lines
for /L %%j in (%lastLine%,-1,%currentLine%) do (
set "line[!newLast!]=!line[%%j]!"
set /A newLast-=1
)
rem Insert new lines in old place
for /L %%j in (%currentLine%,1,%newLine%) do set "line[%%j]=!ins[%%j]!"
set /A lastLine+=lines, currentLine=newLine
echo %lines% line(s) inserted (current=%currentLine%, last=%lastLine%)
goto GetCommand
:command-E End edit, save file
echo Saving file...
move /Y %1 "%~N1.bak"
(for /L %%i in (1,1,%lastLine%) do echo(!line[%%i]!) > %1
:command-Q Quit edit
echo End edit
This program have multiple problems: don't check for valid input in commands, may have problems with special Batch characters and if the first character of a line is a colon, eliminate it. However, it is a good starting point for this project!
Perhaps you may be interested in this similar project.
I'm having an issue and I have been working on it for hours. I have no idea why it wont work, but I have a feeling it has to do with doing a for loop in a for loop. Its going to be a little difficult to explain. So here goes:
I have this code that works. What it does is copy 3 files from programdata to appdata. We need the "for loop" because the profile %var% is always different in that folder. It will find the name and input it to "cd" into.
Echo Restore Firefox Files
set copycmd=/y
timeout 7
c:
cd "%userprofile%\AppData\Roaming\Mozilla\Firefox\"
for /f "tokens=1,2 delims=/" %%i in ('findstr /l ".default" profiles.ini') do call set var1=%%j
cd profiles\%var1%
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\places.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\places.sqlite"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\key3.db" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\key3.db"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\signons.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\signons.sqlite"
The point is, that code works. It copys from where i want it to. Here's my issue. I have this code. It will not work. Note the main loop starts where it says "Echo Loop through each user and copy files".
Echo Set Usernames into Variables (User1, User2, etc)
for /F "tokens=*" %%A in (C:\ProgramData\TLCloud\WindowsUsers.txt) do (
SET /A vidx=!vidx! + 1
set user!vidx!=%%A
set /A i = !i! + 1
)
Echo Put Usernames into an Array
set i=0
for /f "delims=" %%a in (C:\ProgramData\TLCloud\WindowsUsers.txt) do (
set /A i+=1
set list[!i!]=%%a
)
Echo Loop Through Each User and Copy Files
for /F "tokens=2 delims==" %%s in ('set list[') do (
If NOT "%%s" == "Guest" (
If NOT "%%s" == "Administrator" (
Echo Restore Firefox Files
set copycmd=/y
::start "browser" /d "C:\Program Files (x86)\Mozilla Firefox" firefox.exe
timeout 7
c:
cd "%userprofile%\AppData\Roaming\Mozilla\Firefox\"
for /f "tokens=1,2 delims=/" %%i in ('findstr /l ".default" profiles.ini') do call set var1=%%j
cd profiles\%var1%
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\places.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\places.sqlite"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\key3.db" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\key3.db"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\signons.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%var1%\signons.sqlite"
)
)
)
Why will this code not work? Ignore the variables and array code, that is there so instead of "Username" I can eventually use "%%s" as the variable for changing usernames. But there is no reason this should not work, or am I not allowed to have a for loop in another for loop?
Any help is greatly appreciated.
%var1% is replaced by an empty string already on starting processing the most outer loop with set list[
One solution would be using !var1! instead of %var1% as it looks like delayed environment variable expansion is already enabled. In this case call set var1=%%j should be also corrected to just set var1=%%j.
Another solution is using following code:
Echo Set Usernames into Variables (User1, User2, etc)
for /F "tokens=*" %%A in (C:\ProgramData\TLCloud\WindowsUsers.txt) do (
SET /A vidx=!vidx! + 1
set user!vidx!=%%A
set /A i = !i! + 1
)
Echo Put Usernames into an Array
set i=0
for /f "delims=" %%a in (C:\ProgramData\TLCloud\WindowsUsers.txt) do (
set /A i+=1
set list[!i!]=%%a
)
Echo Loop Through Each User and Copy Files
for /F "tokens=2 delims==" %%s in ('set list[') do (
If NOT "%%s" == "Guest" (
If NOT "%%s" == "Administrator" (
Echo Restore Firefox Files
set copycmd=/y
rem start "browser" /d "C:\Program Files (x86)\Mozilla Firefox" firefox.exe
rem timeout 7
cd /D "%userprofile%\AppData\Roaming\Mozilla\Firefox\"
for /f "tokens=1,2 delims=/" %%i in ('findstr /l ".default" profiles.ini') do (
cd "profiles\%%j"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\places.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\places.sqlite"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\key3.db" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\key3.db"
if exist "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\signons.sqlite" copy "C:\Programdata\TLCloud\BrowserBackup\Username\MozillaRoaming\Firefox\Profiles\%%j\signons.sqlite"
)
)
)
)
Two problems.
The :: style commenting is a broken label. It causes FOR to get confused and terminate. Replace it with REM within a block statement (a parenthesised series of statements).
Second is more major, but easily cured.
You haven't shown us your entire batch. It would follow from such statement as set list[!i!]=%%a that you have delayed expansion turned ON. This is excruciatingly important.
Within a block statement, any %var% is evaluated and replaced at parse time, so %var1% will be replaced by var1's value when the entire block (ie the outermost FOR is parsed. Since you are attempting to change var1 within your innermost loop, to access the changed value, you need to use !var1! which means the current, not the parse-time value of the variable.
Now, since delayed expansion appears to be enabled, simply replacing %var1% with !var1! would appear to be a solution. It's not hard to notice however that var1 is being asigned the value %%j so there appears to be no reason to use var1 at all - simply replace it with %%j!
I have a situation very similar to the one described in this question (but in batch, not shell). I made a simple batch script to iterate through the lines of a tile and download data from a server using a python script (the process itself is more complicated than just a simple download, it has to authenticate with an API and fetch several URLs).
The first version was as follows:
for /F "tokens=*" %%A in (client_name_list.txt) do python download_metadata.py "%%A"
The way it is it waits until each iteration is done to move on, so I updated it to the following:
for /F "tokens=*" %%A in (client_name_list.txt) do start cmd /C python download_metadata.py "%%A"
The second versions does what I want to but, as the file client_name_list.txt is about 30,000 lines long, a lot of command prompts start spawning and the computers freezes within seconds.
How do I limit the number of running instances of CMD (to, for example 10) and make the script wait until there is a "free CMD slot" to go the next line?
Adapted from my answer to "Parallel execution of shell processes". Follow the link to get an explanation.
#echo off
setlocal enableDelayedExpansion
:: Display the output of each process if the /O option is used
:: else ignore the output of each process
if /i "%~1" equ "/O" (
set "lockHandle=1"
set "showOutput=1"
) else (
set "lockHandle=1^>nul 9"
set "showOutput="
)
:: Define the maximum number of parallel processes to run.
set "maxProc=10"
:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
set "lock="
for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
set "lock=%%T"
goto :break
)
:break
set "lock=%temp%\lock%lock%_%random%_"
:: Initialize the counters
set /a "startCount=0, endCount=0"
:: Clear any existing end flags
for /l %%N in (1 1 %maxProc%) do set "endProc%%N="
:: Launch the commands in a loop
set launch=1
for /f "delims=" %%A in (client_name_list.txt) do (
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set cmd!nextProc!=%%A
if defined showOutput echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting %%A
2>nul del %lock%!nextProc!
%= Redirect the lock handle to the lock file. The CMD process will =%
%= maintain an exclusive lock on the lock file until the process ends. =%
start /b "" cmd /c %lockHandle%^>"%lock%!nextProc!" 2^>^&1 python download_metadata.py "%%A"
)
set "launch="
:wait
:: Wait for procs to finish in a loop
:: If still launching then return as soon as a proc ends
:: else wait for all procs to finish
:: redirect stderr to null to suppress any error message if redirection
:: within the loop fails.
for /l %%N in (1 1 %startCount%) do 2>nul (
%= Redirect an unused file handle to the lock file. If the process is =%
%= still running then redirection will fail and the IF body will not run =%
if not defined endProc%%N if exist "%lock%%%N" 9>>"%lock%%%N" (
%= Made it inside the IF body so the process must have finished =%
if defined showOutput echo ===============================================================================
echo !time! - proc%%N: finished !cmd%%N!
if defined showOutput type "%lock%%%N"
if defined launch (
set nextProc=%%N
exit /b
)
set /a "endCount+=1, endProc%%N=1"
)
)
if %endCount% lss %startCount% (
1>nul 2>nul ping /n 2 ::1
goto :wait
)
2>nul del %lock%*
if defined showOutput echo ===============================================================================
echo Done
In each iteration of your for loop you can count the number of CMD task open. If the value is lesser than the limit you start a new task else you wait until one slot is free.
#echo off
set $Limit=11
setlocal enabledelayedexpansion
for /F "tokens=*" %%A in (client_name_list.txt) do (call:wait %%A)
exit/b
:wait
set "$cmd="
for /f %%a in ('tasklist ^| findstr /i "cmd"') do set /a $cmd+=1
if !$cmd! lss %$Limit% (
start cmd /C python download_metadata.py "%1"
goto:eof)
ping localhost -n 2 >nul
goto:wait