Escaping FOR Loop in Batch - batch-file

I've got a batch script to utilize a command for an application to create some backups. I've got a loop setup to run through and fetch folder names and then to run the backup operation utilizing those folder names as file names. My loop runs great but my batch script just closes when it is finished. I'd like to escape the loop so I can go back to a choices location I have specified. I've seemingly been unable to escape this loop properly. My code is below:
set /p work=Folder Location:
set /p staging=Location of staging-backup file:
set /p backup=Location to save backup:
#Echo off
md %backup%
setlocal enabledelayedexpansion
set /a $count=1
cd /d %staging%
for /f "delims=" %%a in ('dir "%work%" /b/o') do (
staging-backup "%%a" "%backup%\%%a"
set /a $Count+=1
)
Again, this all runs great (though I can't get it to log properly but thats another battle for later) except I'd love to GOTO CHOICE at the end. When I add it after the closing ) part of the loop, it still closes. When I bring it inside the loop, it closes. Do I need to wrap this loop in an if else to escape it properly?
I tried pulling the code out and pushing it to it's own bat then calling that bat with my original but no change.
:THREE
echo.
echo.
.\Loops.bat
echo.
echo.
GOTO CHOICE
This portion calls it, runs the new bat but still closes the cmd window when it is finished. Any ideas?

