new to Batch script, what exactly this for loop do - windows

I have a bactch script which I am trying to understand, as I am new to batch programming and have to customize this code. but I can't understand what the subroutines check_utf8_bom,create_utf8bom_free_file,remove_utf8 are actually doing. Can someone please help
set /p bom=<.\bom
for /f "delims=" %%G in (.\file-list.txt) do (
call:check_utf8_bom %bom% !SOURCE_FOLDER!
)
:check_utf8_bom
rem ;; Checks If There Is The UTF-8 Bom At The Beginning Of The File
set /p firstLine=<%2\tmp
if "%firstLine:~0,3%"=="%~1" call:create_utf8bom_free_file %2
goto:eof
:create_utf8bom_free_file
rem ;; Remove UTF-8 BOM From "tmp" File o Avoid Problems During Interpretation
type %1\tmp>%1\tmp.bom
for /f "delims=" %%G in (%1\tmp.bom) do (
if defined i echo.%%G>>%1\tmp
if not defined i (
call:remove_utf8_bom "%%G" %1
set i=1
)
)
del %1\tmp.bom
goto:eof
:remove_utf8_bom
rem ;; Called From create_utf8bom_free_file Function Create The File Without The BOM In The First line
set fl=%~1
echo %fl:~3,-1%%fl:~-1%>"%2\tmp"
goto:eof
can somebody please help me to understand it?

%1 stands for the first argument passed to the script / :create_utf8bom_free_file subroutine.
type %1\tmp>%1\tmp.bom this prints file tmp from %1 directory to tmp.bom file - this should convert unicode file to ascii one (probaly without changing byte order mark )
for /f "delims=" %%G in (%1\tmp.bom) do - means read %1\tmp.bom line by line without splitting the line with delimiters ("delims=") so on each iteration the line will be assigned to %%G token (temporary variable that lives during FOR /F execution).
if not defined i and set i=1 is a workaround for missing break operator in batch file loops . The first checks if the variable is defined and the second sets value to the variable. So the first line is passed to :remove_utf8_bom (here the first two characters of the line should be removed) function and then the for loop is over.
At the end temp file is deleted and goto:eof means go to the end of the script - i.e. something similar to exit.

