CMD for-loop, while executing via default program, and piping - for-loop

I have a directory filled with .nc files. These files have to be executed using a program called 'ncdump', which is set as the default program to open the file. It can then be executed as (output to file):
file.nc > output.txt
The file is somehow executed here. Now I want to do this for all the files in the directory using a for-loop. I tried the below:
for /r %i in (*) do cmd i% > scriptout.txt
And of course a way of numbering the outfile (with a counter no doubt) would be nice. This is probably very basic stuff but I'm rather unfamiliar with CMD (and Windows as a whole). Any help is much appreciated.

echo off
setlocal enabledelayedexpansion
set count=0
for /r %%i in (*.nc) do (
set count+=1
%%i >scriptout!count!.txt
)
echo %count% files processed.
some common traps:
in batchfiles use %%i (on command line only %i)
to use a variable inside a block (between ( and )) you have to use delayed expansion (setlocal enabledelayedexpansion to enable it and writing the variables !var! instead of %var%)
the opening parantheses (() has to be on the same line than do .

Related

How to not lose special characters when processing a text file in a bat?

I have this text document (txt1.txt):
&()[]{}^=;!'+,`~.mp3
¿¡áéíóú!.mp3
When processing it with a FOR command I lose characters like "^" and "!".
The bat file is as follows (it has two FOR commands that maybe can be simplified into just one, and the second one has an ECHO command at the end that I don't know if it can be implemented better):
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
CHCP 1252
SET TXTFILE1=txt1.txt
SET TXTFILE2=txt2.txt
(
FOR /F "usebackq delims=" %%I IN (%TXTFILE1%) DO (
SET LINE=%%I
ECHO !LINE:.mp3=!
)
) > %TXTFILE2%
SET LINENUMBER=1
SET TXTFILE3=txt3.bat.txt
FOR /F "usebackq eol=| delims=" %%J IN (%TXTFILE2%) DO (
ECHO SET TRACK!LINENUMBER!=%%J>> %TXTFILE3%
SET /A LINENUMBER+=1
)
ECHO EXIT /B>> %TXTFILE3%
ENDLOCAL
PAUSE > NUL | SET /P =Presione una tecla para salir . . . & ECHO(
EXIT
Use this code for the batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFile=txt1.txt"
set "OutputFile=txt2.txt"
set "LineNumber=0"
(
for /F usebackq^ delims^=^ eol^= %%I in ("%SourceFile%") do (
set "Line=%%I"
set /A LineNumber+=1
setlocal EnableDelayedExpansion
echo set TRACK!LineNumber!=!Line:.mp3=!
endlocal
)
) >"%OutputFile%"
endlocal
For an explanation of the FOR loop see my answer on How to read and print contents of text file line by line? Empty lines in source file are ignored by FOR which should be no problem here as it looks like the source file contains a list of MP3 file names.
The question does not contain an example content of source file and an example of expected content of output file. So the code was tested with a source file created by myself which are the lines as posted at How to read and print contents of text file line by line? with some lines with .mp3 appended and one line with ^ added.
If all lines end with .mp3 it would be better to use:
echo set TRACK!LineNumber!=!Line:~0,-4!
The line read from file is in this case output without the last four characters.
It is important to have delayed expansion NOT enabled on assigning the line read from file to environment variable Line. Otherwise this line is parsed by cmd.exe a second time as explained by How does the Windows Command Interpreter (CMD.EXE) parse scripts? On second parsing of SET LINE=%%I each ^ in line read from file is interpreted as escape character and each ! is interpreted as begin/end of an environment variable reference.
For that reason it is necessary to assign first the line read from file to environment variable Line while delayed expansion is disabled to avoid parsing the line before execution command SET by Windows command processor. Then delayed environment variable expansion is enabled which results in creating a copy of current list of environment variables, pushing current directory path on stack and pushing also current states of command extensions and delayed environment variable expansion on stack before enabling also delayed expansion. Then the line can be output with the additional text at beginning with line number and with case-insensitive removing all .mp3 occurrences from line. Finally command ENDLOCAL must be used to discard the copy of all environment variables, pop current directory path back from stack and pop also the states of command extensions and delayed expansion from stack and set those two features accordingly which means here disabling delayed expansion. Read this answer for details about the commands SETLOCAL and ENDLOCAL.
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.
echo /?
endlocal /?
for /?
set /?
setlocal /?

use cd in windows .bat file has no effect

i want to run git pull in a path for all projects, so i wrote a bat file.
#echo off
setlocal enabledelayedexpansion
set dir=%1
if "%dir%"=="" (
set dir=%~dp0
)
for /D /R "%dir%" %%i in (*) do (
if exist %%i\.git (
cd /d %%i
rem no effect
echo %cd%
rem git pull
)
)
pause
but is seems that cd in for loop does not take any effect, i don't know why.
can someone help me solve this problem?enter code here
It has effect. But, in batch files, when a block of code (the code enclosed in parenthesis) is reached (the same for a line out of a block), variable reads are replaced with the value of the variable before executing the code in the block. So, when your for command is reached, the read of the %cd% variable is replaced with the value of the %cd% variable before executing the code. This speeds and simplifies execution of the code but generate this kind of problems.
You can enable delayed expansion with setlocal enabledelayedexpansion command, and change the sintax from %cd% to !cd!. This tells cmd that that this variable read should be delayed until execution of the line.

Windows .Bat file behave differently when executed from command window and by double clicking on the bat file

Windows .Bat file behave differently when executed from command window and by double clicking on the bat file. This is my file:
ECHO ON
del activity_ftp.log
cd D:\My_Test
IF EXIST united_ops*.csv (
for %%i in (united_ops*.csv) do (
set size=0
set /A size=%%~zi
echo %%i,%size%,397312,624640 > D:\My_Test\activity_ftp.log
)
)
When I run this by opening command window and calling it,
There are some issues in your code.
cd d:\My_test will only work if you are on D:, you could use cd /d or pushd here.
echo ...%size% doesn't work, as it's expands when the for block is parsed not when it's executed.
The if exist seems to be redundant, as the for %%i in ( united_ops*.csv) only expands if any file exists.
ECHO ON
setlocal EnableDelayedExpansion
del activity_ftp.log
pushd D:\My_Test
for %%i in (united_ops*.csv) do (
set size=0
set /A size=%%~zi
echo %%i,!size!,397312,624640 > D:\My_Test\activity_ftp.log
)
Building on jeb's answer.
1) Your FOR loop may iterate through many files that match your pattern. But you use the overwrite mode of file redirection. Each found file will over-write the output for the prior file. Your final output file will never have more than one line. You could change to the append mode using >>, but there is a better way. It is faster to enclose the entire loop in parentheses and redirect once in overwrite mode using >.
2) You are setting size to 0, then setting it to the file size, and then you don't use it after the line is echoed. I suspect you don't need the variable at all, so you don't need delayed expansion.
3) The file you delete at the top does not include the path information, so it may not be deleting from the correct folder. Even if it were, it is unnecessary since you are redirecting in overwrite mode anyway.
4) Instead of changing the current directory you could include the path in the FOR statement.
ECHO ON
>"D:\My_Test\activity_ftp.log" (
for %%i in ("d:\My_Test\united_ops*.csv") do (
echo %%~nxi,%%~zi,397312,624640
)
)

