I'm having an issue with the batch script I wrote below. If I take out the "if" statement, it works, but doesn't print out the %size%, if I leave the "if" statement it gives me an error about "0 was unexpected at this time."
I really don't see any syntax errors here, and if I leave echo on, I see the variables getting set with the proper values. Ultimately I want this to restore files if it detects they're in a bad state, but I'm a little confused as to why the variables don't seem to be working properly.
#echo off
set folder="C:/Somedir/"
set backupfolder="C:/Backupdir/"
set minbytesize=0
for /R "%folder%" %%I in (*) do (
set size=%%~zI
set file=%%~nxI
echo %file% is %size%
if %size% EQU %minbytesize% (
REM do something
)
)
pause
the old delayed expansion pitfall:
#echo off
setlocal enableDelayedExpansion
set "folder=C:/Somedir/"
set "backupfolder=C:/Backupdir/"
set "minbytesize=0"
for /R "%folder%" %%I in (*) do (
set "size=%%~zI"
set "file=%%~nxI"
echo !file! is !size!
if !size! EQU !minbytesize! (
REM do something
)
)
pause
endlocal
more you can find here: ss64.com/nt/delayedexpansion.html
Related
I have a folder structure, which is like for example C:\Temp\ and there are a lot of folder and file, and within each folder there are a "callme.bat". I would like to create a so called main.bat which is one after another call the callme files within the main' window. But there is a problem, within the callme files are some echo which contains "!" mark what make a problem for me.
I realized the problem with the setlocal-endlocal combo, because the batch scrip wants to interpret the message within the "!" marks, so I must use endlocal, but if I did I not able to run the callme bats.
callme.bat
#echo off
echo !!! hidden message !!! not hidden message
pause
main.bat variant 1
#echo off
setlocal enabledelayedexpansion
set PATH=C:\Temp
for /F %%x in ('dir /B/A:D %PATH%') do (
set CURR_DIR=%PATH%\%%x
set ACTUAL_BATCH=!CURR_DIR!\callme.bat
echo !ACTUAL_BATCH!
call !ACTUAL_BATCH!
pause
)
pause
exit
main.bat variant 2
#echo off
set PATH=C:\Temp
for /F %%x in ('dir /B/A:D %PATH%') do (
setlocal enabledelayedexpansion
set CURR_DIR=%PATH%\%%x
set ACTUAL_BATCH=!CURR_DIR!\callme.bat
echo !ACTUAL_BATCH!
ENDLOCAL & SET VAR=!ACTUAL_BATCH!
echo %VAR%
pause
)
pause
exit
main.bat variant 3
#echo off
set PATH=C:\Temp
for /F %%x in ('dir /B/A:D %PATH%') do (
setlocal enabledelayedexpansion
set CURR_DIR=%PATH%\%%x
set ACTUAL_BATCH=!CURR_DIR!\callme.bat
echo !ACTUAL_BATCH!
REM source: https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal
for /f "delims=" %%A in (""!ACTUAL_BATCH!"") do endlocal & set "VAR=%%~A"
echo %VAR%
call %VAR%
pause
)
pause
exit
So I don't know what to do. Anyone has an idea?
variant 1's output:
C:\Temp\1\callme.bat
not hidden message
C:\Temp\2\callme.bat
not hidden message
variant 2-3's output:
C:\Temp\1\callme.bat
ECHO is off.
C:\Temp\2\callme.bat
ECHO is off.
TL;DR
ENDLOCAL&set "varname=%sourcevarname%"
probably, where varname is the variablename to set and sourcevarname is the variable whose value is to be assigned to varname - and they CAN be the same name, even if the statement appears logically null - it's exporting the variable from within the setlocal/endlocal block.
Key point: MUST be on one physical line and may be repeated if necessary (ie
ENDLOCAL&set "varname=%sourcevarname%"&set "varname2=%sourcevarname2%"
So
ENDLOCAL&set "fred=%fred%"&set "bill=%george%"
is perfectly valid, to set the value of fred outside the setlocal/endlocal bracket to its final value inside and of billoutside to the final value of george inside.
Some points about your code:
Never use PATH as a variable name, as it destroys the PATH variable for searching executable files.
Use the extended SET syntax set "varname=content" to avoid problems with trainling spaces.
You only need to disable the delayed expansion mode by using setlocal DisableDelayedExpansion
#echo off
setlocal EnableDelayedExpansion
set MY_PATH=C:\Temp
for /F %%x in ('dir /B/A:D %PATH%') do (
set "CURR_DIR=%MY_PATH%\%%x"
set "ACTUAL_BATCH=!CURR_DIR!\callme.bat"
call :execute ACTUAL_BATCH
pause
)
pause
exit /b
:execute ACTUAL_BATCH
set "batFile=!%~1!"
echo Calling !batFile!
setlocal DisableDelayedExpansion
call %batFile%
endlocal
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 need to make delayed expansion inside of delayed expansion, i.e. smth like:
!PARAMS[!BEFORE_LAST!]!
Of course, the above is not valid, so I tried to workaround it with for-loop, but with no success:
SETLOCAL EnableExtensions EnableDelayedExpansion
SET PARAM_COUNT=0
FOR %%P IN (%*) DO (
SET /A PARAM_COUNT+=1
SET PARAMS[!PARAM_COUNT!]=%%P
IF !PARAM_COUNT! GTR 1 (
SET /A BEFORE_LAST = !PARAM_COUNT!-1
FOR /L %%G IN (!BEFORE_LAST!) DO SET BEFORE_LAST_PARAM=!PARAMS[%%G]!
IF "!BEFORE_LAST_PARAM!"=="--buildroot" (
REM perfrom some actions here
)
)
)
ENDLOCAL
How could I achieve the explained behavior?
delayed expansion inside delayed expansion doesn't work, try this:
CALL SET "myvar=%%PARAMS[!BEFORE_LAST!]%%"
This is not so much of an answer, but using #Enduro's Answer, I solved my problem. I am just giving an example here, to see how to use it.
This answer was the only one that worked for me! After a LOT of searching; Basically I was trying to generate a file with all android app package names, without version codes. We have used a gradle script to generate names in a particular format also, something like: com.package.appname-releaseVersion.apk.
Here is the original code for generating file (apps.ls) with required package names, which I mainly use like this, inside android shell: for app in $(cat < apps.ls ); do monkey -p $app 1; done;. It just launches all those apps to make sure their services start running and registered.
My Original Script
#echo off
setlocal EnableDelayedExpansion
cd "C:\remoteUpdate"
>apps.ls (
for /r %%v in (*.apk) do (
set "name=%%~nv"
call :indexof "!name!" "-" idx
call echo %%name:~0,!!idx!!%%
)
)
C:\dos2unix\bin\dos2unix.exe apps.ls
#echo Done generating apps.ls
:indexof [%1 - string ; %2 - find index of ; %3 - if defined will store the result in variable with same name]
::http://ss64.org/viewtopic.php?id=1687
#echo off
setlocal enableDelayedExpansion
set "str=%~1"
set "s=!str:%~2=&rem.!"
set s=#%s%
if "%s%" equ "#%~1" endlocal& if "%~3" neq "" (set %~3=-1&exit /b 0) else (echo -1&exit /b 0)
set "len=0"
for %%A in (2187 729 243 81 27 9 3 1) do (
set /A mod=2*%%A
for %%Z in (!mod!) do (
if "!s:~%%Z,1!" neq "" (
set /a "len+=%%Z"
set "s=!s:~%%Z!"
) else (
if "!s:~%%A,1!" neq "" (
set /a "len+=%%A"
set "s=!s:~%%A!"
)
)
)
)
endlocal & if "%~3" neq "" (set %~3=%len%)
exit /b 0
Now the issue was that I needed to filter out certain packages out of my apps.ls file,
Changes
All I needed to do was change the code inside the for loop like so:
CALL SET "truncname=%%name:~0,!!idx!!%%"
if NOT "!truncname!"=="com.blhealthcare.package1" if NOT "!truncname!"=="com.android.package2" echo !truncname!
Everything I tried before that, using only set was not working. However using call with set worked really well. I cannot find any documentation on this as well, as it's difficult to even search this issue. What I noticed was that call echo %%name:~0,!!idx!!%% was printing correctly, whereas echo %%name:~0,!!idx!!%% was printing something like name:~0,28 and not working. I couldn't understand it then, but today I can understand from #Endoro's answer that
delayed expansion inside delayed expansion doesn't work
new to batch. I've narrowed down my problem to how batch variables are evaluated I think.
setlocal enableextensions enabledelayedexpansion
set timelimit=30
for /F "USEBACKQ tokens=4,6,8" %%a in (`systeminfo ^| qgrep -e "System Up Time:"`) do set /A timepassed=%%a*24*60+%%b*60+%%c
IF "!timepassed!" LEQ "%timelimit%" (
echo %timelimit%
) ELSE (
echo !timepassed!
)
When run, I expect the batch to output !timepassed! (1250 currently) however, it always outputs %timelimit% (30), leading me to believe that the LEQ IF is being taken, which makes no sense to me.
as suggested, try the following changes in your code
set the variable value using /A option
set /a timelimit=30
and compare the values with
IF !timepassed! LEQ !timelimit! (
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).