It's not really clear what the script does because you didn't post the remove_utf8_bom procedure. So far I can tell you that the the loop reads the content of the file tmp.bom line by line. In each iteration it checks whether the variable i is set or not. If it is, the currently processed line is appended to the file tmp. Otherwise the current line as well as the parameter %1 both are passed to the procedure remove_utf8_bom (which we don't know) and the variable i is being set to 1.
This is - as you've asked - what the for-loop does (without the lines above and below it). For more information I need more code.
EDIT:
By the way...even without the code I'd suppose that this script should remove the BOM from UTF-8 encoded text files. This is often needed because if a batch or cmd file is stored in UTF-8 there is a BOM at the beginning of it which causes that the first line in the script won't be executed.
To eliminate this problem you should avoid saving scripts as UTF-8. If you can't be sure about the encoding, you also could start scripts with REM doesn't matter what text is here as this line will be ignored. The rest of the script will be executed as desired.

Related

Remove space at last Property value of WMIC and add another string

Sorry for my bad English.
I want to remove space at the last Property value of WMIC and add another string.
del /f /q "GPU.txt"
for /f "skip=2 tokens=2,3,4 delims=," %%a in ('"wmic path Win32_VideoController get Caption,CurrentHorizontalResolution,CurrentVerticalResolution /format:csv"') do (
SETLOCAL EnableDelayedExpansion
For %%# in (*) do (
SET var=%%~n#
Set MyVar=!var!
set MyVar=!MyVar: =!
)
echo %%a (%%b x !MyVar!)>>"GPU.txt"
)
Nothing to display.
Thanks.
Why does the code in question not work?
Please read this answer for details about the commands SETLOCAL and ENDLOCAL. On using SETLOCAL inside a FOR loop it is highly recommended to use also ENDLOCAL in same FOR loop or a stack overflow could occur during execution of the loop. This does not occur here because there are not many loop iterations, but ENDLOCAL should be used nevertheless in same FOR loop containing also SETLOCAL.
For %%# in (*) do in code of question processes each file name of non-hidden files in current directory. The file name without file extension is assigned to environment variable var. Next this variable is assigned to another variable MyVar without any modification, except the line Set MyVar=!var! would contain trailing spaces or tabs. And last all spaces are removed from string value of environment variable MyVar which is a file name.
I have no idea what this FOR loop for processing file names in current directory has to do with name of video controller and current horizontal and vertical resolution.
I recommend reading answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The closing round bracket ) in command line echo %%a (%%b x !MyVar!)>>"GPU.txt" is interpreted as end of command block of first FOR. It would be necessary to escape ) with ^ and use echo %%a (%%b x !MyVar!^)>>"GPU.txt" to get closing parenthesis interpreted by cmd.exe as literal character to output by echo on parsing the entire command block starting with ( on first FOR command line.
wmic.exe outputs the text UTF-16 Little Endian (two bytes per character) instead of ANSI encoded (one byte per character). FOR respectively cmd.exe has a bug on interpreting the Unicode encoded character stream as explained for example in detail at How to correct variable overwriting misbehavior when parsing output?
What you think is a space at end of vertical resolution value is in real a carriage return appended to this value because of wrong processing of Unicode output of wmic.exe by command FOR respectively cmd.exe.
What is a working code and why does it work?
One possible solution for this task is using this batch code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
(for /F "tokens=2-4 delims=," %%I in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_VideoController GET Caption^,CurrentHorizontalResolution^,CurrentVerticalResolution /FORMAT:CSV ^| %SystemRoot%\System32\findstr.exe /R ",[0123456789][0123456789]*$"') do (
set /A CurrentVerticalResolution=%%K
call echo %%I (%%J x %%CurrentVerticalResolution%%^)
))>"GPU.txt"
endlocal
The Unicode output of Windows Management Instrumentation Command is redirected to FINDSTR which filters the output on lines ending with a comma and one or more digits. So heading line of CSV output is removed and also video controller lines ending with ,, without values for current horizontal and vertical resolution. Such a CSV line is output also on my Windows computer in addition to the CSV line with the correct values.
The problem with wrong carriage return at end of last value of current vertical resolution remains despite filtering output of wmic.exe with findstr.exe.
For that reason the current vertical resolution value assigned to loop variable K is not used directly in echo command line because this would result in ) overwriting first character of video controller caption string. The solution used here is using an arithmetic expression to assign the current vertical resolution value to the environment variable CurrentVerticalResolution. The carriage return at end of the integer value is interpreted like any other whitespace character at end of an expression on evaluation of the arithmetic expression which means it is ignored by cmd.exe. So environment variable CurrentVerticalResolution has assigned the value without the unwanted carriage return at end.
The code above avoids usage of SETLOCAL and ENDLOCAL by using command CALL to parse the echo command line twice as explained by:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
The echo command line with escaped ) and with %%CurrentVerticalResolution%% instead of %CurrentVerticalResolution% is already modified to the line below on first parsing the echo command line by cmd.exe before executing command FOR:
call echo %I (%J x %CurrentVerticalResolution%)
The remaining environment variable reference %CurrentVerticalResolution% is replaced by current value of this environment variable before executing command ECHO on second parsing on each iteration of the loop because of command CALL.
It would be also possible to use inside the FOR loop:
setlocal EnableDelayedExpansion
echo %%I (%%J x !CurrentVerticalResolution!^)
endlocal

Batch File - Read specific line, and save a specific string in that line as a variable

Is there any way to get for /f loop (or anything else) to read a specific line?
Here is the code I have so far, it reads first word of every line.
#echo off
set file=readtest.txt
for /f "tokens=1 delims= " %%A in (%file%) do (echo %%A)
pause
If someone can point me in the right direction, it'd be much appreciated.
Thanks
Additional Information: I want to make a batch file which will rename a TXT file to a string within that TXT file, located at a specific location. I have figured out how to rename files, all I need to learn to do is to retrieve a string (located at a specific location) with in the file which will go into the name of that TXT file.
Since you haven't fully defined what you mean by "a specific location", I'll make some (reasonable, in my opinion) assumptions, though the method I present is equally valid no matter what your definition turns out to be.
You can get arbitrary lines and arbitrary words on that line by using a line counter variable in conjunction with tokens.
Let's assume your text file name can be found as the second argument on the fourth line of the infile.txt file. You can get that with something like:
#setlocal enableextensions enabledelayedexpansion
#echo off
set /a "line = 0"
for /f "tokens=2 delims= " %%a in (infile.txt) do (
set /a "line = line + 1"
if !line!==4 set thing=%%a
)
endlocal & set thing=%thing%
echo %thing%
This actually uses a few "tricks" which warrant further explanation:
the line counter to ensure you only grab what you want from a specific line, though you could change the test !line!==4 into anything you need such as a line beginning with #, the fifth line containing the string xyzzy and so on.
the use of setlocal/endlocal to effectively give you a scope from which variables cannot leak. This is good programming practice even for a language often not normally associated with such things :-)
the use of endlocal & set to bypass that scope so that thing is the only thing that does actually leak (as it should).
the use of delayed expansion and !..! variables to ensure they're correct within the for loop. Without this, the %..% will always be expand to the value they were set to when the for loop started.
Those last two bullet points are actually related. %..% variables are expanded when the command is read rather than when it is executed.
For a for loop, the command is the entire thing from the for to the final ). That means, if you use %line% within the loop, that will be evaluated before the loop starts running, which will result in it always being 0 (the variable itself may change but the expansion of it has already happened). However, !line! will be evaluated each time it is encountered within the loop so will have the correct value.
Similarly, while endlocal would normally clear out all variables created after the setlocal, the command:
endlocal & set thing=%thing%
is a single command in the context of expansion. The %thing% is expanded before endlocal is run, meaning it effectively becomes:
endlocal & set thing=whatever_thing_was_set_to_before_endlocal
That's why the use of setlocal and endlocal & set is a very useful way to limit variables "escaping" from a scope. And, yes, you can chain multiple & set stanzas to allow more variables to escape the scope.