#Echo off
setlocal enabledelayedexpansion
:AGAIN
for %%a in (work staging backup) do set "%%a="
set /p "work=Folder Location: "
if not defined work goto :eof
set /p "staging=Location of staging-backup file: "
set /p "backup=Location to save backup: "
md %backup%
set /a $count=1
PUSHD %staging%
for /f "delims=" %%a in ('dir "%work%" /b/A-D') do (
staging-backup "%%a" "%backup%\%%a"
set /a $Count+=1
)
POPD
GOTO AGAIN
Here's a revision.
The first two lines are moved to the top to turn off command-echoing prevent the variables remaining set for each run.
Then insert a label, and clear the variables.
If you reply just Enter to the work prompt, the batch terminates as work will be undefined (set /p leaves the variable unchanged under these circumstances - it does not clear the variable)
PUSHD a directory to switch temporarily to a different directory.
execute staging-backup in that directory. Note that if staging-backup is a batch, then you should use CALL staging-backup... (actually, you could do that with any executable - if it's a batch, you must do it so that cmd knows where to come back to at the end of the destination batch (in this case, staging-backup.bat)
When the for loop is finished, POPD to return to the original directory and loop back to :AGAIN to well - do it all again.

Related

Batch file to read folders in a share and append each folder name to the end of a path

I'm trying to parallelise a script im using to backup some files using TSM.
Basically I run the following commands:
net use y: \\Server\share
dsmc incr y: -optfile="c:\path\folder\optionfile.opt" > c:\logs\logfile
.. and TSM backus up the entire directory tree in serial.
But I want to do this to create some seperate scripts to use more than one client to backup subsets of the directory tree (to speed things up)
FOR each subdirectory in \\Server\share
IF not processed 1/3 of the directories in \\server\share
DO
net use y: \\Server\share\**<append subdirectory here>**
Backup y
This is what I've got so far:
FOR /D %%a IN (W:) DO (
net use y: \\server\share\%%a
run command to backup y:
(y:\ should be now \server\share\first_dir_name)
net use y: /delete
timeout /t 10
)
I still need the if logic to count the number of folders in the share.
The first step was to find how many top level sub directories are in the share:
I use dir /a:d /s /b "Folder Path" | find /c ":\" but the command never completes.
Hope that makes sense!
Thanks.
You could do something like this using semaphore files.
In this example I have it set up for 8 separate tasks.
I recommend that you copy this code and test to see how it works and then change the number of scripts you want to run simultaneously or figure out the optimum number to run simultaneously and change the code to keep that number of scripts running until done. You didn't say how many separate scripts you will have.
SeparateProcess.bat
#echo off
setlocal EnableDelayedExpansion
set "Script[1]=Script1"
set "Script[2]=Script2"
set "Script[3]=Script3"
set "Script[4]=Script4"
set "Script[5]=Script5"
set "Script[6]=Script6"
set "Script[7]=Script7"
set "Script[8]=Script8"
set /a nScripts=8
rem For test only. Change values (seconds) below if desired
set /A testDelay[1]=10
set /A testDelay[2]=4
set /A testDelay[3]=8
set /A testDelay[4]=14
set /A testDelay[5]=17
set /A testDelay[6]=6
set /A testDelay[7]=9
set /A testDelay[8]=7
rem time to wait between checking status of all tasks
set /A timeout=5
set /A tasksProcessing=0
rem Set all Status to 0 and delete semaphore files.
for /L %%L in (1,1,%nScripts%) do set /A Status[%%L]=0 & del /f /q "SemaphoreFile!Script[%%L].txt!" 2>nul
echo This program will simulate starting separate processes
echo Open a Windows Explorer to %CD% and observe that semaphore files appear.
echo Observe that the files disappear when the processes complete after varying delays.
pause
echo Starting %nScripts% processes
for /L %%L in (1,1,%nScripts%) do (
echo( >"SemaphoreFile!Script[%%L]!.txt"
start /MIN cmd.exe /C !Script[%%L]!.bat "SemaphoreFile!Script[%%L]!.txt" !testDelay[%%L]!
set /A Status[%%L]=1
set /A tasksProcessing+=1
)
:KeepChecking
for /L %%A in (1,1,%nScripts%) do (
echo Waiting %timeout% seconds for !tasksProcessing! processes to complete
timeout %timeout% >nul
for /L %%L in (1,1,%nScripts%) do (
if !Status[%%L]!==1 if not exist "SemaphoreFile!Script[%%L]!.txt" (
set /A Status[%%L]=0
set /A tasksProcessing-=1
echo !Script[%%L]! finished.
)
)
if !tasksProcessing! GTR 0 goto :KeepChecking
goto :Done
)
:done
echo All processes completed.
pause
endlocal
Then have 8 scripts that are identical (for this test) where n is 1-8.
Scriptn.bat (code below)
#echo off
REM 1=Filespec of semaphor file
REM 2=Delay in seconds. This is for test only.
echo %~nx1 is doing something
timeout %~2
del "%~1"
got :eof
Execute SeparateProcess.bat to see how it works. When you are satisfied with the results replace the code in Script1-8 with whatever you need them to do. Just make sure that the last thing they do is delete the semaphore file to indicate that they are done.

Trying use a batch file to delete filenames appended with numbers

I"m trying to delete these files with numbers appended to them using a for loop in a Windows batch file.
My problem is that I can't seem to build the filename strings using the for loop, despite several attempts at doing this.
Below is the snippet of my code that I"m trying to run with. As you can see, I've got 4 files named file0.txt, file1.txt, file2.txt and file3.txt in some nested folder, and I'm trying to delete them using a for loop
Ideally I want to be able to be able to set the limit of the for loop depending on how many files there are. And then I'd want to change the file extension from .txt to whatever to delete other files except the ones I want to keep.
Any help would be appreciated!! Here's the code:
setlocal enabledelayedexpansion
set num=3
set /a forLoopLimit=%num%-1
cd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
setlocal enabledelayedexpansion
echo %x
set y=%x
set filename=file%y%.txt
del %filename% /f /q
echo %filename%
)
cd ..
setlocal enabledelayedexpansion
set num=3
set /a forLoopLimit=%num%-1
cd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
echo %%x
set filename=file%y%.txt
ECHO del file%%x /f /q
echo file%%x
)
cd ..
Problems:
To refer to the metavariable (Loop-control variable) x, you need %%x, not %x within a batch file.
setlocal is not a switch. Each time it is used, it establishes a new frame which is terminated by endlocal or reaching end-of-file.
If you are using delayededexpansion then you need to refer to the variable you are changing using !var!, not %var%. !var! means the changed value, %var% means the value of the variable as it was when the for keyword was encountered.
You don't need to use y in your application. %%x is actually a string, but it will be a numeric string, so it can be used in calculations.
The del command is simply echoed above to allow the command to be displayed - in case there's a code error which might delete unexpectedly.
Basically, you fell into the delayed expansion trap (you enabled delayed expansion, but you didn't use it).
But your task can be done without those variables that need delayed expansion. You can use %%x directly and define the rest outside the loop:
#echo off
set num=3
set /a forLoopLimit=%num%-1
set "filebase=file"
pushd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
ECHO del "%filebase%%%x.txt" /f /q
)
popd
Like Magoo, I disarmed the del command by just echoing it. If the output is what you expect, just remove the ECHO.

