I am trying to write a batch script that will loop through a list of file names and:
Attempt to create that file
Check if the creation was successful, and if not, try again after a few seconds
This is as far as I've gotten, which borrows from this question. However, it will only check and create file1.txt, not file2.txt or file3.txt. Then it will create a file "%A". Why is it only looping through the first file, and where is "%A" coming from?
I assume it is something to do with how I'm using the variable %A. Especially because if I manually type this out in the command prompt, instead of a batch file, and using %A instead of %%A, it works correctly.
FOR %%A IN ("file1.txt" "file2.txt" "file3txt") DO (
:Check
IF EXIST %%A GOTO Found
ECHO NotFound: %%A
echo.>%%A
timeout /t 5
GOTO Check
:Found
ECHO Found: %%A
)
You can accomplish this by using the CALL command to essentially work like a Function. When you use CALL the code execution will return to the original spot in the code it was called from and continue on.
#echo off
FOR %%A IN ("file1.txt" "file2.txt" "file3txt") DO (
CALL :CHECK "%%~A"
)
GOTO :EOF
REM Functions only below this line
:Check
IF EXIST "%~1" (
ECHO Found: %~1
) ELSE (
ECHO NotFound: %~1
timeout /t 5
GOTO Check
)
GOTO :EOF
Related
I'm feeling a bit constrained by batch's limitations and unsure the best way to code some logic. I have a hybrid Batch/VBScript for printing and decided the best way to confirm the VBScript had finished was by utilizing an external file as a placeholder.
The logic is like this:
Batch Script --> Create tmpfile --> Run VBScript
VBScript --> Print Job --> Delete File
That part works, but in my "call script" I have an array of files that I use a FOR LOOP to iterate over and print, but I can't figure out how to code the logic WHILE FILE EXISTS DON'T RUN NEXT PRINT JOB. I have figured out how to "loop" until the file doesn't exist, but this uses GoTo statements and they don't work inside FOR LOOPS. I tried using "EXIT /B" which I thought just exits a function, but it just closed the main script all together.
Here is my "call script" which needs some help~
::This is a placeholder file to check if VBScript is running
SET File="%TEMP%\jobrunning.txt"
del /F /Q %File%
::Print Each File in Array
for /l %%n in (0,1,%arrsize%) do (
SET FILEPATH="FILEPATH=!file[%%n]!
GoTo file_exists
)
::Is Job Still Running?
:file_exists
IF EXIST %File% (
GoTo :file_exists
) ELSE (
ECHO %PFUNC% %FILEPATH%
%PFUNC% %FILEPATH%
EXIT /B
)
The issue was using GoTo w/ EXIT /B. GoTo just jumps to a LINELABEL, and EXIT /B exits PROCESS IE: The Script. CALL opens a SUBPROCESS and EXIT /B returns to the main PROCESS allow further code execution. (It's the same as calling an external script and closing it).
This is what I ended up using to test and just deleted the file manually after each iteration of the array.
#ECHO OFF
setlocal enabledelayedexpansion
::This is a placeholder file to check if VBScript is running
SET File="%TEMP%\jobrunning"
del /F /Q %File%
::Set arrsize -1
set arrsize=4
set arrayline[0]=1A
set arrayline[1]=2A
set arrayline[2]=3A
set arrayline[3]=4A
set arrayline[4]=5A
::read it using a FOR /L statement
for /l %%n in (0,1,%arrsize%) do (
copy /y NUL %TEMP%\jobrunning.txt >NUL
TIMEOUT 2
CALL :file_exists
echo !arrayline[%%n]!
)
PAUSE
GoTo :EOF
:Is Job Still Running?
:file_exists
IF EXIST %File% (
GoTo :file_exists
) ELSE (
EXIT /B
)
I am trying to collect first file from the directory then process the file. But at the second time when the running and processing the batch file I am unable to store the values in the variable for the file name
Below is the sample code:
for /R C:\abcde_efghij\ab_abcabca %%i IN (*.*) DO (
set filename=%%i
set newname=%filename:~14%
set transname=%filename:~25%
goto tests
)
:tests
echo %filename%
echo %newname%
echo %transname%
I am sure we have to use something called SETLOCAL but I am unable to make it in the above code.
Any Help!
You should avoid percent expansion inside of blocks, also FOR blocks, as the expansion only occours only once when the block is parsed.
for /R C:\abcde_efghij\ab_abcabca %%i IN (*.*) DO (
set filename=%%i
goto :tests # Get only the first file
)
exit /b
:tests
set newname=%filename:~14%
set transname=%filename:~25%
echo %filename%
echo %newname%
echo %transname%
exit /b
As #Stephan noted, you could also use delayed expansion inside blocks.
setlocal EnableDelayedExpansion
for /R C:\abcde_efghij\ab_abcabca %%i IN (*.*) DO (
set filename=%%i
set newname=!filename:~14!
set transname=!filename:~25!
goto :tests # Get only the first file
)
I have been successfully using the CALL mechanism to allow one batch file to CALL another to setup environment variables. This code has been working well for over a year on Windows XP.
However, it does not appear to be working in the same way on Windows 7. The variables exist in the second batch file just before the EXIT /B statement. But, they do not exist upon the return to the first batch file.
Some trivial examples seem to work as expected, but the large batch scripts do not.
Has anyone had difficulties with this or know any workarounds?
In years of advanced batch scripting, I have never seen a CALL fail to preserve environment variables unless the called script (or label) set the variable when SETLOCAL was still active. There is an implicit ENDLOCAL for every active SETLOCAL from the within the CALL upon termination of the CALL.
It sounds like you have put in diagnostic messages prior to your EXIT /B to confirm that your variables are defined. I would take it one step further and add multiple ENDLOCAL statements prior to your diagnostic messages. I suspect you will then see your values dissapear prior to EXIT /B. You can add as many ENDLOCAL as you want. ENDLOCAL will never affect SETLOCAL that occurred prior to the CALL.
The most likely explanation is that either your script has somehow changed from XP to Win 7, or else there is some context change in your Win 7 environment that is exercising some aspect of the code that hadn't been exposed before.
Try this:
(
ENDLOCAL
SET "_Var1=Some Variable You want to exist"
SET "_Var2=Some Other Variable You want to exist"
EXIT /b 0
)
Also make sure you call Batch 2 from batch 1 like this:
CALL "\\PathToBatch2\Batch2.cmd"
ALTERNATELY you can do this:
CMD One:
REM Script: Batch1
#(
SETLOCAL
ECHO OFF
SET "_CallBatch2=C:\PathToBatch2\Batch2.cmd"
SET "_SetCmd=CALL :SetCMD "
SET "_RecievedVarList="
SET "_RecievedVar1=" & REM -- Note only done to show this is being created, normally you won't know or care what variables are being returned.
SET "_eLvL=0"
)
CALL :Main
(
ENDLOCAL
EXIT /b %_eLvl%
)
:Main
FOR %%A IN (CALL "%_CallBatch2%") DO (
IF /I "%%~A" EQU "SET" (
REM CALL %%A "%%~B" would work too
%_SetCmd% %%~B
) ELSE (
REM Looks like this was intended to be some output, show it.
ECHO.%%A %%B
)
)
FOR /F "Tokens=1*" %%A IN (%_RecievedVarList%) DO (
REM ECHO the Variable's name and it's contents:
CALL ECHO."%%~A" = "%%%%~A%%"
)
GOTO :EOF
:SetCMD
SET "%*"
FOR /F "Tokens=1 Delims==" %A IN ("%*") DO (
REM Store vars to output later to check their values.
SET "_RecievedVarList=%_RecievedVarList% "%A""
)
GOTO :EOF
.
CMD Two:
REM Script: Batch2
#(
SETLOCAL
ECHO OFF
)
CALL :Main
(
ENDLOCAL
EXIT /b %_eLvl%
)
:Main
ECHO.SET "_RecievedVar1=This is Recieved Var 1"
ECHO.SET "_RecievedVar2=This is Recieved Var 2"
GOTO :EOF
i am writing a batch script monotonic file renamer. basically, it makes the titles of all the files 1 2 3 4 .... and so on. i have since expanded it to be able to handle files of different types (txt, doc, flv, etc) but not everything is working out.
my main concern is i have broken the delayed expansion calls i was making before. now using !var1! is never expanded, or never recognized as a variable.
here is a verbosely commented version of my script
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET tempfile=temp.txt
SET exttemp=exttemp.txt
if [%1] == [] goto usage
::make sure your dont overwrite something useful
if EXIST %tempfile% (
ECHO Temp file already exists, are you sure you want to delete?
del /P %tempfile%
)
if EXIST %exttemp% (
ECHO EXT Temp file already exists, are you sure you want to delete?
del /P %exttemp%
)
::initialize
SET /a counter=0
SET type=
SET /a ender=%1
::write filenames to tempfile
DIR /B /ON > %tempfile%
::read lines one by one
for /f "usebackq delims=" %%a in (%tempfile%) do (
REM make sure we do not rename any of the working files
if NOT "%%a"=="renamer.bat" (
if NOT "%%a"=="temp.txt" (
if NOT "%%a"=="exttostr.bat" (
SET /a counter+=1
REM get file extension
exttostr %%a > %exttemp%
SET /P type= < %exttemp%
REM housekeeping
del /F %exttemp%
REM rename
ren %%a !counter!.!type!
ECHO Renamed "%%a" to "!counter!.!type!"
)))
REM exit when we have run enough
if "!counter!"=="!ender!" goto exit
)
goto exit
:usage
echo Usage: renamer NUMFILES
:exit
::final housekeeping
DEL temp.txt
the idea is i drop my two files, renamer.bat(this file) and exttostr.bat(helper to get the file extension) into the folder and run it, it will rename files sorted alphabetically from 1 to how ever many files i specify.
when i run the code, it never uses the variables marked for delayed expansion appropriately, always leaving them as "!varname!", so it renames the first file "!counter!.!type!" and throws errors for the rest because there is already a file in the directory with that name.
this brings me to a secondary issue. sorting the dir list alphabetically results in a poor handling of numbered files. for example the list:
"1 7 15 75 120"
is sorted:
"1 120 15 7 75"
i have not been able to find a way around this yet, only that it is indeed the intended result of the dir sort. the only workaround i have is padding numbers with enough zeroes in the front.
thanks in advance for any insight!
everything is sorted but the second problem. i think i have not spoken well. i have this issue when i take IN the directory file names, not when writing out. so they already need to be padded. i has hoping there was some other way to read the directory and have it be sorted appropriately.
the most promising thing i have found is here: http://www.dostips.com/DtCodeBatchFiles.php#Batch.SortTextWithNumbers
#ECHO OFF
if "%~1"=="/?" (
echo.Sorts text by handling first number in line as number not text
echo.
echo.%~n0 [n]
echo.
echo. n Specifies the character number, n, to
echo. begin each comparison. 3 indicates that
echo. each comparison should begin at the 3rd
echo. character in each line. Lines with fewer
echo. than n characters collate before other lines.
echo. By default comparisons start at the first
echo. character in each line.
echo.
echo.Description:
echo. 'abc10def3' is bigger than 'abc9def4' because
echo. first number in first string is 10
echo. first number in second string is 9
echo. whereas normal text compare returns
echo. 'abc10def3' smaller than 'abc9def4'
echo.
echo.Example:
echo. To sort a directory pipe the output of the dir
echo. command into %~n0 like this:
echo. dir /b^|%~n0
echo.
echo.Source: http://www.dostips.com
goto:EOF
)
if "%~1" NEQ "~" (
for /f "tokens=1,* delims=," %%a in ('"%~f0 ~ %*|sort"') do echo.%%b
goto:EOF
)
SETLOCAL ENABLEDELAYEDEXPANSION
set /a n=%~2+0
for /f "tokens=1,* delims=]" %%A in ('"find /n /v """') do (
set f=,%%B
(
set f0=!f:~0,%n%!
set f0=!f0:~1!
rem call call set f=,%%%%f:*%%f0%%=%%%%
set f=,!f:~%n%!
)
for /f "delims=1234567890" %%b in ("!f!") do (
set f1=%%b
set f1=!f1:~1!
call set f=0%%f:*%%b=%%
)
for /f "delims=abcdefghijklmnopqrstuwwxyzABCDEFGHIJKLMNOPQRSTUWWXYZ~`##$*_-+=:;',.?/\ " %%b in ("!f!") do (
set f2=00000000000000000000%%b
set f2=!f2:~-20!
call set f=%%f:*%%b=%%
)
echo.!f1!!f2!!f!,%%B
rem echo.-!f0!*!f1!*!f2!*!f!*%%a>&2
)
this code can sort the filenames with one number in them (i.e. video100.mov is fine, video100video10.mov would break it)
the issue i have is i think adding a call to this helper fn will break it again, so i will be trying to include this in my modified renamer.bat now. any help is appreciated.
Probably the batch for extracting the extension reset the local environment.
But, you don't need it. You may extract the extension with the ~x option. Something similar to this ....
:monotonicrename
set /a counter = 0
for %%a in (%1\*.*) do (
if exist %%~fa (
set /a counter += 1
echo ren %%~fa !counter!%%~xa
)
)
goto :eof
to include leading zeroes in the counter, so that the directory sorts correctly, replace the previous rename command with three lines
set zcounter=0000!counter!
set zcounter=!zcounter:~-4!
echo ren %%~fa !counter!%%~xa
So putting all pieces together, add the monotonicrename function you just created in the batch file that can be as simpler as...
#echo off
setlocal enabledelayedexpansion
call :monotonicrename %1
goto :eof
:monotonicrename
set /a counter = 0
for %%a in (%1\*.*) do (
if exist %%~fa (
set /a counter += 1
set zcounter=0000!counter!
set zcounter=!zcounter:~-4!
echo ren %%~fa !zcounter!%%~xa
)
)
goto :eof
I didn't experience any issues with delayed expansion, everything worked fine for me (except, of course, for the fact that I didn't have the exttostr.bat helper script.)
Anyway, there are several things that could be improved about your script:
You don't need to store the result of DIR into a file to read it afterwards. You can read the output directly in the FOR loop.
You don't need the helper batch script. The extension can be extracted from %%a by using the ~x modifier with the loop variable: %%~xa. You can read more about modifiers by issuing HELP FOR from the command prompt.
The renamer batch file's own name can be referenced in the script as %0. You can apply the ~n modifier where you only need to use the name without the extension. The combined modifier of ~nx will give you the name with the extension.
So, here's how your script might look like with the above issues addressed:
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
IF [%1] == [] GOTO usage
::initialize
SET /A counter=0
SET type=
SET /A ender=%1
::read lines one by one
FOR /F "usebackq delims=" %%a IN (`DIR /B /ON`) DO (
REM make sure we do not rename any of the working files
IF NOT "%%~a"=="%~nx0" (
SET /A counter+=1
RENAME "%%~a" "!counter!%%~xa"
ECHO Renamed "%%~a" to "!counter!%%~xa"
)
REM exit when we have run enough
IF "!counter!"=="!ender!" GOTO :EOF
)
GOTO :EOF
:usage
ECHO Usage: %~n0 NUMFILES
As for your secondary issue, it can be easily resolved like this:
Use something like 100000 as counter's initial value. (Use however many 0s you like, but possibly no more than nine.) Add the same value to ender as well.
When renaming files, instead of !counter! use the expression that removes the first character (the 1): !counter:~1! (in fact, this is not about removal, but about extracting a substring starting from the offset of 1, learn more about it with the HELP SET command).
Here's the modified version of the above script:
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
IF [%1] == [] GOTO usage
::initialize
SET /A counter=1000
SET type=
SET /A ender=%1
SET /A ender+=counter
::read lines one by one
FOR /F "usebackq delims=" %%a IN (`DIR /B /ON`) DO (
REM make sure we do not rename any of the working files
IF NOT "%%~a"=="%~nx0" (
SET /A counter+=1
RENAME "%%~a" "!counter:~1!%%~xa"
ECHO Renamed "%%~a" to "!counter:~1!%%~xa"
)
REM exit when we have run enough
IF "!counter!"=="!ender!" GOTO :EOF
)
GOTO :EOF
:usage
ECHO Usage: renamer NUMFILES
You can also see that I made some other enhancements, like making sure the file name is enclosed in double quotes, and using GOTO :EOF instead of GOTO exit (:EOF is a special pre-defined label that points at the end of the batch script so you don't need to define your own).
I am developing a batch file to collect websphere products information, it seems to work fine except for some cases.
For some reason under some circumstances versionInfo.bat -maintenancePackages is called but the following code (check for manageprofiles.bat), it seems like it's returning from the :check section after calling versionInfo.
My Windows batch writing skills are very rusty, other improvements are welcome.
#echo off
SetLocal EnableDelayedExpansion
set tmpfile=%TEMP%\tmpdone.txt
echo. > %tmpfile%
For /F "eol= delims=| tokens=13" %%a in (%windir%\vpd.properties) Do (
set check=%%a
call :check
)
goto eof
:check
Set skip=No
For /F "eol= delims=|" %%a in (%tmpfile%) Do (
if "%%a" == "%check%" set skip=YES
)
if %skip% == YES goto eof
echo %check%>>%tmpfile%
if exist "%check%\bin\versionInfo.bat" "%check%\bin\versionInfo.bat" -maintenancePackages
echo %check%\bin\manageprofiles.bat
if exist "%check%\bin\manageprofiles.bat" "%check%\bin\manageprofiles.bat" -listProfiles
goto eof
:del
echo Done
del %tmpfile%
:eof
You need to use call to run batch files from another batch file. Otherwise cmd won't return from the called one. So your code should read:
if exist "%check%\bin\versionInfo.bat" call "%check%\bin\versionInfo.bat" -maintenancePackages
echo %check%\bin\manageprofiles.bat
if exist "%check%\bin\manageprofiles.bat" call "%check%\bin\manageprofiles.bat" -listProfiles
goto :eof
(Also no need for a :eof jump label, you can just use the goto :eof special syntax to exit the batch file directly. I usually only use such a jump label if I need some cleanup to do first, but I name it differently then, to avoid confusion :-))