Our current build system is a mass of batch files that are called from a "master" batch file. How can I find all the batch files that are called from the "master" batch file?
Unfortunately the repository also contains a lot of other batch files and I cannot just list all the batch files in the working copy.
I don't care much about "simulating" a real execution. It would be fine if I got a list of all the files potentially called from the master batch file.
There are 299 files, so I'd rather not add an echo "" to each one of them.
Referenced batch files are called using "call xyz.bat", i.e. relative paths. Sometimes the batch files change the current working directory though, like so:
cd client
call mk.bat
cd ..
or
pushd client\install
call prepare.bat
popd
EDIT: added examples
Yuck - this problem is nearly impossible to solve perfectly for several reasons:
1) Batch files may be "called" by multiple mechanisms:
CALL scriptName
pipes, for example: scriptName | findstr ...
FOR /F, for example: for /f ... in ('scriptName ...') do ...
CMD, for example: cmd /c scriptName ... or %comspec% /c scriptName
START, for example start "" scriptName ...
2) Any of the above constructs may be present without being a "call" to a batch script. For example, CALL can be used to call a label, or to execute a command. FOR /F could be used to parse a string. etc.
3) No matter which "call" mechanism is used, the "call" target may be represented by a variable instead of the string literal. For example:
set "myScript=ScriptName"
REM the SET may not be anywhere near the CALL
call %myScript%
4) The script name and path might not appear in the code. It could be dynamically read from the file system, or derived via logic.
5) The actual call itself may be embedded as a value in a variable. This is true for any call mechanism. For example:
set "myCommand=CALL"
%myCommand% ScriptName
6) As you have noted in your question, the path to the script may be relative, and the script may change the current directory. Or the "call" may be relying on the PATH environment variable.
7) Any "called" script could itself "call" another script.
You could use FINDSTR to look for any of the call mechanisms, and you will likely find most of the "calls". But there will likely be many false positives. You could add the /N switch to prefix each matching line with the line number. Then you would need to check each matching line manually in your text editor to see if it is a "call" that you are interested in.
findstr /nir /c:"\<call\>" /c:"|" /c:"for */f " /c:"\<cmd\>" /c:"\<%comspec%\>" /c:"\<start\>" *.bat
There may be so many false positives that you might be better off manually tracing the logic of the entire script :-( This is especially true since there is no guarantee that FINDSTR will find all "calls", since the call itself could be masked behind a variable.
You could prepend logging to each of the batch files. I know you said you'd rather not, but it's not as hard as you might think.
#echo off
:: addlogging.bat patch|unpatch
::
:: addlogging.bat patch
:: finds every .bat file in the current directory and every subdir
:: beyond, and patches each, prepending an echo to a log file
::
:: addlogging.bat unpatch
:: finds every .bat file in the current directory and every subdir
:: beyond, and removes the logging patch
setlocal
if #%1==#patch goto next
if #%1==#unpatch goto next
goto usage
:next
for /f "delims=" %%I in ('dir /s /b *.bat') do (
if not "%%I"=="%~f0" (
if #%1==#patch (
set /p I="Patching %%I... "<NUL
echo #echo %%time%% %%~f0 %%* ^>^> "%%userprofile%%\Desktop\%%date:/=-%%.log">"%%~dpnI.tmp"
) else (
set /p I="Unpatching %%I... "<NUL
)
findstr /v "^#echo.*time.*~f0.*>>" "%%I">>"%%~dpnI.tmp"
move /y "%%~dpnI.tmp" "%%I">NUL
echo Done.
)
)
goto :EOF
:usage
echo usage: %~nx0 patch^|unpatch
Related
Before I start posting code I am wondering if this is even feasible.
I have a directory of folders I need to move to a new directory.
But I only need to move the folders that contain only 2 files in them.
The rest of the folders have more than 2 files in them, but they need to stay.
So would this be feasible with a batch file?
This was interesting so I took a stab at it:
#echo off
set "dir=C:\Your\Current\Directory"
set "ndir=C:\Your\New\Directory"
setlocal enabledelayedexpansion
for /d %%A in (%dir%\*) do (
pushd "%%A"
for /f %%B in ('dir /a-d-s-h /b ^| find /v /c ""') do (
set cnt=%%B
if "!cnt!" == "2" (if not exist "%ndir%\%%~nA" robocopy "%%A" "%ndir%\%%~nA" /e)
)
)
pause
I kept running into issues so I modified a few things to make it do what I wanted; there're likely more elegant ways to go about it, but this worked ¯\_(ツ)_/¯. First thing is setting variables for your current directory (dir) and your new directory (ndir) to make it a little easier to digest later on; we also need to enable delayed expansion since the value of our counting variable (cnt) will change between loop iterations. The first FOR loop is /d, which will loop through folders - we set each of those folders as parameter %%A and use that to change our directory (using pushd) prior to running our nested commands.
The second FOR loop is /f, which will loop through command results - the commands in this case being dir and find. For dir we are specifying /a to show all files that -d aren't folders, -s system files, or -h hidden files, and we display that output in /b bare format. Using the output from dir, we run find and specify to /v display all non-empty lines and then /c count the number - which becomes parameter %%B.
Finally, we set %%B as our counting variable (cnt) - if !cnt! is equal to 2, we see if the folder already exists in the new directory, and if it does not we robocopy it over. The move command was giving me some trouble because the folder would be locked by the loop, so if you want you could also throw in a DEL command to delete the original folder.
Let me know if that helps! Hopefully your research was going well anyway.
References: Counting Files, FOR Looping, pushd, DIR, FIND, robocopy
I need to move files from one Dir to another while placing the files in sub-directorates according to their file name.
Background:
File name (building Number)-assign-flr-pln.dgn
new location would be F:/Assignment Floor Plans/Buildings/(Building Number)/floor plan file.
The batch files needs to read the .dgn name for the building number then place the file in the corresponding floor plan sub folder in the building number folder
so...
take (building number)-assign-flr-pln.dgn file from one dir and place it.....
Assignment Floor Plans
- Buildings
-(building number)
-Floor Plan Files <-- Here
You have a fair bit to learn if you want to do something this complicated directly from a batch file.
Research list:
FOR
SET
CALL
You'll want to use the FOR command to get the filename; pay attention to the %~ options. For example:
set BAT_DGNFNM=
for %%F in (*.dgn) do set BAT_DGNFNM=%%~nF
This causes the environment variable BAT_DGNFNM to be set to the base filename. However, it loops through all the files, so really, you need it to call a subroutine for each file:
for %%F in (*.dgn) do call :DODGN "%%~dpnxF" "%%~nF"
This calls the subroutine :DODGN and passes it two quote-encapsulated parameters; the first one is the fully-qualified filename and the second one is simply the base filename.
Then you do the actual file handling in the subroutine:
:DODGN
set BAT_DGNFNM=%1
set BAT_DGNBNM=%2
REM Do stuff with BAT_DGNBNM using SET commands
GOTO :EOF
You'll need to make sure you don't "fall through" to the subroutine after the main loop is done, and you should clean up your environment variables when you're done, so this would look something like:
#echo off
for %%F in (*.dgn) do call :DODGN "%%~dpnxF" "%%~nF"
goto ENDIT
:DODGN
set BAT_DGNFNM=%1
set BAT_DGNBNM=%2
REM Do stuff with BAT_DGNBNM using SET commands
GOTO :EOF
:ENDIT
set BAT_DGNFNM=
set BAT_DGNBNM=
Good luck -- this stuff can get complicated.
Final reminder: Most of your learning curve will be with the SET command -- that's how you extract substrings out of one environment variable and put it into another, perform text replacement, etc.
This stuff is way easier in VBS or PowerShell.
Although you showed no code we could help you with...
It's quite easy if you know the commands to use (a great site for starters:
SS64):
Use a plain for loop to get each file.
Use a for /f loop to extract the building-nr (according to your comment it's the first token separated by a dash)
Use md to create the destination folder (ignore error if already existent)
Use copy to copy the file.
Finished.
#echo off
REM prepare some files for testing:
break>100-assign-flr-pln.dgn
break>110-assign-flr-pln.dgn
break>235-assign-flr-pln.dgn
REM now copy them into their destination folders:
set "dest=Assignment Floor Plans - Buildings - # - Floor Plan Files"
for %%f in (*-assign-flr-pln.dgn) do (
for /f "tokens=1 delims=-" %%a in ("%%f") do (
call md "%%dest:#=%%a%%" 2>nul
call copy "%%f" "%%dest:#=%%a%%\"
)
)
REM now show the result:
tree /f
Note: adapt your destination (add F:\)
"Special Effects":
call <command>: introduce another layer of parsing to avoid delayed expansion
#=%%aString replacing within a variable
2>nul suppress error messages
I have a number of files with the same naming scheme. As a sample, four files are called "num_001_001.txt", "num_002_001.txt", "num_002_002.txt", "num_002_003.txt"
The first set of numbers represents which "package" it's from, and the second set of numbers is simply used to distinguish them from one another. So in this example we have one file in package 001, and three files in package 002.
I am writing a windows vista batch command to take all of the files and move them into their own directories, where each directory represents a different package. So I want to move all the files for package 001 into directory "001" and all for 002 into directory "002"
I have successfully written a script that will iterate over all of the txt files and echo them. I have also written a scrip that will move one file into another location, as well as creating the directory if it doesn't exist.
Now I figure that I will need to use substrings, so I used the %var:~start,end% syntax to get them. As a test, I wrote this to verify that I can actually extract the substring and create a directory conditionally
#echo off
set temp=num_001_001.txt
NOT IF exist %temp:~0,7%\
mkdir %temp:~0,7%
And it works. Great.
So then I added the for loop to it.
#echo off
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
echo directory %temp:~0,7%
)
But this is my output:
directory num_002
directory num_002
directory num_002
directory num_002
What's wrong? Does vista not support re-assigning variables in each iteration?
The four files are in my directory, and one of them should create num_001. I put in different files with 003 004 005 and all of it was the last package's name. I'm guessing something's wrong with how I'm setting things.
I have different workarounds to get the job done but I'm baffled why such a simple concept wouldn't work.
Your problem is that the variable get replaced when the batch processor reads the for command, before it is executed.
Try this:
SET temp=Hello, world!
CALL yourbatchfile.bat
And you'll see Hello printed 5 times.
The solution is delayed expansion; you need to first enable it, and then use !temp! instead of %temp%:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
echo directory !temp:~0,7!
)
See here for more details.
Another solution is to move the body of the for loop to a subroutine and call it.
#echo off
FOR /R %%X IN (*.txt) DO call :body %%X
goto :eof
:body
set temp=%~n1
echo directory %temp:~0,7%
goto :eof
Why do this? One reason is that the Windows command processor is greedy about parentheses, and the results may be surprising. I usually run into this when dereferencing variables that contain C:\Program Files (x86).
If the Windows command processor was less greedy, the following code would either print One (1) Two (2) or nothing at all:
#echo off
if "%1" == "yes" (
echo 1 (One)
echo 2 (Two)
)
However, that's not what it does. Either it prints 1 (One 2 (Two), which missing a ), or it prints 2 (Two). The command processor interprets the ) after One as the end of the if statement's body, treats the second echo as if it's outside the if statement, and ignores the final ).
I'm not sure whether this is officially documented, but you can simulate delayed expansion using the call statement:
#echo off
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
call echo directory %%temp:~0,7%%
)
Doubling the percent signs defers the variable substitution to the second evaluation. However, delayed expansion is much more straightforward.
I want to replace the content paths defined into the file i.e logging.properties to the desired location path of the jboss7 location .
Basically i'm using installer where i have to browse my jboss7 folder and locate it to any desired location of the user . But in few files of jboss7 there are some hardcoded path defined like in given logging.properties file.
I need to change that hard coded path to the desired location path.
As of now i'm having repl.bat and file test.bat files in the same folder.
repl.bat helper file could be find in following link:-
http://www.dostips.com/forum/viewtopic.php?f=3&t=3855
I just copied the code and created repl.bat file.
test.bat file :-
#ECHO OFF
SETLOCAL
SET "folder="
FOR /r "C:\" %%a IN (tintin.txt) do IF EXIST "%%a" SET "folder=%%~dpa"&GOTO got1
FOR /r "D:\" %%a IN (tintin.txt) do IF EXIST "%%a" SET "folder=%%~dpa"&GOTO got1
:got1
echo "%folder%"
PAUSE
set "newpath=%folder%"
set "newpath=%newpath:\=\\%"
echo "%newpath%"
PAUSE
type "logging.properties" | repl "(Directory=).*(\\\\standalone\\\\)" "$1%newpath%$2">"logging.properties.tmp"
PAUSE
move "logging.properties.tmp" "logging.properties"
PAUSE
GOTO :EOF
PAUSE
Here in this test.bat file , i'm searching a file tintin.txt file and setting the path into a variable name as 'folder'. tintin.txt file is just inside the folder of jboss7.This is because of the possibilities of more than one jboss7 application server folder into the system.
Till now i have got the path i.e "C:\Users\Anuj\Desktop\jboss7\" and set into the variable 'folder'.
Now there is file named logging.properties into the folder location
C:\Users\Anuj\Desktop\jboss7\standalone\configuration
logging.properties :-
com.latilla.import.uploadDirectory=C:\\progra~2\\Latilla\\C4i\\jboss7\\ standalone\\uploads
com.latilla.import.maxFilesUploadNumber=10
com.latilla.export.templateFile=C:\\progra~2\\Latilla\\C4i\\jboss7\\standalone\\templates\\GDV_HDI_Format.xls
com.latilla.etl.pluginsRootDirectory=C:\\progra~2\\Latilla\\C4i\\jboss7\\standalone\\cloverETL\\plugins
com.latilla.etl.templatesDirectory=C:\\progra~2\\Latilla\\C4i\\jboss7\\standalone\\etl
com.latilla.db.user=postgres
com.latilla.db.pass=password
repl.bat helper file helps to replace the url path with the desired path i.e path set to variable name 'folder'.
I want to replace the C:\progra~2\Latilla\C4i\jboss7\ with the path set to variable name 'folder'.
Note :-
here in logging.properties file path contents is having different format of path i.e C:\
means double slash. \
Might be the script that i have tried test.bat is incorrect.
When i double click the test.bat file i got error.
Although I can't help you with fixing the issue you are getting while using the repl.bat file, I can suggest a different way of solving the initial problem of path replacement.
If the jboss7 string is guaranteed to be present in all the original paths in your configuration file(s), you could try the following approach:
#ECHO OFF
SETLOCAL DisableDelayedExpansion
FOR /F "delims=" %%A IN ('DIR /B /S C:\tintin.txt') DO (CD /D "%%~dpA" & CALL :got1)
FOR /F "delims=" %%A IN ('DIR /B /S D:\tintin.txt') DO (CD /D "%%~dpA" & CALL :got1)
GOTO :EOF
:got1
SET "propfile=%CD%\standalone\configuration\logging.properties"
IF NOT EXIST "%propfile%" GOTO :EOF
SET "tempfile=%TEMP%\logging.properties.tmp"
FIND /I /V "jboss7\\" >"%tempfile%"
>>"%tempfile%" (
FOR /F "tokens=1,* delims=" %%I IN ('FIND /I "jboss7\\"') DO (
SET "pathname=%%J"
SETLOCAL EnableDelayedExpansion
IF NOT "!pathname!" == "!pathname:*jboss7\\=!" (
SET "pathname=%__CD__:\=\\%!pathname:*jboss7\\=!"
)
ECHO %%I=!pathname!
ENDLOCAL
)
)
ECHO Old file "%propfile%":
TYPE "%propfile%"
ECHO =======================================
ECHO New file:
TYPE "%tempfile%"
PAUSE
:: uncomment the next line once you have verified the replacement works correctly
::MOVE "%tempfile%" "%propfile%"
Searching for the tintin.txt file has been changed slightly so as to possibly make the process faster. Instead of iterating over every directory and checking if it contains the file, the loops now read the output of DIR, which returns only actually existing entries.
Note that you could also use a FOR /R loop, as in your present code, with the same effect i.e. returning only existing paths, but the IN clause would need to contain a mask rather than a normal name, but that would have to be a mask that couldn't match anything else in your system than just tintin.txt. For instance, if you knew for certain that there could be no file called tintin.txt1 or tintin.txtx or anything else where tintin.txt is followed by exactly one character, you could use the following template instead:
FOR /R "C:\" %%A IN (tintin.txt?) DO (CD /D "%%~dpA" & CALL :got1)
and same for D:\. That would return only references to files actually existing and matching the mask.
Also, you can see that the loops do not jump (GOTO) to the got1 label but instead call the got1 subroutine. With that change, it is possible to process many application instances in one go. I don't know yours can be installed multiple times. If not, you'll probably want to change it back to GOTO.
The subroutine in my script is referencing the config file using its full path as specified in your description (...\standalone\configuration\logging.properties). For some reason, in your script the file is referenced simply by its name, even though there's no preceding CD or PUSHD command changing the current directory to the location of the file. I assumed you were trying to simplify your script and omitted that bit, whether intentionally or not. Otherwise I may have missed something in your explanation and/or script.
After verifying that the config file exists at the expected location, the replacement itself is done in this way:
All the non-path config lines are written to a temporary file with one go.
Every config line containing a path is processed in this way:
if it does not contain the jboss7\\ string, it is omitted;
otherwise the part of the path up to and including jboss7\\ is removed;
the current directory is inserted before the remaining part (after every \ is replaced with \\);
the new value is put back into the config line;
the update line is added to the same temporary file.
The old version is of the configuration file replaced with the new one.
Obviously, the script may change the order of lines in the processed file, but it is assumed that that doesn't matter.
I have a number of files with the same naming scheme. As a sample, four files are called "num_001_001.txt", "num_002_001.txt", "num_002_002.txt", "num_002_003.txt"
The first set of numbers represents which "package" it's from, and the second set of numbers is simply used to distinguish them from one another. So in this example we have one file in package 001, and three files in package 002.
I am writing a windows vista batch command to take all of the files and move them into their own directories, where each directory represents a different package. So I want to move all the files for package 001 into directory "001" and all for 002 into directory "002"
I have successfully written a script that will iterate over all of the txt files and echo them. I have also written a scrip that will move one file into another location, as well as creating the directory if it doesn't exist.
Now I figure that I will need to use substrings, so I used the %var:~start,end% syntax to get them. As a test, I wrote this to verify that I can actually extract the substring and create a directory conditionally
#echo off
set temp=num_001_001.txt
NOT IF exist %temp:~0,7%\
mkdir %temp:~0,7%
And it works. Great.
So then I added the for loop to it.
#echo off
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
echo directory %temp:~0,7%
)
But this is my output:
directory num_002
directory num_002
directory num_002
directory num_002
What's wrong? Does vista not support re-assigning variables in each iteration?
The four files are in my directory, and one of them should create num_001. I put in different files with 003 004 005 and all of it was the last package's name. I'm guessing something's wrong with how I'm setting things.
I have different workarounds to get the job done but I'm baffled why such a simple concept wouldn't work.
Your problem is that the variable get replaced when the batch processor reads the for command, before it is executed.
Try this:
SET temp=Hello, world!
CALL yourbatchfile.bat
And you'll see Hello printed 5 times.
The solution is delayed expansion; you need to first enable it, and then use !temp! instead of %temp%:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
echo directory !temp:~0,7!
)
See here for more details.
Another solution is to move the body of the for loop to a subroutine and call it.
#echo off
FOR /R %%X IN (*.txt) DO call :body %%X
goto :eof
:body
set temp=%~n1
echo directory %temp:~0,7%
goto :eof
Why do this? One reason is that the Windows command processor is greedy about parentheses, and the results may be surprising. I usually run into this when dereferencing variables that contain C:\Program Files (x86).
If the Windows command processor was less greedy, the following code would either print One (1) Two (2) or nothing at all:
#echo off
if "%1" == "yes" (
echo 1 (One)
echo 2 (Two)
)
However, that's not what it does. Either it prints 1 (One 2 (Two), which missing a ), or it prints 2 (Two). The command processor interprets the ) after One as the end of the if statement's body, treats the second echo as if it's outside the if statement, and ignores the final ).
I'm not sure whether this is officially documented, but you can simulate delayed expansion using the call statement:
#echo off
FOR /R %%X IN (*.txt) DO (
set temp=%%~nX
call echo directory %%temp:~0,7%%
)
Doubling the percent signs defers the variable substitution to the second evaluation. However, delayed expansion is much more straightforward.