The usual escape character in dos batch files is caret, ^. However for percent, %, the delimiter for variables, the escape is to double up the percents: %%cd%%. Things change when using parameter extensions inside a for loop. Instead of %%~dpnx0 emitting %~dpnx0, as it will outside the loop, it carries out the substitution, emitting D:\Scripts\foo.py.
Here's a batch file demonstration:
#echo off
echo This is a pipe: ^|
echo Use this var for the current directory: %%cd%%
echo Use this to echo full path of running batch file: %%~dpnx0
for %%a in (foo.py baz.py) do (
echo #python %%~dpnxa ^> %%~na.bat
)
These are the results I get:
This is a pipe: |
Use this var for the current directory: %cd%
Use this to echo full path of running batch file: %~dpnx0
#python d:\Scripts\foo.py > foo.bat
#python d:\Scripts\baz.py > baz.bat
But this is what I want:
This is a pipe: |
Use this var for the current directory: %cd%
Use this to echo full path of running batch file: %~dpnx0
#python %~dpnxa > foo.bat
#python %~dpnxa > baz.bat
I've tried doubling, tripling, and quadrupling the percents as well is interspersing carets throughout, all without success.
It is impossible to prevent a FOR variable expression from expanding. If a FOR is in effect with variable X defined, then the FOR expansion phase will always expand %X.
But you can hide the percent behind another FOR variable :)
The following gives the result you are looking for:
#echo off
echo This is a pipe: ^|
echo Use this var for the current directory: %%cd%%
echo Use this to echo full path of running batch file: %%~dpnx0
for %%P in (%%) do for %%A in (foo.py baz.py) do (
echo #python %%P~dpnxA ^> %%~nA.bat
)
FOR variables have global scope (though they are only accessible within the DO clause). This can lead to an insidious problem. Any time you have a subroutine that uses a percent literal within a FOR loop, then you are at risk of unintended results! A FOR statement issued before your CALL can influence the results of a FOR DO clause within your CALLed routine.
#echo off
for %%) in (Yikes!) do call :test
exit /b
:test
echo This works outside loop (confidence = 100%%)
for %%A in (1) do echo This does not work inside loop (confidence = 100%%)
for %%P in (%%) do for %%A in (1) do echo This works inside loop (confidence = 100%%P)
exit /b
Here is the output
This works outside loop (confidence = 100%)
This does not work inside loop (confidence = 100Yikes
This works inside loop (confidence = 100%)
You could use delayed expansion or call-percent expansion, or like dbenham shows expanding another FOR variable.
setlocal EnableDelayedExpansion
set percent=%%
set "line=%%~dpnxa ^>"
for %%a in (foo.py baz.py) do (
echo #python !percent!~dpnxa ^> %%~na.bat
call echo #python %%line%% %%~nxa.bat
)
Related
I am trying to create a script that will create another batch script. However this code will not run properly, immediately exiting as soon as the if statement is evaluated.
set yes=yes
pause
IF /I %yes% == yes (
ECHO REM Music>>mf.bat
ECHO FOR /f %%i in (C:\CopyToRoot\MusicFileAndLocation.txt) do set MusicFile=%%i>>mf.bat
)`
However if the second line in the if statement is removed the code executes without issue.
What am I doing wrong? is there something I am missing about the echo statement?
Perhaps:
#Echo Off
Set "yes=yes"
Pause
If /I "%yes%"=="yes" (
>"mf.bat" (
Echo #Echo Off
Echo Rem Music
Echo For /F "UseBackQ Delims=" %%%%A In ("C:\CopyToRoot\MusicFileAndLocation.txt"^) Do Set "MusicFile=%%%%A"
)
)
Double the percents and escape any internal closing parentheses.
You need to format the code block with a bit of effort, however, it is much simpler to get rid of the block and simply call a label. Also, the batch file will consume the % so you need to double them in order to redirect the string correctly to file.
#echo off
set yes=yes
pause
IF /I "%yes%"=="yes" call :final
goto :eof
:final
echo REM Music>mf.bat
echo FOR /f %%%%i in (C:\CopyToRoot\MusicFileAndLocation.txt) do set MusicFile=%%%%i>>mf.bat
Note I use single redirect on the first rem line to overwrite the file, else it will append each time you run the code, if that was the intention, simply do double redirect >>
I want count only files in a directory with Windows batch.
(The ultimate purpose is to call a vbs file if I have any files whatsoever.)
Here's what I have so far:
set /a db=0
echo %db%
for /f %%i in ('dir /b') do (
set /a db=%db%+1
echo %db%
)
echo %db%
This will me give the following: 0 0 0 0 1 for the value of %db%
(as I have 3 files in the directory right now)
Maybe trivial, but why won't it increase the value of %db% during the loop, only in the end?
What happens between the last loop of for (where %db% still was 0) and the last line (where %db% is 1 already)?
How should I fix it?
Thanks in advance!
While the batch file is being executed, each line or block of lines (lines enclosed in parenthesis) is first parsed and then executed. During the parse phase, variable read operations (where you retrieve the value of the variable, that is %var%) is removed, being replaced with the value inside the variable. Once this is done, the resulting command is executed.
Inside your for loop you are changing the db variable, but you can not retrieve the changed value. All the read operations were replaced with the value in the variable before the for command start to execute.
The usual way to solve this problem is to enable delayed expansion and change the %var% syntax into !var! syntax where needed. This tells the batch parser that the variable/value substitution must be delayed until the command is executed, not when the line/block is parsed.
setlocal enabledelayedexpansion
set /a db=0
echo %db%
for /f %%i in ('dir /b') do (
set /a db=!db!+1
echo !db!
)
echo %db%
Now the read operations inside the for loop are using delayed expansion.
But, you don't even need it. The set /a uses its own parser that is able to retrieve the value in the referenced variables, so you can use any of those options
set /a db=db+1
set /a db+=1
to change the variable without having to use read syntax.
Also, unless you need to take into consideration hidden files, it is better to not use a for /f processing the output of a dir command that is executed in a separate cmd instance. Just use a for loop
set /a "db=0"
for %%a in (*) do set /a "db+=1"
echo %db%
But, if as you point all you need is to know if you have any file, and not the number of them, all this is not even needed
dir /a-d >nul 2>nul && ( echo there are files ) || ( echo there is not any file )
It just executes the dir command, with folders excluded (/a-d), discarding any output (>nul) or error message (2>nul).
- If any file is found (no errors), the command after the conditional operator && is executed.
- If there is not any file, the dir command fails and the command after the || conditional operator is executed.
If you just want to detect whether or not there are any files, MC ND already showed a reliable way in their answer.
To count the number of files you can let the find command do the work, as it features a /C switch that counts the number of matching lines. If you specify to do an inverse search by /V (so to return all non-matching lines), together with an empty search string "", all lines are returned.
So when the input for find comes from a dir command line that lists all files, the total count of files is returned (2> nul suppresses the error message in case no files are present):
2> nul dir /B /A:-D | find /C /V ""
To capture the count in a variable, use a for /F loop:
for /F %%C in ('
2^> nul dir /B /A:-D ^| find /C /V ""
') do set "COUNT=%%C"
echo %COUNT%
Note that special characters like > and | need to be escaped by preceding with ^ in order for them not to be processed immediately but in the cmd instance initiated by for /F.
I have written below script:
#echo off
setlocal EnableDelayedExpansion
REM Collect source filenames from C:\Files and load into C:\doc.txt
dir C:\sources\Sourcefiles /b /a-d > C:\sourcefilenames.txt
REM fetch count of source files and store into variable count
For /F %%I in ('call C:\count.bat ') Do Set count=%%I
REM loop "count" number of times and echo temp.txt value
FOR /L %%A IN (1,1,%count%) DO (
REM call line.bat to fetch line 1,line 2 and so on of sourcefilenames.txt for each loop
call line.bat %%A>C:\temp.txt
set /p var=<C:\temp.txt
echo var:%var% ----------> returns previous run value
type C:\temp.txt ----------. returns current value of temp.txt
)
Basically what i am trying to do out of the above script is:
I am creating a variable(var) from the content of temp.txt(data in temp.txt will change for each time loop runs) to be used in multiple loops.
But the problem i am facing is :
Echo var is:%var% command returning me previous run value not temp.txt current content.whereas command "type C:\temp.txt" returning me temp.txt current content.
(Note: if i have called/created variable "var" from some other script it returns me that previous value else it returns Null)
Your help/guidance on above issue is really appreciated.
Thanks
I suppose the variable remains in memory, without being re-read.Attempts to limit the validity of the variable.
setlocal
echo something.....
endlocal
or #echo off & setlocal
When CMD.exe encounters a block of code in parentheses, it reads and parses the entire block before executing. This can cause unintuitive behavior. In this case, your echo var:%var% line is being parsed once at the beginning of the loop and never again.
The easiest fix for this is to change that line to
echo var:!var!
The !var! syntax is parsed every time through the loop. This works because you have enabledelayedexpansion set in your script.
Another workaround to this type of problem is to remove the parentheses and instead call out to a subroutine.
FOR /L %%A IN (1,1,%count%) DO call :loopLineBat %%A
... rest of script
exit /b
:loopLineBat
>%temp%\temp.txt call line.bat %1
<%temp%\temp.txt set /p var=
echo var:%var%
type %temp%\temp.txt
exit /b
This loop does the same as above, but because it is not in a parenthesized block, all of the lines are parsed and executed in order.
This code loops through the contents of "Fire" folder on PC, which contains several subfolders. For each subfolder it runs another FOR to look for a match to directories on a tablet. If no match found it calls subroutine :missing to set variables about the missing directory.
When I get back to main routine I'm unable to echo the vars set in the sub. I've tried many permutations of !name[%CNT%]! (doubling/tripling the % and !) but can't get it right. Hope someone can lend a hand.
#Echo off
SETLOCAL EnableDelayedExpansion
:main
Set /A CNT=0
:: Fire folder contains dirs that may have been deleted from tablet.
For /F %%G IN ('dir /b %Fire%') DO (
SET thisdir=%%G
SET /A CNT+=1
:: set command that will look for %%G on the tablet
set cmd="adb shell ls /system/priv-app/!thisdir!"
:: This loop returns nul if dir exists on device
For /F "tokens=1* delims=:" %%g in ('!cmd!') do (set _N=%%h)
If NOT "!_N!"=="" call :missing !thisdir!
:: HERE'S THE PROBLEM: With this syntax I get "CNT" "CNT"
Echo AFTER THE CALL: "!name[!CNT!]!" "!pkg[!CNT!]!"
:: this displays: "pkg[3]]
Echo AFTER THE CALL: "%!name[!CNT!]%!" [!pkg[!CNT!]!]
)
:missing
:: Both %CNT% and !CNT! work here
Set pkg[%CNT%]=%1
:: set friendly name for this dir
If "%1"=="com.name.video" set "name[%CNT%]=Video"
If "%1"=="com.name.games" set "name[%CNT%]=Games"
. . .
:: THIS WORKS HERE IN THE SUB. HOW TO DISPLAY IN MAIN?
Echo MISSING: !name[%CNT%]! (!pkg[%CNT%]!)
:: Output: MISSING: Video (com.name.video)
exit /b
To display in main loop:
call Echo AFTER THE CALL: "%%name[!CNT!]%%" [%%pkg[!CNT!]%%]
Iassume you know not to use ::comments in a block and that your maun will run into your sub...
In your first AFTER THE CALL line, your script will try to evaluate !name[! and then a string literal CNT and then !]! and so on. Since both !name[! and !]! are undefined, they evaluate to nothing and you're left with the string literal, CNT. Instead, you should convert the inner !CNT! to percent notation, so it can peacefully coexist with the outer delayed variable. You can accomplish this with a for loop.
for %%I in ("!CNT!") do echo !name[%%~I]!
... to avoid the conflicting exclamation marks. In your second AFTER THE CALL line, you attempt to begin a variable with %! while also ending it with %!. When using that sort of syntax, the ! has to be outside, and the % inside (or possibly vice-versa -- see Magoo's answer for an example). But since it's inside a parenthetical code block, it won't work like you want anyway. You need the delayed expansions. Use the for loop I described above.
By the way, don't use those :: pseudo-labels as comments within a parenthetical code block. Use rem instead. The :: can break things.
Maybe you can try this:
#echo off&SetLocal EnableDelayEdexpansion
set "name[1]=something"
set "a=1"
echo !name[%a%]!
rem echo %name[!a!]%
call echo !name[%a%]!
call echo %%name[!a!]%%
call echo %%name[%a%]%%
for %%a in (!a!) do (echo !name[%%a]!)
pause
I have a batch with the following code:
SET CI=MySubDir
SET CIDIR=SomePath\..\..\%CI%
SET OutDir=MyOutDir
for /f %%G in ('dir /b %CIDIR%') do (
SET SCHEMADIR=%CIDIR%\%%G\schema
SET CATDIR=%CIDIR%\%%G\catalog
echo %%G
echo %SCHEMADIR%
echo %CATDIR%
if exist %SCHEMADIR% (
echo copy "%SCHEMADIR%" to "%OutDir%\..\Schema"
XCOPY /E /Y /I /Q /D %SCHEMADIR% "%OutDir%\..\Schema"
)
if exist %CATDIR% (
echo copy "%CATDIR%" to "%Outdir%\..\Catalog"
XCOPY /E /Y /I /Q /D %CATDIR% "%OutDir%\..\Catalog"
)
)
This program should copy all files within any sub-directory of SCHEMADIR or CATDIR to my OutDir (of course the files within my OutDir might be overridden several times depending on the existing ones within the source-directories).
When I echo the current file-name with echo %%G I get the sub-directory as wanted, curiously echoing either SCHEMADIR or CATDIR results in only the very last sub-directory found within CIDIR. So while %%Gresults in e.g. BE, SCHEMADIR results to SomePath\..\..\MySubDir\TH (where TH is the last sub-directory within MySubDir). What is whrong here?
In batch files, each line or block of lines (code inside parenthesis) is parsed, executed and the process repeated for the next line/block. During the parse phase, all variable reads are removed, being replaced with the value in the variable before the code starts to execute. If a variable changes its value inside the line/block, this changed value can not be retrieved from inside the same line/block as the variable read operation does not exist.
The usual way to solve it is to use delayed expansion. When enabled, you can change (where needed) the syntax from %var% to !var!, indicating to the parser that the read operation must be delayed until the command that uses the value starts to execute.
You can try this code to see it in action
#echo off
setlocal enabledelayedexpansion
set "test=initial value"
for %%a in (a b c) do (
rem Variable changed inside the block of code
set "test=%%a"
rem Retrieve data with normal expansion
echo Normal expansion: [%%a] [%test%]
rem Retrieve data with delayed expansion
echo Delayed expansion: [%%a] [!test!]
)
In your case, since SCHEMADIR and CATDIR are changed inside the block of code, to retrieve the changed value inside the same block you will need delayed expansion.