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.
Related
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 this file structure where each subdirectory contains particular file types, lets say pdfs.
ParentDIR
-->SUBDIR1
-->SUBDIR2
I am trying to run a batch file from the parent to recursively return paths so I can parse them and perform some action
#echo off
for /R %%v in (*.pdf) do (
set pathname=%%~pv
echo %pathname%
)
I would expect the output of the path variable here to read
\PARENTDIR\SUBDIR1
\PARENTDIR\SUBDIR2
but it reads
\PARENTDIR\SUBDIR2
\PARENTDIR\SUBDIR2
If I echo the value of %%~pv without assigning it to a variable, it is correct.
How can I get the value of this variable to be assigned correctly at each iteration through the loop?
#echo off >Nul
setlocal EnableDelayedExpansion
for /R %%v in (*.pdf) do (
set pathname=%%~pv >Nul
echo !pathname!
)
endlocal
Here mind EnableDelayedExpansion keyword of setlocal command = Expand variables at execution time rather than at parse time, if used !pathname! syntax instead of %pathname% one.
I'm trying to create a batch file that would rename a bunch of files in a folder. These files would have a naming of something like: blah(lol).txt. There will always be a four letters, followed by an open bracket, three letters, and finally a close bracket.
I want the batch file to remove the bracketed part of the name of the file, ie. rename the file without the bracketed part.
for %%i IN (*.txt) DO (set name=%%~ni
set name2=%name:~1,4%
ren %%i %name2%)
Why doesn't this work?
Magoo provided an explanation as to why your script failed, as well as a working script.
But in your case, there is no need for a script. A simple REN command is all that is needed:
ren "????(???).txt" "????.*"
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "tokens=1,2,3delims=()" %%a IN (
'dir /b /a-d "%sourcedir%\*(*).*" '
) DO ECHO REN "%sourcedir%\%%a(%%b)%%c" %%a%%b%%c
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.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
simple but works from the folder with the files to be renamed.
#echo off
title Rename Bat
echo This bat must be in the folder that
echo contains the files to be renamed.
:begin
echo Enter File Name
set /p old=
echo Enter New Name
set /p new=
ren "%old%" "%new%"
echo File Renamed
ping -n 3 127.0.0.1 >NUL
goto begin
a much simpler approach ... try a for loop that cycles through all files in your folder
I'm going to use lol as an example of three letter word inside brackets as stated in your question
#echo off
for %%a in (*) do (
rename "%%a" "%%a(lol).exe"
)
to use this batch file you have to place it in the folder containing the files you wanna rename
Here is my folder structure of folder A
A\Temp\folder1\c\one.txt
A\Temp\folder2\c\one.txt
A\Temp\folder3\c\one.txt
and another folder B
B\folder1.ext\c\ Note: the extension is part of its folder name.
B\folder2.ext\c\
B\folder3.ext\c\
What i want to do is to copy all the one.txt's from A\Temp*\c\one.txt to C*\c\
What i am trying now is:
for /D %%b in (TEMP\*) do (
set folder=%%~nb
ECHO %%~b
ECHO Copying %%b
ECHO.
ECHO.
COPY %%b\c\one.txt B\%folder%.ext\c
It doesn't work coz the foldername gettig stored in %folder% is only the name of the last folder (here folder3) everytime, ie the one.txt from the folder1 gets copied to C\folder3 and others dont get copied (system cannot find the file specified)
If you want to assign a loop variable to another variable inside the loop you have to enable delayed expansion:
setlocal EnableDelayedExpansion
for /D %%b in (TEMP\*) do (
set folder=%%~nb
ECHO %%~b
ECHO Copying %%b
ECHO.
ECHO.
COPY %%b\c\one.txt B\!folder!.ext\c
)
endlocal
Otherwise you have to use the loop variable:
for /D %%b in (TEMP\*) do (
COPY %%~b\c\one.txt B\%%~nb.ext\c
)
The reason for this is that without delayed expansion, variables (%folder%) are expanded at parse time, i.e. when the command (block) is read. With delayed expansion enabled, variables (!folder!) are expanded at run time.
I want to write batch file which will loop through all directories containing backup directory and remove files older than X days within it.
On computer which I want run my script there's no "forfile" command.
There's no PowerShell, so CMD or VBScripts seems to be only way of accomplishing this task.
Currently I have problem with "set" statement - it seems that when I'm calling %checkpath% I didn't receive expected folder.
rem we will memorize current directory
pushd %cd%
set folder="C:\Documents and Settings\myname\Desktop"
cd %folder%
rem loop only folders with five chars within their names (unfortunately on less also
for /D %%g in (?????) DO (
set checkpath="%cd%\%%g\backup"
if exist %checkpath% (
for %%a in ('%%g\backup\*.*') do (
set FileDate=%%~ta
set FileDate=%%FileDate:~0,10%%
rem here I want to compare file modification data with current date
)
)
popd
pause
You need to use delayed expansion to read a variable that you have set inside a for loop.
Try this instead
setlocal enabledelayedexpansion
rem we will memorize current directory
pushd %cd%
set folder="C:\Documents and Settings\myname\Desktop"
cd %folder%
rem loop only folders with five chars within their names (unfortunately on less also
for /D %%g in (?????) DO (
set checkpath="%cd%\%%g\backup"
if exist !checkpath! (
for %%a in ('%%g\backup\*.*') do (
set FileDate=%%~ta
set FileDate=!%FileDate:~0,10%!
rem here I want to compare file modification data with current date
)
)
popd
pause
Replacing the %'s with !'s on variables you have created will signal it to use delayed expansion instead.
Bali's answer has a slight mistake. The second set filedate is incorrect, otherwise his is fine, but may not work if you do not have delayed expansion enabled. I fixed his mistake and showed you how to ensure delayed expansion is enabled. I have also made some other changes:
::This command will ensure that the delayed expansion, i.e. the "!"s below,
:: will work. Unfortunately, it also means you loose the results of any
:: "set" commands as soon as you execute the "endlocal" below.
setlocal ENABLEDELAYEDEXPANSION
::you might want
::set "folder=%USERPROFILE%\Desktop"
set "folder=C:\Documents and Settings\myname\Desktop"
rem we will memorize current directory
::If you ran this code with the current directory set to a directory on
::a drive other than C:, your previous code would not have worked to
:: change to your desired target directory. this slight change fixes that.
pushd "%folder%"
rem loop only folders with five chars within their names - unfortunately on less also
::Use capitals for your loop variables. The var names are case sensitive, and
::using capitals ensures there is no confusion between the var names and ~modifiers
for /D %%G in ( ????? ) DO (
set checkpath="%CD%\%%G\backup"
if exist !checkpath! (
for %%A in ('%%G\backup\*.*') do (
set FileDate=%%~tA
set FileDate=!FileDate:~0,10!
rem here I want to compare file modification data with current date
)
)
popd
endlocal
pause
But, you don't need the "setlocal" or delayed expansion if you write it like this:
::you might want
::set "folder=%USERPROFILE%\Desktop"
set "folder=C:\Documents and Settings\myname\Desktop"
rem we will memorize current directory
::If you ran this code with the current directory set to a directory on
::a drive other than C:, your previous code would not have worked to
:: change to your desired target directory. this slight change fixes that.
pushd "%folder%"
rem loop only folders with five chars within their names - unfortunately on less also
for /D %%G in ( ????? ) DO (
if exist "%%~G\backup" (
for %%A in ('%%~G\backup\*.*') do (
for /F "usebackq" %%T in ( '%%~tA' ) do (
echo File date is %%T, todays date is %DATE%
)
)
)
popd
pause