cmd for loop mass renaming again oneliner

I'm over my head with this - spent too much time searching already - evidently I don't understand the basics of CMD variables etc. - and it always gives me such a headache
why wouldn't this work?
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
the above code outputs the value of %tmpx% in some other scope - and it is always constant
yes, i run setlocal ENABLEDELAYEDEXPANSION
basically i need to do a simple rename of all files in folder from constantstring_somenameXX.tif to somenameXX.tif, where i.e. constantstring=0000000005
i had to use set because other posts rightly suggested that %a in a for loop has a special behaviour, and the substitutions wouldn't work for it as it is.
i would prefer not to use scripts and/or powershell - unless not using them is impossible
thank you
for %a in (*) do ( set tmpx=%a & echo %tmpx% )
The problem with the previous code is delayed expansion. Yes, you enabled it, but you have not used it, and depending on how you enabled it, it will not work
In cmd, when a line or block of lines (code inside parenthesis) is reached, it is first parsed and then executed. During the parse phase, variable read operations are removed from the command, replaced with the value in the variable before the command starts to execute. So, if you change the value of a variable inside a line/block you can not retrieve the changed value inside the same line/block as there are no variable reads (they were replaced)
setlocal enabledelayedexpansion allows to replace (where needed) the variable read syntax from %var% to !var!, indicating to the parser that the read operation will be delayed until the execution phase.
So, in your case, your code should have been something like
setlocal enabledelayedexpansion & for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )
BUT this will not work (in default configured environments).
cmd has two execution modes: batch file and command line. In your case, you are using command line (no escaped percent sign in for loop) and in command line mode the setlocal enabledelayedexpansion will not work. It is intended for batch files (see setlocal /?)
How to make it work from the command line? By default cmd is started with delayed expansion disabled and you can not enable it if not inside a batch file. But you can start cmd with delayed expansion enabled and run your command in this started instance (see cmd /?)
cmd /v:on /c "for %a in (*) do ( set "tmpx=%a" & echo !tmpx! )"
Anyway, to solve your rename problem, delayed expansion is not needed
for %a in (*_*.tif) do for /f "tokens=1,* delims=_" %b in ("%~nxa") do echo ren "%a" "%c"
That is, for each tif file with an underscore, take the name and extension of the file (%~nxa) as a string, and using the underscore as a delimiter between tokens, retrieve the first token (the text on the left of the first underscore) in %b and the rest of the text (to the right of the underscore) into %c. Now, just rename the original file name (stored in %a) to the contents of %c (the text on the right of the underscore)
In this code rename operations are only echoed to console. If the output is correct, remove the echo command.
! is the character to use rather than % when wanting execution time value. % does when it's read value.
CMD was written by IBM engineers and they were trying to make MSDos a programming language while making sure Dos commands ran the same. So we get a hodge podge.
& seperates commands on a line.
&& executes this command only if previous command's errorlevel is 0.
|| (not used above) executes this command only if previous command's errorlevel is NOT 0
> output to a file
>> append output to a file
< input from a file
| output of one command into the input of another command
^ escapes any of the above, including itself, if needed to be passed to a program
" parameters with spaces must be enclosed in quotes
+ used with copy to concatinate files. E.G. copy file1+file2 newfile
, used with copy to indicate missing parameters. This updates the files modified date. E.G. copy /b file1,,
%variablename% a inbuilt or user set environmental variable
!variablename! a user set environmental variable expanded at execution time, turned with SelLocal EnableDelayedExpansion command
%<number> (%1) the nth command line parameter passed to a batch file. %0 is the batchfile's name.
%* (%*) the entire command line.
%<a letter> or %%<a letter> (%A or %%A) the variable in a for loop. Single % sign at command prompt and double % sign in a batch file.