DOS Batch: Getting partial file names

I've got this little batch file I'm trying to write for a Windows 7 environment. It's supposed to go through a directory and echo the last four characters of the file names. So far I've got:
#ECHO OFF
SETLOCAL
for /r C:\Users\userName\Desktop\testFolder %%g in (*) do (
Set fileName = %%~ng
echo %fileName:~-4%
)
And all that is echoed out is "~-4" once for each file in testFolder. I can't figure out what's wrong, but then I'm not very well versed in batch files or dos. Any help would be appreciated, thanks.
Environment variable expansion occurs when the command is read, so your %fileName:~-4% is evaulated when the for is read, which is before the Set is performed. Use delayed expansion.
#ECHO OFF
SETLOCAL SETDELAYEDEXPANSION
for /r C:\Users\userName\Desktop\testFolder %%g in (*) do (
Set fileName=%%~ng
echo !fileName:~-4!
)
Note also that spaces are significant in the Set command. With the space, you created a variable called fileName  with a trailing space.

Drag and drop batch file for multiple files?

I wrote a batch file to use PngCrush to optimize a .png image when I drag and drop it onto the batch file.
In the what's next section, I wrote about what I thought would be a good upgrade to the batch file.
My question is: is it possible to create a batch file like I did in the post, but capable of optimizing multiple images at once? Drag and drop multiple .png files on it? (and have the output be something like new.png, new(1).png, new(2).png, etc...
Yes, of course this is possible. When dragging multiple files on a batch file you get the list of dropped files as a space-separated list. You can verify this with the simple following batch:
#echo %*
#pause
Now you have two options:
PngCrush can already handle multiple file names given to it on the command line. In this case all you'd have to do would be to pass %* to PngCrush instead of just %1 (as you probably do now):
#pngcrush %*
%* contains all arguments to the batch file, so this is a convenient way to pass all arguments to another program. Careful with files named like PngCrush options, though. UNIX geeks will know that problem :-)
After reading your post describing your technique, however, this won't work properly as you are writing the compressed file to new.png. A bad idea if you're handling multiple files at once as there can be only one new.png :-). But I just tried out that PngCrush handles multiple files just well, so if you don't mind an in-place update of the files then putting
#pngcrush -reduce -brute %*
into your batch will do the job (following your original article).
PngCrush will not handle multiple files or you want to write each image to a new file after compression. In this case you stick with your "one file at a time" routine but you loop over the input arguments. In this case, it's easiest to just build a little loop and shift the arguments each time you process one:
#echo off
if [%1]==[] goto :eof
:loop
pngcrush -reduce -brute %1 "%~dpn1_new%~x1"
shift
if not [%1]==[] goto loop
What we're doing here is simple: First we skip the entire batch if it is run without arguments, then we define a label to jump to: loop. Inside we simply run PngCrush on the first argument, giving the compressed file a new name. You may want to read up on the path dissection syntax I used here in help call. Basically what I'm doing here is name the file exactly as before; I just stick "_new" to the end of the file name (before the extension). %~dpn1 expands to drive, path and file name (without extension), while %~x1 expands to the extension, including the dot.
ETA: Eep, I just read your desired output with new.png, new(1).png, etc. In this case we don't need any fancy path dissections but we have other problems to care about.
The easiest way would probably be to just start a counter at 0 before we process the first file and increment it each time we process another one:
#echo off
if [%1]==[] goto :eof
set n=0
:loop
if %n%==0 (
pngcrush -reduce -brute %1 new.png
) else (
pngcrush -reduce -brute %1 new^(%n%^).png
)
shift
set /a n+=1
if not [%1]==[] goto loop
%n% is our counter here and we handle the case where n is 0 by writing the result to new.png, instead of new(0).png.
This approach has problems, though. If there are already files named new.png or new(x).png then you will probably clobber them. Not nice. So we have to do something different and check whether we can actually use the file names:
rem check for new.png
if exist new.png (set n=1) else (set n=0 & goto loop)
rem check for numbered new(x).png
:checkloop
if not exist new^(%n%^).png goto loop
set /a n+=1
goto checkloop
The rest of the program stays the same, including the normal loop. But now we start at the first unused file name and avoid overwriting files that are already there.
Feel free to adapt as needed.
To do Drag & Drop in a secure way, isn't so simple with batch.
Dealing with %1, shift or %* could fail, because the explorer is not very smart, while quoting the filenames, only filenames with spaces are quoted.
But files like Cool&stuff.png are not quoted by the explorer so you get a cmdline like
pngCr.bat Cool&stuff.png
So in %1 is only Cool even in %* is only Cool, but after the batch ends, cmd.exe tries to execute a stuff.png (and will fail).
To handle this you could access the parameters with !cmdcmdline! instead of %1 .. %n,
and to bypass a potential error at the end of execution, a simple exit could help.
#echo off
setlocal ENABLEDELAYEDEXPANSION
rem Take the cmd-line, remove all until the first parameter
set "params=!cmdcmdline:~0,-1!"
set "params=!params:*" =!"
set count=0
rem Split the parameters on spaces but respect the quotes
for %%G IN (!params!) do (
set /a count+=1
set "item_!count!=%%~G"
rem echo !count! %%~G
)
rem list the parameters
for /L %%n in (1,1,!count!) DO (
echo %%n #!item_%%n!#
)
pause
REM ** The exit is important, so the cmd.ex doesn't try to execute commands after ampersands
exit
Btw. there is a line limit for drag&drop operations of ~2048 characters, in spite of the "standard" batch line limit of ~8192 characters.
As for each file the complete path is passed, this limit can be reached with few files.
FOR %%A IN (%*) DO (
REM Now your batch file handles %%A instead of %1
REM No need to use SHIFT anymore.
ECHO %%A
)
And to differentiate between dropped files and folders, you can use this:
FOR %%I IN (%*) DO (
ECHO.%%~aI | FIND "d" >NUL
IF ERRORLEVEL 1 (
REM Processing Dropped Files
CALL :_jobF "%%~fI"
) ELSE (
REM Processing Dropped Folders
CALL :_jobD "%%~fI"
)
)
This is a very late answer, Actually I was not aware of this old question and prepared an answer for this similar one where there was a discussion about handling file names with special characters because explorer only quotes file names that contain space(s). Then in the comments on that question I saw a reference to this thread, after that and not to my sureprise I realized that jeb have already covered and explained this matter very well, which is expected of him.
So without any further explanations I will contribute my solution with the main focus to cover more special cases in file names with this ,;!^ characters and also to provide a mechanism to guess if the batch file is directly launched by explorer or not, so the old fashion logic for handling batch file arguments could be used in all cases.
#echo off
setlocal DisableDelayedExpansion
if "%~1" EQU "/DontCheckDrapDrop" (
shift
) else (
call :IsDragDrop && (
call "%~f0" /DontCheckDrapDrop %%#*%%
exit
)
)
:: Process batch file arguments as you normally do
setlocal EnableDelayedExpansion
echo cmdcmdline=!cmdcmdline!
endlocal
echo,
echo %%*=%*
echo,
if defined #* echo #*=%#*%
echo,
echo %%1="%~1"
echo %%2="%~2"
echo %%3="%~3"
echo %%4="%~4"
echo %%5="%~5"
echo %%6="%~6"
echo %%7="%~7"
echo %%8="%~8"
echo %%9="%~9"
pause
exit /b
:: IsDragDrop routine
:: Checks if the batch file is directly lanched through Windows Explorer
:: then Processes batch file arguments which are passed by Drag'n'Drop,
:: rebuilds a safe variant of the arguments list suitable to be passed and processed
:: in a batch script and returns the processed args in the environment variable
:: that is specified by the caller or uses #* as default variable if non is specified.
:: ErrorLevel: 0 - If launched through explorer. 1 - Otherwise (Will not parse arguments)
:IsDragDrop [retVar=#*]
setlocal
set "Esc="
set "ParentDelayIsOff=!"
setlocal DisableDelayedExpansion
if "%~1"=="" (set "ret=#*") else set "ret=%~1"
set "Args="
set "qsub=?"
:: Used for emphasis purposes
set "SPACE= "
setlocal EnableDelayedExpansion
set "cmdline=!cmdcmdline!"
set ^"ExplorerCheck=!cmdline:%SystemRoot%\system32\cmd.exe /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" (
set ^"ExplorerCheck=!cmdline:"%SystemRoot%\system32\cmd.exe" /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" exit /b 1
)
set "ExplorerCheck="
set ^"cmdline=!cmdline:*"%~f0"=!^"
set "cmdline=!cmdline:~0,-1!"
if defined cmdline (
if not defined ParentDelayIsOff (
if "!cmdline!" NEQ "!cmdline:*!=!" set "Esc=1"
)
set ^"cmdline=!cmdline:"=%qsub%!"
)
(
endlocal & set "Esc=%Esc%"
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if not defined cmdline endlocal & endlocal & set "%ret%=" & exit /b 0
:IsDragDrop.ParseArgs
if "%cmdline:~0,1%"=="%qsub%" (set "dlm=%qsub%") else set "dlm= "
:: Using '%%?' as FOR /F variable to not mess with the file names that contain '%'
for /F "delims=%dlm%" %%? in ("%cmdline%") do (
set ^"Args=%Args% "%%?"^"
setlocal EnableDelayedExpansion
set "cmdline=!cmdline:*%dlm: =%%%?%dlm: =%=!"
)
(
endlocal
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if defined cmdline goto :IsDragDrop.ParseArgs
if defined Esc (
set ^"Args=%Args:^=^^%^"
)
if defined Esc (
set ^"Args=%Args:!=^!%^"
)
(
endlocal & endlocal
set ^"%ret%=%Args%^"
exit /b 0
)
OUTPUT with sample files dragged and dropped onto the batch file:
cmdcmdline=C:\Windows\system32\cmd.exe /c ""Q:\DragDrop\DragDrop.cmd" Q:\DragDrop\ab.txt "Q:\DragDrop\c d.txt" Q:\DragDrop\!ab!c.txt "Q:\DragDrop\a b.txt" Q:\DragDrop\a!b.txt Q:\DragDrop\a&b.txt Q:\DragDrop\a(b&^)).txt Q:\DragDrop\a,b;c!d&e^f!!.txt Q:\DragDrop\a;b.txt"
%*=/DontCheckDrapDrop "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
#*= "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
%1="Q:\DragDrop\ab.txt"
%2="Q:\DragDrop\c d.txt"
%3="Q:\DragDrop\!ab!c.txt"
%4="Q:\DragDrop\a b.txt"
%5="Q:\DragDrop\a!b.txt"
%6="Q:\DragDrop\a&b.txt"
%7="Q:\DragDrop\a(b&^)).txt"
%8="Q:\DragDrop\a,b;c!d&e^f!!.txt"
%9="Q:\DragDrop\a;b.txt"
In :IsDragDrop routine I specially tried to minimize the assumptions about command line format and spacing between the arguments. The detection (guess) for explorer launch is based on this command line signature %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments"
So it is very possible to fool the code into thinking it has launched by double click from explorer or by drag'n'drop and that's not an issue and the batch file will function normally.
But with this particular signature it is not possible to intentionally launch batch file this way: %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments & SomeOtherCommand" and expect that the SomeOtherCommand to be executed, instead it will be merged into the batch file arguments.
You don't need a batch script to optimize multiple PNGs, all you need is the wildcard:
pngcrush -d "crushed" *.png
That will pngcrush all PNGs in the current dir and move them to a sub-dir named "crushed". I would add the -brute flag to likely shave off a few more bytes.
pngcrush -d "crushed" -brute *.png
I'm posting this because it doesn't seem to be well documented or widely known, and because it may be easier for you in the long run than writing and maintaining a drag and drop batch file.

Resources