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
Related
The idea is I want to check if a substring is inside of a string with Batch script.
For example I want to check if pass is inside of %rev%. If no for loop is related, I can do:
if /I "%rev:pass=%" neq "%rev%" (
echo String has pass
) else (
echo it doesnt has pass
)
But I don't know how to use this "%rev:pass=%" in side of a for loop when "%rev%" is replaced by %%g
My code is here:
FOR /F "tokens=*" %%g IN (test.txt) do (
if /I "%%g:pass=" neq "%%g" (
echo String has pass
) else (
echo it doesnt has pass
)
)
I need to check each string inside of this test.txt if it has substring pass. Any help is appreciated.
I try to change the form of %%g but still cannot make it happen. The main idea is to check each string in the file, if the string has pass as a sub-string
You can't manipulate for loop variables directly, so you'll need to store them in regular variables first. However, since you can't ordinarily set and use variables inside of sets, you'll need to enable delayed expansion. This can be done by adding setlocal enabledelayedexpansion to the top of your script.
#echo off
setlocal enabledelayedexpansion
FOR /F "tokens=*" %%g IN (test.txt) do (
set "line=%%g"
if /I "!line:pass=!" neq "%%g" (
echo String has pass
) else (
echo it doesnt has pass
)
)
Try this. Will match if the case is UPPER or lower.
It works by string replacement and comparison of before and after.
Also gives a running hit count. Nifty.
setlocal EnableDelayedExpansion
FOR /F "delims= tokens=*" %%g IN (test.txt) do (
set "str1=%%g"
set "str2=!str1:pass=replace!"
if not !str1!==!str2! set /a i+=1 & echo YES - FOUND !i!)
Oh also. the other answer is wrong. The way it's written the two values will NEVER be equal. Try it.
Apologies if duplicate, but no other answers so far have helped.
All I'm trying to do is loop through the files in a folder, and rename the last part of the file/extension.
Simply put - there could be 1-90 files, [filename]_01 - [filename]_90, and each day (via windows event scheduler) the number has to increment by one.
Nothing I do seems to achieve this.
The files are also meant to behave slightly differently when they hit certain milestones (30-60-90) but this I believe should already work if the variables update properly.
I have tried so many possible combinations of variable addressing (!variable!/%variable%/etc.) and while I can enter the loop, it does not repeat, nor update the variable number for the end of the files.
SetLocal EnableDelayedExpansion
set cnt=0
for %%A in (*) do set /a cnt+=1
set /A fileNumber = %cnt%-1
set /A newFileNumber = %cnt%
echo %fileNumber%
echo %newFileNumber%
for /l %%F in (%fileNumber%,1,1) do (
if %newFileNumber%==90 (
ren "*_%fileNumber%.don" "*_%newFileNumber%.csv"
)
if %newFileNumber%==60 (
ren "*_%fileNumber%.don" "*_%newFileNumber%.csv"
)
if %newFileNumber%==60 (
"ren *_%fileNumber%.don" "*_%newFileNumber%.csv"
)
ren "*_%fileNumber%.don" "*_%newFileNumber%.don"
set fileNumber=%fileNumber%-1
set newFileNumber=%newFileNumber%-1
)
This should simply update all the files in the directory to increment by 1 in the file name. If anyone can point out where I'm going wrong I would really appreciate it.
#ECHO OFF
SetLocal EnableDelayedExpansion
:: target directory name in variable for convenience.
SET "targetdir=U:\sourcedir"
:: switch to target directory
pushD "%targetdir%"
:: Start count at 100 so that it is 3 digits long
set cnt=100
dir
for %%A in (*) do set /a cnt+=1
set /A fileNumber = %cnt%
set /A newFileNumber = %cnt%+1
:: echoing last 2 characters (will be digits) of variables
echo %fileNumber:~-2% (%filenumber%)
echo %newFileNumber:~-2% (%newfilenumber%)
:: Assign %%F to values 100+actual descending by 1 to 101
for /l %%F in (%fileNumber%,-1,100) do (
rem note need REM remarks within the loop
REM use !varname! for current value of variable VARNAME
if !newFileNumber:~-2!==90 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
if !newFileNumber:~-2!==60 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
if !newFileNumber:~-2!==30 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
rem Since you've just renamed (eg) _59.don to _60.csv, _59.don is missing
IF EXIST "*_!fileNumber:~-2!.don" ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.don"
set /A fileNumber=fileNumber-1
set /A newFileNumber=newFileNumber-1
)
:: return to original directory
popd
GOTO :EOF
The required REN commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO REN to REN to actually rename the files.
Unfortunately, you've shown us HOW you've NOT been able to do some operation that's a little vague. We thus need to nut out what you intend to do, which is more work and prone to chasing wild geese.
You don't say what to do with files *_90, so we can't help there.
You appear to want to change *_59.don to *_60.csv, but your rename wants to change *_60.DON the next invocation, so unless the name has been changed from _*60.CSV back to *_60.DON, this isn't going to work.
Note that the basis of this routine is to work with the last two characters of variables. This is to accommodate the leading 0 you say is in your numbering scheme.
It's standard practice to assume that you will exercise any routine against a test directory for verification.
Note that every REN is ECHOed, so it is not EXECUTED, merely reported. Change the ECHO REN to REN to actually execute the command.
Note also that batch is largely case-insensitive. This means you don't have to wear out your SHIFT key unless you want to.
To do math you have to use /A with SET
SET x=1
SET /A x=%x%+1
ECHO %x%
The /A switch specifies that the string to the right of the equal sign
is a numerical expression that is evaluated. The expression evaluator
is pretty simple and supports the following operations, in decreasing
order of precedence:
For nested variables you need to use ! instead of %
SetLocal EnableDelayedExpansion
Setlocal EnableDelayedExpansion
for /f %%G in ("abc") do (
set _demo=%%G & echo !_demo!
)
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
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
If I am iterating over each file using :
#echo off
FOR %%f IN (*\*.\**) DO (
echo %%f
)
how could I print the extension of each file? I tried assigning %%f to a temporary variable, and then using the code : echo "%t:~-3%" to print but with no success.
The FOR command has several built-in switches that allow you to modify file names. Try the following:
#echo off
for %%i in (*.*) do echo "%%~xi"
For further details, use help for to get a complete list of the modifiers - there are quite a few!
This works, although it's not blindingly fast:
#echo off
for %%f in (*.*) do call :procfile %%f
goto :eof
:procfile
set fname=%1
set ename=
:loop1
if "%fname%"=="" (
set ename=
goto :exit1
)
if not "%fname:~-1%"=="." (
set ename=%fname:~-1%%ename%
set fname=%fname:~0,-1%
goto :loop1
)
:exit1
echo.%ename%
goto :eof
Sam's answer is definitely the easiest for what you want. But I wanted to add:
Don't set a variable inside the ()'s of a for and expect to use it right away, unless you have previously issued
setlocal ENABLEDELAYEDEXPANSION
and you are using ! instead of % to wrap the variable name. For instance,
#echo off
setlocal ENABLEDELAYEDEXPANSION
FOR %%f IN (*.*) DO (
set t=%%f
echo !t:~-3!
)
Check out
set /?
for more info.
The other alternative is to call a subroutine to do the set, like Pax shows.