Windows: copy a file until the file not exist

I want to use a Windows batch file in to copy a file (myfile0001.bdg) from one specific directory to another. But I want to check if the file in the target directory exists and if the answer is yes, increment the file with 0001 and check again if the file exists (myfile0002.bdg) and so on, until the file does not exist, and copy the file with the new title.
So, if in the target directory, I have these files:
myfile0001.bdg
myfile0002.bdg
myfile0003.bdg
myfile0004.bdg
myfile0005.bdg
myfile0006.bdg
The new file should be named myfile0007.bdg. The next time I will execute the batch, the new file will be myfile0008.bdg, etc.
I know there is a command "IF EXIST" but I don't know to do what I need.
==============
I'm under Windows 7 x32
The source directory is "C:\USERS\RAMBYTES\DOCUMENTS\"
The target directory is "P:\BACKUP\"
The file is "MYFILE0001.BDG"
Something like this:
#echo off
set source_file=C:\USERS\RAMBYTES\DOCUMENTS\MYFILE0001.BDG
set target_dir=P:\BACKUP\
set done=0
for /l %%i in (1,1,1000) do (
call :check_and_copy %%i
if errorlevel 1 goto :eof
)
goto :eof
:check_and_copy
setlocal EnableDelayedExpansion
set num=000000%1
set fnum=!num:~-4!
set fname=%target_dir%\myfile%fnum%.bdg
rem echo %fname%
if not exist "%fname%" (
echo copying %source_file% to %fname%
exit /b 1
)
exit /b 0
There is no error handling in case there are more than a 1000 files present in the target directory. If you want to increas the file limit, you need to adjust the "main" for loop and the "formatting" of the number in the sub-program
The trick with adding the leading zeros was taken from here: https://stackoverflow.com/a/9430912/330315
#ECHO OFF
SET destdir=c:\destdir
SET newname=myfile0000
FOR /f %%i IN (' dir /b /on %destdir%\myfile????.bdg ' ) DO SET newname=%%~ni
SET newname=1%newname:~-4%
SET /a newname+=1
SET newname=myfile%newname:~-4%.bdg
COPY myfile0001.bdg %destdir%\%newname%
change the destination directory as desired, and include the source directory if required.
Take the file name.
Extract the numeric part.
Check if the corresponding target name exists.
If so,
4.1) increase the numeric part;
4.2) if it doesn't exceed the highest possible number go to Step 3;
4.3) otherwise terminate.
If the target name doesn't exist, copy the file with the current numeric part and terminate.
Although algorithmically the condition in 4.2 may be more natural to be checked just after increasing the numeric part, like I put it above, the below script performs the check at a different point, at the beginning of the loop, which starts just after extracting the original numeric value from the source filename. Implementationally, that seemed to me more convenient.
In all other respects, the script implements the same algorithm:
#ECHO OFF
SET "fname=%~n1"
SET counter=1%fname:~-4%
:loop
IF %counter% GTR 19999 (
1>&2 ECHO Cannot copy the file: no free slots.
EXIT /B 1
)
SET "targetname=%~2\%fname:~0,-4%%counter:~1%%~x1"
IF EXIST "%targetname%" (
SET /A counter+=1
GOTO loop
) ELSE (
COPY %1 "%targetname%"
)
To explain some parts:
The tilde (~) in references to positional parameters means de-quoting of the correspondent parameter.
Sometimes in the script, the tilde is also directly followed by a modifier. Two modifiers are used here, n and x. The former causes the parameter to expand to the corresponding file name only (without the path and the extension) and the latter extract only the extension.
You can learn more about modifier in the built-in of the FOR command (by running
The fname environment variable is needed because extracting of name parts can only be done on environment variables. The %fname:~-4 expression, in particular, evaluates to the last four characters of the fname value. More specifically, it reads: extract the substring that starts at the 4th character from the end and, as -4 isn't followed by another argument, includes all the characters from that point till the end of the string.
Another similar-looking expression, %fname:~0,-4%, does the opposite: it returns the contents of fname except the last four characters. The meaning of the numbers is this: extract the substring that starts at the beginning of the string (offset 0) and spans the range up to and including the character at the offset of 4 from the end.
One more expression of this kind, %counter:~1, extracts the characters starting from the second one (i.e. offset 1) and up to the end of string (no second argument).
Run SET /? to find out more about string expressions.
The counter implementation may also require explanation. The 1 added in front of the numeric part of the file name is needed so that the entire value could be interpreted and processed correctly when incrementing it.
The thing is, a numeric value starting with a 0 is treated as an octal by the CMD command processor, so, putting 1 at the beginning makes it to interpret the number as a decimal, which it actually is. When constructing the complete name of the target file, we simply need to discard the added 1, which is what the %counter:~1 is used for.

What does this batch file code do?

What does this bat code do?
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=!n:~0,-4!
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
)
What does %%~ni,~n:~0,-4!,%%i,!t! mean?
Keep in mind that in batch files, you need to escape percentage signs unless you're referring to arguments given to the batch file. Once you remove those, you get
for /f %i in ('dir /b Client\Javascript\*_min.js') do (
set n=%~ni
set t=!n:~0,-4!
cp Client\Javascript\%i build\Client\Javascript\!t!.js
)
%i is the declaration of a variable used to place the current file for has found. %~ni extracts the filename portion of %i. !n:~0,-4! uses delayed expansion to remove the last four characters from %n% (set in the previous line) !t! is simply delayed expansion of the %t% variable set in the previous line.
Delayed expansion is used because otherwise, the variables will be substituted as soon as the line is encountered, and future iterations will not re-expand the variable.
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
Iterate over every file in the Client\Javascript folder that match "*_min.js". Thedircommand andfor /f` are totally unneeded here, though and only complicate things, especially when file names contain spaces, commas and the like. A more robust and simpler alternative would be
for %%i in (Client\Javascript\*_min.js) do (
But that's just beside the point. People tend to write unelegant batch files sometimes, ignoring the pitfalls and common errors. That's just one example of that.
set n=%%~ni
Creates a variable n, containing the file name (without any directory information or extension) of the file currently processed. We remember that the for statement iterates over every file it finds. With this line starts what it does with those files.
set t=!n:~0,-4!
Creates a second variable, t, containing everything but the last four characters of the file name. This essentially strips away the "_min"
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
Finally, this copies the original file to the directory build\Client\Javascript with the new name, just constructed. So a file like Client\Javascript\foo_min.js will be copied to Client\Javascript\foo.js. The !t! here is just a delayed-evaluated environment variable. More on that below. Here it should suffice that it just inserts the contents of said variable at that point in the line.
Again, bad practice here that will break in numerous interesting ways:
cp is not a command on Windows so this batch will assume cygwin, GNUWin32 or similar things installed. I tend to avoid having too many unneeded dependencies and stick to what Windows provides; in this case the copy command. Two bytes won't kill anyone here, I think.
No quotes are around either argument. Leads to interesting results when spaces start appearing in the file name. Not good, either.
As for why delayed expansion was used (! instead of % surrounding the variables: The for command consists of everything in the block delimited by parentheses here as well. The entire block is parsed at once and normal variable expansion takes place when a line/command is parsed. That would mean that every variable in the block would be evaluated before the loop even runs, leaving just the following:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=
cp Client\Javascript\%%i build\Client\Javascript\.js
)
which is certainly not what you want in this case.
Delayed expansion is always needed when creating and using variables in a loop such as this. A workaround not needing delayed expansion would be to offload the loop interior into a subroutine:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do call :process "%%i"
goto :eof
:process
set n=%~n1
set t=%n:0,-4%
copy "Client\Javascript\%~1" "build\Client\Javascript\%t%.js"
goto :eof
Since the subroutine is not a single "block" (something delimited by parentheses) it will be parsed line by line as usual. Therefore it's safe to use normal expansion instead of delayed expansion here.
A complete help for the FOR command can be found on the Microsoft TechNet site. See here for more information on delayed expansion :
// Pseudo code
for each file named *_min.js in the specified directory
n is set to the file name (*_min)
t is set to the file name, excluding the last 4 characters (*)
the file is copied and renamed t.js to the specified directory
%~ni expands to just the filename part of i.
!n:~0,-4! expands to all but the last four characters of n.
In general, help for at the command prompt will give an overview of the multitude of ways for can expand variables these days.

Resources