I want to execute some program passing an argument. The argument changes depending of the day of week and it is an url
Code:
#echo off
setlocal enableDelayedExpansion
for /f %%a in ('wmic path win32_localtime get dayofweek /format:list ^| findstr "="') do (set %%a)
if %dayofweek% == 7(
EXIT
)
set link="http://backlog.telecom.pt/maps/selector/download?map_name=workline_fornecedores&organization_id=1"
if %dayofweek%==5 (
set link="http://backlog.telecom.pt/maps/selector/download?map_name=all_fornecedores&organization_id=1"
)
REM start /wait D:\Planview\Services\BackLog_Integration_Client_Service\Backlog_Integration_Client_Exe\Backlog_Integration_Client_Exe.exe %link%
REM timeout /t 600 /nobreak > NUL
REM start D:\Planview\Services\PV_Backlog_ProcessData_Service\PV_Backlof_ProcessData_Exe\PV_Backlog_ProcessData_Exe.exe
I read that ^ before & would work to escape the & char, but for me it never did and the only way i managed to do it was enableDelayedExpansion and encapsulate the url in ", but this brought me a problem.. my variable instead of having the url has "url".
I tried to do set link=%link:"% but it did not worked.
I'll try to give you some advice via simple examples:
#setlocal enableDelayedExpansion
rem This fails because & is a "poison" character (an instruction or operator)
echo abc&xyz
rem This succeeds because & is escaped
echo abc^&xyz
rem This succeeds because & is quoted
echo "abc&xyz"
rem This succeeds, but it consumes the escape: stored value = abc&xyz
set test=abc^&xyz
rem So this fails because & is not escaped
echo %test%
rem You could "fix" above by double escaping when doing SET so stored value = abc^&xyz
rem But I don't like this - I pretty much never do it
set test=abc^^^&xyz
rem Now this works
echo %test%
rem Better to single escape and use delayed expansion.
set test=abc^&xyz
rem This works because poison characters don't bother delayed expansion
echo !test!
rem You could use quotes instead of escape, but now quotes are in value
set test="abc&xyz"
rem Delayed expansion is not needed because value is quoted
echo %test%
rem Putting the leading quote before the variable still quotes everything
rem But now the quotes are not in the value, as desired. Value = abc&xyz
set "test=abc&xyz"
rem Now you must use delayed expansion because value is not quoted
echo !test!
So the general rules of thumb that I like to use when poison characters are involved:
Use quotes around the entire assignment when SETting a variable: set "var=value"
Use delayed expansion when expanding a variable: !var!
Wouldn't it be nice if those rules solved everything. But of course batch is not that simple. There are situations where these simple rules will fail, but it should be enough to get you started.
Related
I've a code like below:
FOR /F "tokens=*" %%B IN ('%*') DO (
ECHO %%B
SET data=%%B
ECHO %data%
)
The %%B contains one or more than one | character, for example the value of %%B is First | Second | Third. The SET data=%%B or SET data="%%B" function doesn't work. When %data% is called back, it shows Echo is off.
I need to remove the | character from %%B and save it to a %data% variable. But I don't know how?
I will be grateful if any solution has been provided from anyone....
The first solution is using:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /F delims^=^ eol^= %%B in ('%*') do (
set "data=%%B"
set "data=!data:|=!"
echo(!data!
)
endlocal
tokens=* results in getting assigned to loop variable B the entire line with leading spaces/tabs removed while delims= results in getting assigned to the loop variable B the entire line including leading spaces/tabs. So the definition of an empty list of delimiters is better in general.
FOR ignores by default empty lines and lines starting with a semicolon because of ; is interpreted by default as end of line character. For that reason the uncommon, not double quoted option string delims^=^ eol^= is used here to define an empty list of delimiters and no end of line character. The caret character ^ is used to escape the next character to get it interpreted as literal character and not as argument separator although not enclosed in a double quoted argument string.
But there is one problem with this solution: A line containing one or more exclamation marks ! is not processed correct because Windows command processor interprets on command line set "data=%%B" each ! as begin/end of a delayed environment variable reference and replaces the string between two exclamation marks by the current value of the environment variable with that name or nothing on no variable existing with such a name and removes everything after ! if there is no more !.
There are at least three solutions.
The first one is enabling and disabling delayed expansion within the loop.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F delims^=^ eol^= %%B in ('%*') do (
set "data=%%B"
setlocal EnableDelayedExpansion
set "data=!data:|=!"
echo(!data!
endlocal
)
endlocal
Please read this answer for details about the commands SETLOCAL and ENDLOCAL as they do much more than just toggling delayed expansion on/off in the loop.
( between echo and value of environment variable data is used in case of a line contains only | without or with additionally just spaces/tabs resulting in data becoming either undefined or a string consisting only of spaces/tabs. A space between echo and !data! would be on execution just the command echo with an ignored space and 0 or more spaces/tabs resulting in getting output ECHO is off. instead of an empty line or a line with just spaces/tabs. The opening round bracket prevents that and is interpreted by cmd.exe as separator between command echo and its argument string which begins in this case always with (. ECHO ignores the first character of the argument string on output as long as the argument string is not /? and so ( is never output.
The second solution is using a subroutine:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F delims^=^ eol^= %%B in ('%*') do (
set "data=%%B"
call :ProcessLine
)
endlocal
goto :EOF
:ProcessLine
set "data=%data:|=%"
echo(%data%
goto :EOF
Note: echo %data% can result in unexpected behavior if the line contains characters like the redirection operators < and > or the operator & as Windows command processor executes echo(%data% after substituting %data% with current string of environment variable data. Even set "data=%data:|=%" can be problematic depending on line containing " and <>&. So this solution is really not safe.
See also Where does GOTO :EOF return to?
The third solution is using a "double percent" environment variable reference and using command CALL to force a double parsing of the command line containing %%variable%% instead of just %variable% as usual.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F delims^=^ eol^= %%B in ('%*') do (
set "data=%%B"
call set "data=%%data:|=%%"
call echo(%%data%%
)
endlocal
Note Also this solution is not really safe depending on the data as the second solution.
See also How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Windows command processor is designed for executing commands and applications and not for reformatting or processing CSV files using vertical bar as delimiter/separator.
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.
call /?
echo /?
endlocal /?
for /?
goto /?
set /?
setlocal /?
And read also answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? for the reason using the syntax set "variable=value" instead of set variable=value or set variable="value".
Can someone give me an example of where a batch script would act differently with or without delayed expansion? Are there any situations where you would NOT want to use delayed expansion? Thanks.
Look at the following examples...
Example 1: The following code DOESN'T use delayed expansion, so the variables in the for loop are expanded only one time. This means that %Count% will always expand to 0 in each iteration of the loop, no matter what we do to it with the set command:
#echo off
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=%COUNT% + 1
echo Count = %COUNT%
)
pause
So this script will output:
Count = 0
Count = 0
Count = 0
Count = 0
This is not how this loop is supposed to work.
Example 2: On the other hand, if we use delayed expansion, we have the following script, which will run as expected.
setlocal ENABLEDELAYEDEXPANSION
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=!COUNT! + 1
echo Count = !COUNT!
)
pause
and, as expected, it will output:
Count = 1
Count = 2
Count = 3
Count = 4
When you use the ENABLEDELAYEDEXPANSION, and expand a variable using ! instead of %, the variable is re-expanded each time, and everything works as it's supposed to.
I wanted to add a great example on how "EnableDelayedExpansion" (EDE) can be useful outside of the ubiquitous FOR loop examples.
Here is a line of earthquake data that I wish to parse (I call it it 1line.txt)
ak_11574812 2015.04.29.193822 62.9525 -148.8849 1.0 9.5 1 49km S of Cantwell, Alaska
The problem I ran into was that last segment of this line does not always start at the same column number. So I needed to create a flexible SET command that will accurately pluck out the last segment of this line.
ECHO OFF
setlocal enableDelayedExpansion
set where=72
set /p line=<1line.txt
set locate=!line:~%where%,28!
echo %locate%
EDE allows me to place a variable (where) inside another variable (line). EDE will translate the variable bracketed by % first, then process the variable bracketed by ! and (in this case) push out the results into the "locate" variable.
Max's answer gives an example of where a batch script would act differently with or without delayed expansion.
For the sake of completeness, let's answer another part of the question and show a situation where you would NOT want to use delayed expansion when your data contain an exclamation mark ! (and show two ways of processing such data):
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_auxFile=%temp%\%~n0.txt"
rem create multiline sample file
>"%_auxFile%" ( for /L %%G in (1,1,3) do echo line %%G is 100%% valid! Sure! Hurrah!)
rem create one-line sample file
>"%_auxFile%" echo this line is 100%% valid! Sure! Hurrah!
echo(
echo --- file content
type "%_auxFile%"
echo(
SETLOCAL EnableDelayedExpansion
echo --- enabled delayed expansion chokes down unescaped exclamation marks^^^! "^!"
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%~G"
echo loop var=%%~G
echo _auxLine=!_auxLine!
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- toggled delayed expansion works although might be laborious!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
SETLOCAL EnableDelayedExpansion
echo _auxLine=!_auxLine!
ENDLOCAL
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- keep delayed expansion DISABLED: use CALL command!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
call :ProcessVar
)
ENDLOCAL
rem delete the sample file
del "%_auxFile%"
ENDLOCAL
goto :eof
:ProcessVar
echo _auxLine=%_auxLine%
echo WARNING: neither !_auxLine! nor %%G loop variable is available here!
goto :eof
Note that above script shows proper ways of escaping
% percent sign by %% doubling it (delayed expansion does not matter), and
! exclamation mark if delayed expansion is enabled:
"^!" if enclosed in a pair of double quotes, then use the cmd and batch-script general escape character ^ caret;
^^^! otherwise, use three ^ carets.
Output:
==> D:\bat\SO\10558316.bat
--- file content
this line is 100% valid! Sure! Hurrah!
--- enabled delayed expansion chokes down unescaped exclamation marks! "!"
loop var=this line is 100% valid Hurrah
_auxLine=this line is 100% valid Hurrah
--- toggled delayed expansion works although might be laborious!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
--- keep delayed expansion DISABLED: use CALL command!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
WARNING: !_auxLine! as well as %G loop variables are not available here!
==>
As pointed in the answer the main usage of the delayed expansion is the setting and accessing variables in brackets context.
Though it can be useful in another situations too.
Parametrizing substring and string substitution:
#echo off
setlocal enableDelayedExpansion
set "string=test string value"
set start=5
set get_next=6
echo #!string:~%start%,%get_next%!#
set "search_for=string"
set "replace_with=text"
echo #!string:%search_for%=%replace_with%!#
the output will be:
#string#
#test text value#
though this can be achieved with additional call this way is more performant
Using shift command within brackets parameterized argument access
#echo off
echo first attempt:
(
echo "%~1"
shift
echo "%~1"
)
::now the shift command will take effect
setlocal enableDelayedExpansion
echo second attempt:
(
set /a argument=1
call echo %%!argument!
shift
call echo %%!argument!
)
the output will be:
first attempt:
"first argument"
"first argument"
second attempt:
"second argument"
"third argument"
As you can see parameterized argument access can be done only with delayed expansion.
Using for tokens (or function arguments) for parameterization
One more approach for mixing !s and %s this could be useful for nested loops:
#echo off
setlocal enabledelayedexpansion
set begin=2
set end=2
set string=12345
for /f "tokens=1,2" %%A in ("!begin! !end!") do set "string2=!string:~%%A,%%B!"
echo !string2!
endlocal
as you can see now the for command tokens are used as parameters.
Several answers here answer the "How to use delayed expansion?" question or what happen if you don't use delayed expansion. However, the second question is "Are there any situations where you would NOT want to use delayed expansion?" and a couple answers take this question as "how to avoid the problems caused by using delayed expansion?"
My answer answers the question as I understand it: "In which situations is better to NOT use delayed expansion (instead of use it)?"
If you want to exchange the contents of two variables, the simplest way to perform this is using the %standard% variable expansion:
set "var1=%var2%" & set "var2=%var1%"
The way that the %standard% expansion works makes possible to achieve this replacemenmt without using any auxiliary variable. As far as I know, the Batch-file "programming language" is the only one that allows to perform this exchange in this way, that is, making good use of a language "feature" (not via a specialized "exchange" instruction/statement).
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
)
As far as I know, I need to escape every escape characters when echoing them. The ^ method works fine for a few echoes. (which should be something like:)
#echo ^|
#echo ^> ^>^>
However, when there are a lot of characters to escape, the ^ method won't work anymore. So, my question is:
Are there any ways escape all special characters without "spamming" the caret?
As mentioned by Mofi, you could use variables with delayed expansion to echo any content.
There are some more possible ways.
1) Disappearing quotes
for /F %%^" in ("""") do (
echo %%~"Hello^you
echo %%~"Line2 |&<>
)
This works as the %~" will quote the rest of the line, but after expansion it will disappear
2) When you want to echo multiple lines you should read about the different variants of heredoc
3) The MagicEcho can display any content without escaping or doubling percent signs like
%magicEcho% "^!%path%<>" ^%!%<> ^
Well, there is no need to escape redirection operators and other special characters listed in last paragraph in help output by running cmd /? in a command prompt window on last help page when the string to output is enclosed in double quotes.
But using " on line with ECHO command results in having also the double quote output.
There are several solutions.
The first one is assigning the string to output to an environment variable and output the value of the environment variable using delayed expansion.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Line=pipe = | and percent sign = %% and exclamation mark ^!"
echo !Line!
set "Line=redirection operators: < and > and >>"
echo !Line!
endlocal
Or a little bit shorter, but not so good readable:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Line=pipe = | and percent sign = %% and exclamation mark ^!" & echo !Line!
set "Line=redirection operators: < and > and >>" & echo !Line!
endlocal
Note: % and ! must be nevertheless escaped with another % and with ^ to be interpreted as literal character in string assigned to environment variable Line.
Another solution using a subroutine PrintLine:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
call :PrintLine "pipe = | and percent sign = %%%% and exclamation mark !"
call :PrintLine "redirection operators: < and > and >>"
endlocal
goto :EOF
:PrintLine
set "Line=%~1"
setlocal EnableDelayedExpansion
echo !Line!
endlocal
goto :EOF
The disadvantages of this solution are:
A percent sign must be defined with 4 percent signs to be finally printed as literal character.
It is slower because of usage of SETLOCAL and ENDLOCAL on printing each line.
Read this answer for details about the commands SETLOCAL and ENDLOCAL.
One more solution according to comment by JosefZ uses command FOR for an implicit delayed expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%I in (
"pipe = | and percent sign = %% and exclamation mark !",
"redirection operators: < and > and >>"
) do echo %%~I
endlocal
The lines to output are specified in a comma separated list of double quoted strings for being processed by FOR.
It has the big advantage that just the percent sign must be escaped with an additional percent sign on delayed expansion being disabled. But the string to output can't contain a double quote with exception of "" within string.
Thanks JosefZ for this contribution.
Other great solutions are provided by jeb in his answer.
I am fighting with little piece of code for last two days.
In this I am not able to set variable in a for loop.
I want to assign a filename to a variable for string manipulation.
echo off
for /f %%a IN ('dir /b *_ah.ttf') DO (
set /a fName=%%~na
echo %fName%
)
When I echo fName variable I get only last filename repeatedly number of times for for loop count.
(I want to pass this variable as an argument to some batch file as follows
ttfhnt --strong-stem-width=D -i %%a %fName:~0,-3%.ttf
but its failing due to above problem)
Can somebody help me please?
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
And set /A is only used for arithmetic operations
setlocal enabledelayedexpansion
for /f "delims=" %%a IN ('dir /b *_ah.ttf') DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
edited to adapt to comments
While for command is able to execute a command (in the OP code, the dir...), retrieve its output and then iterate over the lines in this output, the original reason for the command is to iterate over a set of files. In this form, the code can be written as
setlocal enabledelayedexpansion
for %%a IN ("*_ah.ttf") DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
Now, the for command replaceable parameter will iterate over the indicated set of files. (execute for /? for a list of all the command options).
But as foxidrive points, the problem with delayed expansion are the exclamation signs. Without delayed expansion, they are another normal character, but with delayed expansion they frequently become a problem when a value containig them is assigned/echoed.
A quick test
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
echo normal : %test%
for /f "delims=" %%a in ("!test!") do echo for : %%a
Will show
---------------------
test=this is a test!
---------------------
delayed : this is a test!
normal : this is a test
for : this is a test
Obviously when the value is a file name, this behaviour will make the code find or not the file.
Depending on the case different solutions can be used, but usually it involves the activation / desactivation of the delayed expansion behaviour (beware, the endlocal removes any change in environment variables from the previous setlocal).
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
rem Commuted to no delayed expansion
setlocal disabledelayedexpansion
echo normal : %test%
endlocal
rem Cancelled the initial enable delayed expansion
for /f "delims=" %%a in ("!test!") do endlocal & echo for : %%a
rem The last endlocal has removed the changes to the variable
echo no data : [%test%]