Ampersand as a delim in a FOR /F statement - windows

I am trying to escape ampersand in a FOR /F statement in windows command line script, as follows:
FOR /F "tokens=1,2 delims=^&" %%A IN ("%Var%") DO (...
Result from running the script is still:
& was unexpected at this time
What is the correct way to use & as a delimeter? Or should it be replaced with something else in the string to be parsed?

The ampersand is already escaped by double quotes. So more escaping is not necessary and successful:
#echo OFF &SETLOCAL
for /f "delims=&" %%i in (file) do echo %%i

As Endoro has already stated, you don't need to escape the & within your delims specification.
Your error is probably occurring with the expansion of "%Var%". If your value contains both quotes and & in such a way that the & is not quoted after expansion, then that is precicely the error you will get. Remember that the quotes in your statement can be canceled by quotes within the value.
You could probably solve the problem using delayed expansion
setlocal enableDelayedExpansion
FOR /F "tokens=1,2 delims=&" %%A IN ("!Var!") DO (...
But if your value also contains !, then you need to disable the delayed expansion before you you expand your FOR variables, otherwise the expanded values will be corrupted.
setlocal enableDelayedExpansion
FOR /F "tokens=1,2 delims=&" %%A IN ("!Var!") DO (
endlocal
...
)

Related

Double quotes in delims=?

I'm very new to batch scripting, but in my last question I was trying to extract a link from a line of text, specifically:
83: href="https://beepbeep.maxresdefault.rar"><img
What I want out of it is this:
https://beepbeep.maxresdefault.rar
Someone suggested using for /f to separate the string, and I'd like to separate it every " mark, taking only the second token, as what I want is trapped between the two "".
Here is what I've got:
for /f "delims=^" tokens=^2" %%G in (output2.txt) do #echo %%G %%H >> output3.txt
The batch crashes at this point, I'm guessing it's the wrong syntax, but I'm not sure where the issue is, maybe in the " part?
See how we delimit on double quotes, without surrounding quotes. We have already assigned the variable between the quotes to %%a but if we did not, then to remove the double quotes from the string we expand the variable %%a to %%~a (see for /? on variable expansion):
#for /f delims^=^"^ tokens^=2 %%a in (output2.txt) do #echo %%~a
Neat problem.
I'd do it this way:
SETLOCAL ENABLEDELAYEDEXPANSION
for /F "tokens=2 delims=^>=" %%i in (output2.txt) do (
set x=%%i
set x=!x:"=!
echo !x! >> output3.txt
)
Notes:
Instead of tokenising on the quote, I've tokenised on = (before) and > (after). Because, as you already know, quotes are hard
I always do the delims last. Otherwise it might think the space between delims and tokens is a delimeter.
Then it uses the SET syntax that allows you to substitute one character for another to replace all occurances of the double quote with nothing.
The SETLOCAL ENABLEDELAYEDEXPANSION is necessary because otherwise, each evaluation of the %x% in the loop uses the original value of %x% which is probably wrong. I always have this as the second line in my batch file.
Judging by how much you've already got, I'm guessing you've seen it, but if you haven't, I've found ss64.com to be the best resource.
https://ss64.com/nt/syntax-dequote.html

Replacing angle brackets in variables

I have a plain text file with one url per line, enclosed with <link></link> tags. ECHO-ing the variablee (including the tags) works fine but now I'd like to remove the tags. escaping the angle brackets with one or multiple ^ does not work.
here's the code
FOR /F "tokens=* USEBACKQ" %%F IN (`findstr "<link>" test.txt`) DO (
SET what=%%F
SET result=%what:<link>=%
ECHO %result%
)
is there another way of doing it?
You need delayed expansion and quotes when you are using > or <:
#echo off
setlocal enableDelayedExpansion
FOR /F "tokens=* USEBACKQ" %%F IN (`findstr "<link>" test.txt`) DO (
SET "what=%%F"
SET "result=!what:<link>=!"
ECHO !result!
)
endlocal

Windows command line string replace removes "!"

I have this little snippet that replaces production:false with production:true.
(FOR /F "tokens=1,* delims=]" %%A in ('"type test.js|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:production:false=production:true%%"
FOR /F "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)) >test-temp.js
move /Y test-temp.js test.js
So far so good, but, in the test.js it says something like:
if ( !production ) {}
The thing is, the "!" is removed by the above command as well. Any idea how does happens?
You probably have delayedexpansion invoked.
Running your snippet (which is all you give us) against your single line of provided data simply reproduced the data line verbatim.
with delayedexpansion, the ! disappeared as you describe.
To fix (with delayedexpansion in effect
SETLOCAL DISABLEDELAYEDEXPANSION
(FOR /F "tokens=1,* delims=]" %%A in ('"type q19406461.txt|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:production:false=production:true%%"
FOR /F "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)) >test-temp.js
ENDLOCAL
This is a robust method and uses a helper batch file called repl.bat from - http://www.dostips.com/forum/viewtopic.php?f=3&t=3855
Put repl.bat in the same folder as the batch file.
#echo off
type "test.js"|repl "production:false" "production:true" >"test-temp.js"
Peter already diagnosed the problem with delayed expansion. Here is a bit more explanation.
FOR variables are expanded before delayed expansion. So the line set "line=%%B" first sets the value to if ( !production ) {}, but then delayed expansion sees the unpaired ! and strips it. If it were paired, it would attempt to expand the variable in between.
Here is a summary of the order of variable expansion (practical, but a bit imprecise):
1) Normal expansion: argument (%1) and variable (%var%). Arguments take precedence over variables.
2) FOR variable: %%A
3) Delayed expansion: !var!
4) SET /A variables
See How does the Windows Command Interpreter (CMD.EXE) parse scripts for a more exact description of how lines are parsed and expanded.
It is possible to use delayed expansion within your loop without corrupting ! literals if you toggle delayed expansion on and off. This is preferred over using the CALL syntax because it is faster, does not muck with % literals, does not double quoted carets, protects against all poison characters, even when they are not quoted.
The use of delayed expansion makes the code much simpler, faster, and more reliable:
setlocal disableDelayedExpansion
(FOR /F "tokens=1,* delims=]" %%A in ('"type test.js|find /n /v """') do (
set "line=%%B"
setlocal enableDelayedExpansion
if defined line (
echo(!line:production:false=production:true!
) ELSE echo(
endlocal
)) >test-temp.js
move /Y test-temp.js test.js
endlocal
Note that I use ECHO( instead of ECHO. because the latter can fail in some obscure scenarios. ECHO( looks like it would cause problems with block parentheses, but it actually always works without any problems.
Note that I would still not use the above code. Instead I would use the REPL.BAT utility as foxidrive has in his answer.

How to store the output of batch (CALL) command to a variable

I have the batch file which has the command to call second.bat file. It will produce single line of output when it called. I want to store that line into variable.
CALL second.bat
I have tried using the following lines of commands but no use
FOR /F "tokens=* USEBACKQ" %%F IN ('COMMAND') do SET result=%%F
FOR /F "tokens=1 delims= " %%A IN ('COMMAND') DO SET NumDocs=%%A
I don't know what to replace with COMMAND
As the help will tell you, COMMAND should be the command you want to run and get the output of. So in your case second.bat. At least it works for me:
#echo off
FOR /F "tokens=*" %%F IN ('second.bat') do SET result=%%F
echo %result%
Note that you cannot use the usebackq option if you're using ' to delimit your command.
tl;dr
To complement Joey's helpful answer (which fixes the problem with your 1st command) with a fixed version of both your commands:
:: 'usebackq' requires enclosing the command in `...` (back quotes aka backticks)
FOR /F "tokens=* usebackq" %%F IN (`second.bat`) do SET result=%%F
:: No characters must follow "delims="; 'tokens=1' is redundant
FOR /F "delims=" %%F IN ('second.bat') DO SET result=%%F
Note that in this case there's no need for call in order to invoke second.bat (which you normally need in order to continue execution of the calling batch file), because any command inside (...) is run in a child cmd.exe process.
The only thing needed to make your 2nd command work is to remove the space after delims=:
FOR /F "tokens=1 delims=" %%F IN ('second.bat') DO SET result=%%F
delims= - i.e., specifying no delimiters (separators) - must be placed at the very end of the options string, because the very next character is invariably interpreted as a delimiter, which is what happened in your case: a space became the delimiter.
Also, you can simplify the command by removing tokens=1, because with delims= you by definition only get 1 token (per line), namely the entire input (line), as-is:
FOR /F "delims=" %%F IN ('second.bat') DO SET result=%%F
Finally, it's worth noting that there's a subtle difference between tokens=* and delims=:
delims= (at the very end of the options string) returns the input / each input line as-is.
tokens=* strips leading delimiter instances from the input; with the default set of delimiters - tabs and spaces - leading whitespace is trimmed.

Escaping ampersands in Windows batch files

I realise that you can escape ampersands in batch files using the hat character
e.g.
echo a ^& b
a & b
But I'm using the command
for /f "tokens=*" %%A IN ('DIR /B /A-D /S .acl') DO ProcessACL.cmd "%%A"
which is finding all the files named '.acl' in the current directory, or a subdirectory of the current directory.
The problem is, I'm finding path names that include the '&' character (and no, they can't be renamed), and I need a way of automatically escaping the ampersands and calling the second batch file with the escaped path as the parameter.
rem ProcessACL.cmd
echo %1
The problem is not the escaping, it seems to be in the second script.
If there is a line like
echo %1
Then it is expands and fails:
echo You & me.acl
Better to use delayed expansion like
setlocal EnableDelayedExpansion
set "var=%~1"
echo !var!
To avoid also problems with exclamation points ! in the parameter, the first set should be used in a DisableDelayedExpansion context.
setlocal DisableDelayedExpansion
set "var=%~1"
setlocal EnableDelayedExpansion
echo !var!
Your for line should be (note the *.acl)
for /f "tokens=*" %%A IN ('DIR /B /A-D /S *.acl') DO ProcessACL.cmd "%%A"
ProcessACL.cmd can access the path passed to it with %1.
// ProcessACL.cmd
ECHO %1
Whatever is contained by the variable %1 is fully contained. There is no need for escapes. Escapes are for the batch processor to interpret the characters it is parsing.

Resources