Batch For Loop not separating string at specified delimiter

I am using a program called "Easy Context Menu" which allows me to create customized context menus when you right click in File Explorer. I wanted to create my own Context Menu item that allows me to copy a file and it's parenting folder structure into another directory, excluding all of the files in the parented folders.
Example:
target file path: C:\the\big\foo\bar.txt
destination path: D:\cow
I want the file 'bar.txt' and it's parenting folders up until 'big' to be copied into the directory 'cow' without the other files in 'big' or 'foo'. The end result should look like:
D:\cow\big\foo\bar.txt
'big' should only contain 'foo' and 'foo' should only contain 'bar.txt'. The program allows me to send the file as a parameter to a file of my choice, in this case a batch file.
I have gotten stuck at the for loop in the ':main" subroutine. It will only print the whole path of the selected file and ignores the delimiter. 'countA' is returned as '1' after the loop and it would have printed the whole path minus drive letter. I do not understand why the for loop is ignoring the delimiter and not separating the path into separate folder names. The reason I am tying to do this is so the user can choose at which point the parented folders should be copied over. This is my first time writing actual code in batch so I still don't completely understand 'setlocal' and a few other things.
I have tried changing the tokens and I am able to choose the individual sections in between the slashes, but I am never able to capture the entire string as separate chunks. I have also changed the delimiter and got the same issue.
#echo off
setlocal
set _target=""
set _dest=""
if not exist %1 goto:error_no_path
set _target=%~pnx1
echo %_target%
goto :dest_selct
:dest_selct
echo select destination folder
pause
set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose destination folder.',0,0).self.path""
for /f "usebackq delims=" %%I in (`powershell %psCommand%`) do set "folder=%%I"
setlocal enabledelayedexpansion
echo You chose %folder%
choice /c ync /n /m "Is this correct? ([Y]es, [N]o, [C]ancel)"
if %ERRORLEVEL% EQU 3 goto:eof
if %ERRORLEVEL% EQU 2 goto:dest_selct
if %ERRORLEVEL% EQU 1 (set _dest="%folder%")&&(goto:main)
:error_no_path
ECHO No file path/name
pause
GOTO:eof
:main
echo main
set _countA=0
echo count starts at %_countA%
for /f "tokens=* delims=\" %%F in ("%_target%") do (
echo token %_countA% is %%F
echo count is %_countA%
set var!_countA!=%%F
set /a countA+=1
)
echo var1: %var1%
echo count is now %countA%
pause
I should have more than one variable at the end, each being the name of a folder that parents the target file and the count should be however many parent folders there are, plus the file itself. What I am actually getting is one variable and it contains the whole target path still.
The for loop does not delimit as you have tokens=*,
which gets all tokens as one token. Leading delimiters are stripped.
Only var0 has a value, not the echoed var1.
To split _target by path segments, use:
for %%F in ("%_target:\=" "%") do echo %%~F
Another issue you have is if you choose n at the choice prompt,
the goto:dest_selct will cause setlocal enabledelayedexpansion
to execute again. Since you are not using endlocal, then the
local environment is recursing without ending. Sometimes endlocal
is implied, so you may not need to use it, though not in this case.
Try this fix:
#echo off
setlocal
set "_target="
set "_dest="
if not exist "%~1" goto:error_no_path
set "_target=%~pnx1"
echo %_target%
goto :dest_selct
:dest_selct
echo select destination folder
pause
set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose destination folder.',0,0).self.path""
for /f "usebackq delims=" %%I in (`powershell %psCommand%`) do set "folder=%%I"
echo You chose %folder%
choice /c ync /n /m "Is this correct? ([Y]es, [N]o, [C]ancel)"
if %ERRORLEVEL% EQU 3 goto:eof
if %ERRORLEVEL% EQU 2 goto:dest_selct
if %ERRORLEVEL% EQU 1 (set "_dest=%folder%") && goto:main
:error_no_path
ECHO No file path\name
pause
GOTO:eof
:main
setlocal enabledelayedexpansion
echo main
set "countA=0"
echo count starts at %countA%
rem Remove leading backslash if exist.
if "%_target:~,1%" == "\" set "_target=%_target:~1%"
rem Split into path segments.
for %%F in ("%_target:\=" "%") do (
echo token !countA! is %%~F
echo count is !countA!
set "var!countA!=%%~F"
set /a countA+=1
)
echo var1: %var1%
rem Echo all variables starting with var and their values.
set var
echo count is now %countA%
endlocal
pause
Adjusted double quoting.
Changed instances of variable name of _countA to countA to match.
set var to show all variables starting with var for debugging.
for loop now spilts on path segments.
Moved setlocal enabledelayedexpansion out of label loop.
Fix 1st argument check if not defined.
Refer to set /? about variable substitution i.e. "%_target:\=" "%", which replaces \ with " ".

