BAT-file: FOR %%x variable incorrect expansion - batch-file

I have bat-script with following code:
FOR /F "tokens=1,2 delims==" %%g in ("%CFGFILE%") do (
SET firstChar=%%g
SET firstChar=!firstChar:~1,1!
if /I "!firstChar!"=="#" (
echo %%g>>"%INSTALL_PATH%\tmp.cfg"
)else (
if /I "%%g"=="document.folder" (
SET path_written=TRUE
echo %%g=%DOC_FOLDER%>>"%INSTALL_PATH%\tmp.cfg"
)else (
rem next line is buggy
echo %%g=%%h>>"%INSTALL_PATH%\tmp.cfg"
)
)
)
The point is parsing cfg-file %CFGFILE% contents and copying every string without changes to new config-file, except only one string starting with "document.folder". This line must be changed. Problem is that the line after "next line is buggy" comment gives "c:\program files\myApp\original.cfg=" which is content of %CFGFILE% variable plus equals sign. Is this a bug or i've done something wrong? Is this connected with %%x variables visibility?

You have mis-identified the source of the problem! :-)
Your problem is in the very first line - your FOR statement is processing a string, not a file, because the IN() clause is enclosed by double quotes. If you want the IN() clause to be treated as a quoted file name, then you need to add USEBACKQ to your FOR options.
FOR /F "usebackq tokens=1,2 delims==" %%g in ("%CFGFILE%") do (
Just a heads up - even after the fix above, your code will not give the correct results if any of the following conditions appear
If any line contains ! then expansion of %%g or %%h will be corrupted because delayed expansion is enabled
Commented # line will be incomplete if the original contained =
Your normal lines will not be complete if there is a 2nd = in the original

My suggestion:
Use FART...
http://sourceforge.net/projects/fart-it/

Related

First character disappearing from command reading [Batch file]

I'm trying to read the output of a command (which outputs into multiple lines), and use an arbitrary number of those lines. Because I know neither the number of total lines, nor the number of lines that will be used, I need to analyse and possibly use each line in a loop, which is why I have setlocal enabledelayedexpansion.
Below is a snippet of the code that shows the process of taking the command and reading each line (not using it yet, just reading it to make sure this works (which it doesn't)):
#echo off
setlocal enabledelayedexpansion
for /f "tokens=*" %%i in ('svn status') do (
echo %%i
set file=%%i
echo *!file!
)
The problem that I'm running into is that the %%i values that are being read in are not correct in the for line. The first character is missing from the first line of the input (which is important because I use the first line to decide whether or not to use that line).
The output I get from my code looks like this:
Dir0\TestDoc7.txt
? StatusFile.txt
Whereas if I run this code:
copy /y NUL StatusFile.txt >NUL
>StatusFile.txt (
svn status
)
(Which is just another way of me seeing what the real output of svn status is) I get a proper output into the text file:
! Dir0\TestDoc7.txt
? StatusFile.txt
I'm probably making a fairly clear mistake as I'm rather new to batch scripting.
Thanks in advance.
The cause is EnableDelayedExpansion which will eat the exclamation marks,
Your choice of tokens=* will also strip all leading spaces from the lines.
#echo off
for /f "tokens=1*" %%A in ('svn status') do (
if "%%A" equ "!" (
Rem do whatever
) else If "%%A" equ "?" (
Rem do something else rest of the line is in %%B
) else (
Rem no ! or ? first space sep. content is in %%A rest of the line is in %%B
)
)

Batch read file that contains greater / lower than signs / carets

Alright, so I'm trying to read all lines from a text file. My current way is:
FOR /F "delims=0123456789 tokens=1,*" %%F IN ('find /v /n "" ^< myFile.bat') DO (
SET line = %%G
:: ^ Syntax errors at this line
SET line=!line:~1!
:: Yes, I have delayed expansions enabled due to a lot of fors and ifs needed
)
Basically the input file is another batch file which also contains the exact same code as above and other code with <, >, ^ etc. Once I read a line, it's basically impossible to use %%G as it will expand to stuff like:
SET line=ECHO Hello >> someFile
or
SET line=FOR /L %%G IN (1,1,5) ( SET "line=ECHO Hello %%G" & call :something & >nul SET /P =. )
Which will obviously not work. I've tried many workarounds (all have failed), including:
SET line="%%G
Which (most of the time) works, but from there using is with basically anything is near-impossible, even with something like:
:fixLine
SET line=%line:^=^^^^%
SET line=%line:<=^^^<%
SET line=%line:>=^^^>%
SET line=%line:'=^^^'%
SET line=%line:~2%
GOTO :returnFixLine
But all methods fail in some case or another. How can I read a file containing a batch script from another batch script, including special characters?
EDIT:
Doing
SET "line=%%G"
won't work, as %%G can contain quotes, and even if it doesn't, carets are still special in quotes:
SET "line=ECHO ^<Hello^>"
will turn into
SET "line=ECHO <Hello>"
Also, lines containing exclamation marks will get expanded too.
The first problems are the spaces in set line = %%G, as you set the variable line<space> instead of line.
And you prefix to the content a space.
You should use set line=%%G instead, but even that produces sometimes problems, when spaces are behind the %%G they are appended.
The best way is to use the extended SET syntax set "line=%%G".
Btw. There exists only one special charcter which can fail with a FOR-parameter expansion, that is the exclamation mark when delayed expansion is enabled.
The solution is to toggle delayed expansion.
setlocal DisableDelayedExpansion
FOR /F "delims= tokens=*" %%F IN ('find /v /n "" ^< myFile.bat') DO (
SET "line=%%F"
setlocal EnableDelayedExpansion
SET "line=!line:*]=!"
echo(Testoutput: !line!
endlocal
)

BATCH: How to properly use a %% variable within a for loop

This is the very first time i tried batch scripting so please bear with me.
I just wanted to read each line of my hosts file, and replace the line if it contains/matches a substring. I've seen a lot of answered questions about substrings here but I just can't make it work by using the provided solutions.
I have this code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "hostspath=%SystemRoot%\System32\drivers\etc\hosts"
set "hostsbackuppath=c:\hosts"
>"%hostsbackuppath%.new" (
rem Parse the hosts file, skipping the already present hosts from our list.
rem Blank lines are preserved using findstr trick.
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set str1=%%b
if not x!str1:mydomainname=!==x!str1! (
rem Match found, replace this line.
echo "match!"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
I was trying out this solution to check for substring match among other else: Batch file: Find if substring is in string (not in a file)
Can someone help me? Thanks
SETLOCAL ENABLEDELAYEDEXPANSION
set "matched=true"
>"%hostsbackuppath%.new" (
for /f "delims=: tokens=1*" %%a in ('%SystemRoot%\System32\findstr.exe /n /r /c:".*" "%hostspath%"') do (
set "str1=%%b"
if not "!str1:mydomainname=!"=="!str1!" (
rem Match found, replace this line.
echo "match at %%b in line %%a"
set matched=false
)
// Didn't match, do not replace
if not "!matched!"=="true" echo.%%b
)
)
Hooley-dooley! Someone needs to learn to name variables appropriately.
First, you need to use setlocal enabledelayedexpansion - please see a thousand-and-one SO articles about delayed expansion.
Since str1 is varied within the loop, you need to use setlocal enabledelayedexpansion and !var! to access the varying value of var as %var% is the value at the time the for was encountered.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can safely be used "quoteless".
FOr the same reason, quoting each side of a comparison is preferred as it makes a single token of a string containing separators like spaces.
Then you have a comment "match found" after which you set matched to false ?? Therefore you need to initialise match (to true)
Now quite what you want to do is obscure. On re-reading, you probably want to set "matched=true" as the first line within the loop, not outside as I have it, so that the value is re-set to true for each line found and then set to false if a match is found.
All this negative logic is insane. I need a strong cup of coffee.

print specific lines from a batch file

I am trying to print Line 4, Col 21-50 out of a text file, can this be simply done under Windows somehow? I've been trying to do this:
FOR /F "usebackq tokens=1 delims=-" %G IN (%COMPUTERNAME%.txt) DO ECHO %G
This is just working out terribly. Can't I just print a specific set of lines?
I need this script to be run on multiple computers, ideally I'd like to convert it to a variable for use with slmgr -ipk, maybe someone has a better suggestion?
Contents of text file (I want the XXXXX-XXXXX-XXXXX-XXXXX-XXXXX portion):
==================================================
Product Name : Windows 7 Professional
Product ID : 00371-OEM-9044632-95844
Product Key : XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
Installation Folder : C:\Windows
Service Pack : Service Pack 1
Computer Name : LIBRA
Modified Time : 6/4/2015 7:26:54 PM
==================================================
if you want only the "Product Key" line you can try with
type %COMPUTERNAME%.txt|find /i "Product Key"
or
for /f "tokens=2 delims=:" %%# in (' type %COMPUTERNAME%.txt^|find /i "Product Key"') do echo %%#
For the task at hand, npocmaka's answer is the best suitable approach, as it does not insist on a fixed position of the string to extract from the file.
However, I want to provide a variant that sticks to a certain position.
The following code extracts the string placed at columns 21 to 50 in line 4 of file list.txt (the result is echoed (enclosed in "") and stored in variable LINE_TXT (without ""):
#echo off
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" "list.txt"'
) do (
if %%L equ 4 (
set "LINE_TXT=%%M"
goto :NEXT
)
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
The goto :NEXT command terminates the for /F loop at the given line; this is not mandatory but will improve performance for huge files (as long as the given line number is quite small).
To be more flexible, the following code can be used (define the string position in the initial set block):
#echo off
rem Define the string position here:
set FILE_TXT="list.txt"
set LINE_NUM=4
set COL_FROM=21
set COL_UPTO=50
setlocal EnableDelayedExpansion
set /A COL_UPTO-=COL_FROM
set /A COL_FROM-=1
for /F "tokens=1,* delims=:" %%L in (
'findstr /N /R ".*" %FILE_TXT%'
) do (
if %%L equ %LINE_NUM% (
set "LINE_TXT=%%M"
if defined LINE_TXT (
set "LINE_TXT=!LINE_TXT:~%COL_FROM%,%COL_UPTO%!"
)
goto :NEXT
)
)
:NEXT
endlocal & set "LINE_TXT=%LINE_TXT%"
echo."%LINE_TXT%"
Both of the above code snippets rely on the output of findstr /N /R ".*", which returns every line that matches the regular expression .*, meaning zero or more characters, which in turn is actually true for every line in the file; however, the switch /N defines to prefix each line with its line number, which I extract and compare with the originally defined one.
Here is another variant which uses for /F to directly loop through the content (lines) of the given text file, without using findstr:
#echo off
for /F "usebackq skip=3 eol== delims=" %%L in (
"list.txt"
) do (
set "LINE_TXT=%%L"
goto :NEXT
)
:NEXT
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
This method has got the better performance, because there is the skip option which skips parsing of and iterating through all lines (1 to 3) before the line of interest (4), opposed to the findstring variant.
However, there is one disadvantage:
for /F features an eol option which defines a character interpreted as line comment (and defaults to ;); there is no way to switch this option off as long as delims= defines no delimiters (last position in option string), which is mandatory here to return the line as is; so you have to find a character that does not appear as the first one in any line (I defined = here because your sample text file uses this as header/footer character only).
To extract a string from line 1, remove the skip option as skip=0 results in a syntax error.
Note that goto :NEXT is required here; otherwise, the last (non-empty) line of the file is extracted.
Although for /F does not iterate any empty lines in the file, this is no problem here as the skip option does not check the line content and skip over empty lines as well.
Finally, here is one more approach using more +3 where no text parsing is done. However, a temporary file is needed here to pass the text of the desired line to the variable LINE_TXT:
#echo off
set LINE_TXT=
more +3 "list.txt" > "list.tmp"
set /P LINE_TXT= < "list.tmp"
del /Q "list.tmp"
if defined LINE_TXT set "LINE_TXT=%LINE_TXT:~20,29%"
echo."%LINE_TXT%"
exit /B 0
This method avoids for /F and therefore the problem with the unwanted eol option as mentioned in the above solution. But this does not handle tabs correctly as more substitutes them with spaces (8 indent spaces as per default and configurable by the /Tn switch where n is the number of spaces).

Edit html file in batch file

I have a script in which I read html files which I want to edit. Here I paste the code which calls :remove_redundant_columns subroutine.
It should remove the spaces/white spaces from begin of each line and remove from html file. Only problem is that it adds extra text like = to lines which are almost empty, just have few tabs.
The html file which I downloaded is from hidemyass.com/proxy-list/1
call parse_proxy.bat remove_redundant_columns !FILENAME!
exit /b
:remove_redundant_columns
REM Remove whitespaces from begin of lines and <span></span>
FOR /f "tokens=*" %%t in (%1) do (
SET S=%%t
SET S=!S:^<span^>^</span^>=!
if NOT "!S!"=="" >>$tmp$ echo !S!
)
del %1
REN $tmp$ %1
exit /b
If you believe, that's your only problem... You need to check, if your variable S contains content.
That's required, as substitution on an undefined variable will not produce an undefined/empty variable, the new content will be the substitution text.
:remove_redundant_columns
REM Remove whitespaces from begin of lines and <span></span>
FOR /f "tokens=*" %%t in (%1) do (
SET S=%%t
if defined S (
SET S=!S:^<span^>^</span^>=!
>>$tmp$ echo !S!
)
)
As dbenham stated, you got many other problems,
and one additional problem is the echo !S! command itself.
ECHO has some nasty side effects on different content.
If the content is empty (or only spaces) then it will print it's currently state
ECHO IS OFF
If the content is OFF or ON it will NOT be echoed, it will only change the state.
And if the content is /? it will echo the help instead of /?.
To solve this you could simply change ECHO !S! to ECHO(!S! and all problems are gone.
jeb already solved your = problem (once the extra IF DEFINED check is added to his answer). But you may have at least one other problem.
I agree with Joey that you should not be using batch to manipulate HTML like this. But, if you really want to...
Your potential problem is that HTML usually has ! characters sprinkled within. Your code uses delayed expansion, but that causes corruption of FOR variable expansion when it contains ! character(s). The solution is to toggle delayed expansion on and off within your loop.
:remove_redundant_columns
setlocal disableDelayedExpansion
REM Remove whitespaces from begin of lines and <span></span>
(
FOR /f "usebackq eol= tokens=*" %%t in ("%~1") do (
SET S=%%t
setlocal enableDelayedExpansion
if defined S SET "S=!S:<span></span>=!"
for /f "eol= tokens=*" %%S in ("!S!") do if "%%S" neq "" echo %%S
endlocal
)
) >>$tmp$
move /y $tmp$ "%~1"
exit /b
Other minor changes that were made to the code:
The search and replace can be simplified by using quotes so that special chars don't need to be escaped.
You can replace DEL and REN with a single MOVE.
Redirection is more efficient (faster) if you redirect once using an outer set of parentheses
You may need to search a file name that has spaces and or special characters, in which case you will need to quote the name. But that requires the FOR /F "USEBACKQ" option.
EDIT
Modified code to strip leading spaces after <span></span> has been replaced to eliminate potential of a line containing nothing but spaces and/or tabs.
Also set EOL to space to prevent stripping of lines beginning with ;

Resources