The Windows command line interpreter features a FOR command, which is able to parse the output of a given command and execute the loop for each line of the output, e.g.:
FOR /F %%i IN ('DIR .') DO echo %i # Outputs each file name
The command (DIR .) is executed in a child command line via cmd /C <command> <command-arguments>, however, the /D parameter is not specified ... this leads to weird behavior if the user has a AutoRun command with output (e.g. echo, or cls).
Is there a way to force FOR to execute the command via cmd /C /D <command> <command-arguments>?
You have run across one of the many design flaws of cmd.exe, and this one has bothered me for quite some time. I'm pretty sure there is no way to suppress AutoRun when FOR /F executes a command.
What makes this especially irritating is that pipes also use CMD /C (one for each side of the pipe), but the designers of the pipe were smart enough to incorporate both the /D and /S options. It is really a shame the designers of FOR /F couldn't have done the same.
I believe your only recourse One option is to be defensive within your AutoRun command definition. I suggest putting all the AutoRun commands within a batch script that has something like the following at the top:
#echo off
if defined AutoRunComplete exit /b
set AutoRunComplete=1
REM Put your AutoRun commands below...
But if you cannot control the AutoRun commands, then I think you are out of luck. Aacini's idea of using a temporary file to get around the problem is an effective and simple solution.
A very simple solution for your problem is use a file in the for /F command instead of a command. This way, we just emulate the internal operation of for /F over a command, but executing each step explicitly: 1. Execute the command and store its output in a temporary text file. 2. Process all lines in the temporary file. 3. Delete the file.
DIR . > TempFile.txt
FOR /F %%i IN (TempFile.txt) DO echo %%i
DEL TempFile.txt
When you have many FOR /F blocks for parsing program output, then it could be useful to add a cmd.exe wrapper.
This wrapper can be installed with
set "comspec=C:\somewhere\cmdWrapper.exe"
FOR /F %%i IN ('DIR .') DO echo %%i
The wrapper itself has to start the original cmd.exe with /D /C.
But the behaviour of the comspec variable itself is a bit strange.
Related
I'm trying to find recursively all "MyApp.exe" apps in "C:\Builds" folder and run the apps with "createdatabase closeimmediately" arguments/parameters.
What I search so far:ForFiles Microsoft docs
Here is the forfiles pattern:
forfiles [/p <Path>] [/m <SearchMask>] [/s] [/c "<Command>"] [/d [{+|-}][{|}]]
Here is what I have:
forfiles /p c:\Builds /s /m MyApp.exe /c "cmd /c start #path" "createdatabase closeimmediately"
If I run above script, it is showing error:
ERROR: Invalid argument/option - 'createdatabase closeimmediately'.Type "FORFILES /?" for usage.
If I run without parameteres, it finds apps correctly and runs, but I need to run with parameters:
forfiles /p c:\Builds /s /m MyApp.exe /c "cmd /c start #path"
How can I run apps with parameters in ForFiles?
I know this was mentioned in the comments, but the comments are becoming too long for me to post a decent comment still, so here is an answer. This should do exactly what you want, it will recursively search for the file and execute if exists.
#echo off
for /r "c:\Builds" %%i in (myapp.exe) do if exist "%%i" "%%i" createdatabase closeimmediately
a slightly different way, find all executables, and launch if the name matches myapp.exe:
for /r "c:\Builds" %%i in (*.exe) do if /I "%%~nxi" == "myapp.exe" "%%I" createdatabase closeimmediately
There are multiple methods possible to search for MyApp.exe in C:\Build and all subfolders and execute the found executable with the two parameters createdatabase and closeimmediately.
The first solution uses command FOR to search for any file matching the wildcard pattern MyApp*.exe in C:\Build and any non-hidden subfolder.
For usage in a batch file:
for /R "C:\Build" %%I in ("MyApp*.exe") do if /I "%%~nxI" == "MyApp.exe" "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /R "C:\Build" %I in ("MyApp*.exe") do #if /I "%~nxI" == "MyApp.exe" "%I" createdatabase closeimmediately
It is necessary that the string inside the round brackets contains at least one * or ? to define a wildcard pattern. Otherwise FOR would not search for files with name MyApp.exe on using just "MyApp.exe" in C:\Build and all its subfolders. It would simply append the string "MyApp.exe" (with the double quotes) to folder path of every folder found in C:\Build folder structure and would assign folder path + "MyApp.exe" to loop variable I and execute the command line referencing the loop variable.
The IF condition is used to make sure that only MyApp.exe is executed and not for example MyAppOther.exe found by chance also by FOR with wildcard pattern MyApp*.exe. The string comparison is done case-insensitive because of /I.
It would be also possible to use a different wildcard pattern like MyApp.exe*. This could reduce the number of false positives. But for security the IF condition should be nevertheless used.
The second solution is using just MyApp.exe and check if a file with that name really exists in the given folder path before executing it.
For usage in a batch file:
for /R "C:\Build" %%I in (MyApp.exe) do if exist "%%I" "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /R "C:\Build" %I in (MyApp.exe) do #if exist "%I" "%I" createdatabase closeimmediately
MyApp.exe is specified in round brackets without being enclosed in " as otherwise the string assigned to loop variable I would be for example C:\Build\"MyApp.exe" and not C:\Build\MyApp.exe. By automatic error correction the string value C:\Build\"MyApp.exe" might also work depending on which string is really used instead of MyApp.exe. But this is not really a safe method and does not work if the string MyApp.exe contains a space, comma, semicolon, or other characters like &()[]{}^=;!'+,`~.
The third solution is using the command DIR for searching for MyApp.exe without a wildcard pattern to find only files with exactly that name and let FOR execute the found executables with that name.
For usage in a batch file:
for /F "delims=" %%I in ('dir "C:\Build\MyApp.exe" /A-D-H /B /S 2^>nul') do "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /F "delims=" %I in ('dir "C:\Build\MyApp.exe" /A-D-H /B /S 2^>nul') do #"%I" createdatabase closeimmediately
In comparison to FOR the command DIR really searches for files with name MyApp.exe even on argument string not containing a wildcard character like * or ?.
FOR executes the DIR command line in a separate command process started with cmd.exe /C in background and captures everything written to handle STDOUT of this command process.
Read also the Microsoft article about Using Command Redirection Operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.
2>nul is used to suppress the error message output by DIR to handle STDERR by redirecting it to device NUL if no file MyApp.exe could be found in C:\Build or its subdirectories.
DIR outputs because of /B and /S just the full qualified file name, i.e. file path + file name + file extension, of every found MyApp.exe line by line.
FOR processes the captured output line by line with skipping empty lines and lines starting with a semicolon. Such lines are surely not output by DIR with the used options.
FOR would also split up each line into substrings (tokens) on spaces/tabs and would assign only first substring to loop variable I. This string splitting behavior is not wanted here as a folder name could contain one or more spaces. For that reason FOR option delims= is used to define an empty list of delimiters which disables the line splitting behavior.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
dir /?
for /?
if /?
I am having a problem on running a script to create empty files in a loop.
This is what have done so far:
#echo off
for /l %%a (1;1;20) do (echo m> ".mp4" c:\test)
pause
exit
Basically I have twenty names in a file on my desktop and I intend to create them as empty *.mp4 files in folder c:\test with the command echo m> .mp4. When I run the code above, it does not seem to work.
The following FOR loop can be used in the batch file to create empty files 1.mp4, 2.mp4, 3.mp4, ..., 20.mp4 in directory C:\test as suggested by rojo:
for /L %%I in (1,1,20) do type NUL >"C:\test\%%I.mp4"
And the next FOR loop can be used in the batch file to read the file names for the empty *.mp4 files to create from a list file on Windows desktop of current user as also suggested by rojo:
for /F "usebackq delims=" %%I in ("%USERPROFILE%\Desktop\List of Names.txt") do type NUL >"C:\test\%%I.mp4"
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
for /?
type /?
Further the Microsoft article Using command redirection operators should be read explaining the redirection operator > and the SS64 page about NUL (null device).
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
Like the title says, I'm trying to take the output from a FIND command and save it to a variable. Specifically, I'm using:
DIR /b /s "C:\" | FIND "someexe.exe"
to locate a specific .exe file, which seems to work fine, but then I want to save the results of FIND to use later in the same script.
I've tried various different tweaks of:
for /f "usebackq" %%i in (`DIR /b /s "C:\" | FIND "someexe.exe"`) do SET foobar=%%i
but when I try to run the script the command window immediately closes (presumably due to some error, I tried putting a PAUSE command in the next line to no avail).
I assume it's some stupid minor thing that I'm doing wrong but if someone could show me what it is I'd appreciate it. Just for further reference, I don't care how many copies of "someexe.exe" exist, I just need the path for one of them.
You should be getting this error: | was unexpected at this time.. Your immediate problem is unquoted special characters like | must be escaped using ^ when they appear in a FOR /F ('command').
for /f "usebackq" %%i in (`DIR /b /s "C:\" ^| FIND "someexe.exe"`) do SET foobar=%%i
It sounds like you are running your batch file by double clicking from either your desktop or Windows Explorer. That works, but then the window immediately closes after the batch terminates. In your case it terminates before reaching PAUSE because of the syntax error.
I always run my batch files from a command window: From the Start menu you want to run cmd.exe. That will open up a command console. Then CD to the directory where your batch file resides and then run the batch file by typing its name (no extension needed). Now the window stays open after the script terminates. You can examine your variables using SET, run another script, whatever.
There is no need to use FIND in your case - DIR can find the file directly. Also, the path of your file may include spaces, in which case it will be parsed into tokens. You need to set "DELIMS=" or "TOKENS=*" so that you get the complete path.
I never understand why people use USEBACKQ when they are executing a command. I only find it useful if I am trying to use FOR /F with a file and I need to enclose the file in quotes because of spaces and/or special characters.
Also, you may run across errors due to inaccessible directories. Redirecting stderr to nul cleans up the output. Here again, the > must be escaped.
for /f "delims=" %%F in ('dir /b /s "c:\someexe.exe" 2^>nul') do set foobar=%%F
I was looking for a batch script to identify the newest file in a directory. The examples I found all use FOR /F. When I read the help documentation on FOR, it states that /F opens, reads and processes each file.
Why is /F used in this case? I've used it with large binary files and the script does not seem to slow down so I do not think each file is actually being opened, etc.
I tried using FOR without /F to do the same job and didn't have any luck. Is there a reason for that?
For instance:
FOR %%I IN ('dir "*.AVI" /B /O:D') DO set newestAvi=%%I
does not seem to work. For some reason, newestAvi is equal to "/O:D'" at the end.
If I use
FOR /F "delims=|" %%I IN ('dir "*.AVI" /B /O:D') DO set newestAvi=%%I
then things work.
Thanks,
Dave
I think the relevant bit of the help file is
Finally, you can use the FOR /F command to parse the output of a
command. You do this by making the filenameset between the
parenthesis a back quoted string. It will be treated as a command
line, which is passed to a child CMD.EXE and the output is captured
into memory and parsed as if it was a file. So the following
example:
So, with the /F your command takes the output from
dir "*.AVI" /B /O:D
and parses each line into the command
newestAvi=%%I
which becomes
newestAvi=FileName.AVI
for each file in the current directory. The last value assigned is the one that is left at the end of the for commands execution.