Batch File System cannot find file after Loop

I am creating a batch file to open remote Computer Management console by taking User ID as input and computer name from 2nd column from file data.csv. it works fine on first attempt. When it goes back to :start label. and ask for other input. it gives error. System cannot find file ./data.csv
My code is
:start
set /p Input="Enter User-ID:"
for /f "usebackq tokens=1-4 delims=," %%a in (".\data.csv") do (
if %input% ==%%a ("cmd /c Start /B /wait compmgmt.msc –a /computer=%%b")
)
cls
GOTO start
Good practice to use %~dp0 for paths in batch files (instead of relative paths like .) that way if the current working folder changes the file will always be located.
So change to %~dp0data.csv
:start
set /p Input="Enter User-ID:"
PUSHD
for /f "usebackq tokens=1-4 delims=," %%a in (".\data.csv") do (
if %input% ==%%a ("cmd /c Start /B /wait compmgmt.msc –a /computer=%%b")
)
POPD
cls
GOTO start
should restore sanity, pushing the directory then restoring it before the next cycle.

Batch script, file drag&drop, change extension and reload file

I have a batch script which accepts >=1 files via drag & drop. The full paths are kept in an array and later fed to another program as input. The file name+extension is kept in another array which is then shown to the user.
I am trying to check the file extension and if it is not .bin, automatically rename the file I dragged & dropped to .bin and reload it to be added to the two arrays. How can I do that? I have tried some if commands or %%~xi but usually it doesn't properly detect if it's .bin or not and of course the path is not updated at the array.
#echo off
pushd %~dp0
setlocal enabledelayedexpansion
set /a Count = 0
:START
set file="%~1"
if %file%=="" goto RUN
for %%i in (%file%) do set name=%%~nxi
set /a Count += 1
set [FilePath.!Count!]=%file%
set [FileName.!Count!]=%name%
shift
goto START
:RUN
for /l %%i in (1, 1, %Count%) do (
program.exe -command "![FilePath.%%i]!"
echo.
echo File Name: ![FileName.%%i]!
echo.
pause
cls
if exist file.log del file.log
)
You could just rename it during your array population. There are a few other potential problems with your script (such as pushd to an unquoted directory, delayedexpansion where you don't need it where it will potentially clobber filenames containing exclamation marks, and if %file%=="" should have "%file%" quoted). And there's really no reason to maintain an array of filenames or loop twice. Your script is much more complicated than it needs to be.
#echo off
setlocal
pushd "%~dp0"
for %%I in (%*) do (
if /I not "%%~xI"==".bin" move "%%~fI" "%%~dpnI.bin" >NUL
program.exe -command "%%~dpnI.bin"
echo;
echo File Name: "%%~nxI"
echo;
rem If you want to rename the file back the way it was after running
rem program.exe on it, uncomment the following line:
rem if /I not "%%~xI"==".bin" move "%%~dpnI.bin" "%%~fI" >NUL
pause
cls
if exist file.log del file.log
)

Resources