Related
I've been writing some batch files, and I ran into this user guide, which has been quite informative. One thing it showed me was that lines can be commented not just with REM, but also with ::. It says:
Comments in batch code can be made by using a double-colon, this is better than using the REM command because labels are processed before redirection symbols. ::<remark> causes no problems but rem <remark> produces errors.
Why then, do most guides and examples I see use the REM command? Does :: work on all versions of Windows?
tl;dr: REM is the documented and supported way to embed comments in batch files.
:: is essentially a blank label that can never be jumped to, whereas REM is an actual command that just does nothing. In neither case (at least on Windows 7) does the presence of redirection operators cause a problem.
However, :: is known to misbehave in blocks under certain circumstances, being parsed not as a label but as some sort of drive letter. I'm a little fuzzy on where exactly but that alone is enough to make me use REM exclusively. It's the documented and supported way to embed comments in batch files whereas :: is merely an artifact of a particular implementation.
Here is an example where :: produces a problem in a FOR loop.
This example will not work in a file called test.bat on your desktop:
#echo off
for /F "delims=" %%A in ('type C:\Users\%username%\Desktop\test.bat') do (
::echo hello>C:\Users\%username%\Desktop\text.txt
)
pause
While this example will work as a comment correctly:
#echo off
for /F "delims=" %%A in ('type C:\Users\%username%\Desktop\test.bat') do (
REM echo hello>C:\Users\%username%\Desktop\text.txt
)
pause
The problem appears to be when trying to redirect output into a file. My best guess is that it is interpreting :: as an escaped label called :echo.
Comments with REM
A REM can remark a complete line, also a multiline caret at the line end, if it's not the end of the first token.
REM This is a comment, the caret is ignored^
echo This line is printed
REM This_is_a_comment_the_caret_appends_the_next_line^
echo This line is part of the remark
REM followed by some characters .:\/= works a bit different, it doesn't comment an ampersand, so you can use it as inline comment.
echo First & REM. This is a comment & echo second
But to avoid problems with existing files like REM, REM.bat or REM;.bat only a modified variant should be used.
REM^;<space>Comment
And for the character ; is also allowed one of ;,:\/=
REM is about 6 times slower than :: (tested on Win7SP1 with 100000 comment lines).
For a normal usage it's not important (58µs versus 360µs per comment line)
Comments with ::
A :: always executes a line end caret.
:: This is also a comment^
echo This line is also a comment
Labels and also the comment label :: have a special logic in parenthesis blocks.
They span always two lines SO: goto command not working.
So they are not recommended for parenthesis blocks, as they are often the cause for syntax errors.
With ECHO ON a REM line is shown, but not a line commented with ::
Both can't really comment out the rest of the line, so a simple %~ will cause a syntax error.
REM This comment will result in an error %~ ...
But REM is able to stop the batch parser at an early phase, even before the special character phase is done.
#echo ON
REM This caret ^ is visible
You can use &REM or &:: to add a comment to the end of command line.
This approach works because '&' introduces a new command on the same line.
Comments with percent signs %= comment =%
There exists a comment style with percent signs.
In reality these are variables but they are expanded to nothing.
But the advantage is that they can be placed in the same line, even without &.
The equal sign ensures, that such a variable can't exists.
echo Mytest
set "var=3" %= This is a comment in the same line=%
The percent style is recommended for batch macros, as it doesn't change the runtime behaviour, as the comment will be removed when the macro is defined.
set $test=(%\n%
%=Start of code=% ^
echo myMacro%\n%
)
Performance REM vs :: vs %= =%
In short:
:: and %= =% seems to have the same performance
REM takes ~ 50% more time than ::
In blocks, especially loops only REM consumes time, but :: is removed from the cached block when the block is parsed, therefore it consumes no time
For more info see SO: Question about Comments in Batch *.bat files and speed
This answer attempts a pragmatic summary of the many great answers on this page:
jeb's great answer deserves special mention, because it really goes in-depth and covers many edge cases.
Notably, he points out that a misconstructed variable/parameter reference such as %~ can break any of the solutions below - including REM lines.
Whole-line comments - the only directly supported style:
REM (or case variations thereof) is the only official comment construct, and is the safest choice - see Joey's helpful answer.
:: is a (widely used) hack, which has pros and cons:
Pros:
Visual distinctiveness and, possibly, ease of typing.
Speed, although that will probably rarely matter - see jeb's great answer and Rob van der Woude's excellent blog post.
Cons:
Inside (...) blocks, :: can break the command, and the rules for safe use are restrictive and not easy to remember - see below.
If you do want to use ::, you have these choices:
Either: To be safe, make an exception inside (...) blocks and use REM there, or do not place comments inside (...) altogether.
Or: Memorize the painfully restrictive rules for safe use of :: inside (...), which are summarized in the following snippet:
#echo off
for %%i in ("dummy loop") do (
:: This works: ONE comment line only, followed by a DIFFERENT, NONBLANK line.
date /t
REM If you followed a :: line directly with another one, the *2nd* one
REM would generate a spurious "The system cannot find the drive specified."
REM error message and potentially execute commands inside the comment.
REM In the following - commented-out - example, file "out.txt" would be
REM created (as an empty file), and the ECHO command would execute.
REM :: 1st line
REM :: 2nd line > out.txt & echo HERE
REM NOTE: If :: were used in the 2 cases explained below, the FOR statement
REM would *break altogether*, reporting:
REM 1st case: "The syntax of the command is incorrect."
REM 2nd case: ") was unexpected at this time."
REM Because the next line is *blank*, :: would NOT work here.
REM Because this is the *last line* in the block, :: would NOT work here.
)
Emulation of other comment styles - inline and multi-line:
Note that none of these styles are directly supported by the batch language, but can be emulated.
Inline comments:
* The code snippets below use ver as a stand-in for an arbitrary command, so as to facilitate experimentation.
* To make SET commands work correctly with inline comments, double-quote the name=value part; e.g., SET "foo=bar".[1]
In this context we can distinguish two subtypes:
EOL comments ([to-the-]end-of-line), which can be placed after a command, and invariably extend to the end of the line (again, courtesy of jeb's answer):
ver & REM <comment> takes advantage of the fact that REM is a valid command and & can be used to place an additional command after an existing one.
ver & :: <comment> works too, but is really only usable outside of (...) blocks, because its safe use there is even more limited than using :: standalone.
Intra-line comments, which be placed between multiple commands on a line or ideally even inside of a given command.
Intra-line comments are the most flexible (single-line) form and can by definition also be used as EOL comments.
ver & REM^. ^<comment^> & ver allows inserting a comment between commands (again, courtesy of jeb's answer), but note how < and > needed to be ^-escaped, because the following chars. cannot be used as-is: < > | (whereas unescaped & or && or || start the next command).
%= <comment> =%, as detailed in dbenham's great answer, is the most flexible form, because it can be placed inside a command (among the arguments).
It takes advantage of variable-expansion syntax in a way that ensures that the expression always expands to the empty string - as long as the comment text contains neither % nor :
Like REM, %= <comment> =% works well both outside and inside (...) blocks, but it is more visually distinctive; the only down-sides are that it is harder to type, easier to get wrong syntactically, and not widely known, which can hinder understanding of source code that uses the technique.
Multi-line (whole-line block) comments:
James K's answer shows how to use a goto statement and a label to delimit a multi-line comment of arbitrary length and content (which in his case he uses to store usage information).
Zee's answer shows how to use a "null label" to create a multi-line comment, although care must be taken to terminate all interior lines with ^.
Rob van der Woude's blog post mentions another somewhat obscure option that allows you to end a file with an arbitrary number of comment lines: An opening ( only causes everything that comes after to be ignored, as long as it doesn't contain a ( non-^-escaped) ), i.e., as long as the block is not closed.
[1] Using SET "foo=bar" to define variables - i.e., putting double quotes around the name and = and the value combined - is necessary in commands such as SET "foo=bar" & REM Set foo to bar., so as to ensure that what follows the intended variable value (up to the next command, in this case a single space) doesn't accidentally become part of it.
(As an aside: SET foo="bar" would not only not avoid the problem, it would make the double quotes part of the value).
Note that this problem is inherent to SET and even applies to accidental trailing whitespace following the value, so it is advisable to always use the SET "foo=bar" approach.
Another alternative is to express the comment as a variable expansion that always expands to nothing.
Variable names cannot contain =, except for undocumented dynamic variables like
%=ExitCode% and %=C:%. No variable name can ever contain an = after the 1st position. So I sometimes use the following to include comments within a parenthesized block:
::This comment hack is not always safe within parentheses.
(
%= This comment hack is always safe, even within parentheses =%
)
It is also a good method for incorporating in-line comments
dir junk >nul 2>&1 && %= If found =% echo found || %= else =% echo not found
The leading = is not necessary, but I like if for the symmetry.
There are two restrictions:
1) the comment cannot contain %
2) the comment cannot contain :
After I realized that I could use label :: to make comments and comment out code REM just looked plain ugly to me. As has been mentioned the double-colon can cause problems when used inside () blocked code, but I've discovered a work-around by alternating between the labels :: and :space
:: This, of course, does
:: not cause errors.
(
:: But
: neither
:: does
: this.
)
It's not ugly like REM, and actually adds a little style to your code.
So outside of code blocks I use :: and inside them I alternate between :: and :.
By the way, for large hunks of comments, like in the header of your batch file, you can avoid special commands and characters completely by simply gotoing over your comments. This let's you use any method or style of markup you want, despite that fact that if CMD ever actually tried to processes those lines it'd throw a hissy.
#echo off
goto :TopOfCode
=======================================================================
COOLCODE.BAT
Useage:
COOLCODE [/?] | [ [/a][/c:[##][a][b][c]] INPUTFILE OUTPUTFILE ]
Switches:
/? - This menu
/a - Some option
/c:## - Where ## is which line number to begin the processing at.
:a - Some optional method of processing
:b - A third option for processing
:c - A forth option
INPUTFILE - The file to process.
OUTPUTFILE - Store results here.
Notes:
Bla bla bla.
:TopOfCode
CODE
.
.
.
Use what ever notation you wish *'s, #'s etc.
This page tell that using "::" will be faster under certain constraints
Just a thing to consider when choosing
good question... I've been looking for this functionality for long too...
after several tests and tricks it seem the better solution is the more obvious one...
--> best way I found to do it, preventing parser integrity fail, is reusing REM:
echo this will show until the next REM &REM this will not show
you can also use multiline with the "NULL LABEL" trick...
(dont forget the ^ at the end of the line for continuity)
::(^
this is a multiline^
comment... inside a null label!^
dont forget the ^caret at the end-of-line^
to assure continuity of text^
)
James K, I'm sorry I was wrong in a fair portion of what I said. The test I did was the following:
#ECHO OFF
(
:: But
: neither
:: does
: this
:: also.
)
This meets your description of alternating but fails with a ") was unexpected at this time." error message.
I did some farther testing today and found that alternating isn't the key but it appears the key is having an even number of lines, not having any two lines in a row starting with double colons (::) and not ending in double colons. Consider the following:
#ECHO OFF
(
: But
: neither
: does
: this
: cause
: problems.
)
This works!
But also consider this:
#ECHO OFF
(
: Test1
: Test2
: Test3
: Test4
: Test5
ECHO.
)
The rule of having an even number of comments doesn't seems to apply when ending in a command.
Unfortunately this is just squirrelly enough that I'm not sure I want to use it.
Really, the best solution, and the safest that I can think of, is if a program like Notepad++ would read REM as double colons and then would write double colons back as REM statements when the file is saved. But I'm not aware of such a program and I'm not aware of any plugins for Notepad++ that does that either.
A very detailed and analytic discussion on the topic is available on THIS page
It has the example codes and the pros/cons of different options.
There are a number of ways to comment in a batch file
1)Using rem
This is the official way. It apparently takes longer to execute than ::, although it apparently stops parsing early, before the carets are processed. Percent expansion happens before rem and :: are identified, so incorrect percent usage i.e. %~ will cause errors if percents are present. Safe to use anywhere in code blocks.
2)Using labels :, :: or :; etc.
For :: comment, ': comment' is an invalid label name because it begins with an invalid character. It is okay to use a colon in the middle of a label though. If a space begins at the start of label, it is removed : label becomes :label. If a space or a colon appears in the middle of the label, the rest of the name is not interpreted meaning that if there are two labels :f:oo and :f rr, both will be interpreted as :f and only the later defined label in the file will be jumped to. The rest of the label is effectively a comment. There are multiple alternatives to ::, listed here. You can never goto or call a ::foo label. goto :foo and goto ::foo will not work.
They work fine outside of code blocks but after a label in a code block, invalid or not, there has to be a valid command line. :: comment is indeed another valid command. It interprets it as a command and not a label; the command has precedence. Which is the command to cd to the :: volume, which will work if you have executed subst :: C:\, otherwise you get a cannot find the volume error. That's why :; is arguably better because it cannot be interpreted in this way, and therefore is interpreted as a label instead, which serves as the valid command. This is not recursive, i.e, the next label does not need a command after it. That's why they come in twos.
You need to provide a valid command after the label e.g. echo something. A label in a code block has to come with at least one valid command, so the lines come in pairs of two. You will get an unexpected ) error if there is a space or a closing parenthesis on the next line. If there is a space between the two :: lines you will get an invalid syntax error.
You can also use the caret operator in the :: comment like so:
#echo off
echo hello
(
:;(^
this^
is^
a^
comment^
)
:;
)
:;^
this^
is^
a^
comment
:;
)
But you need the trailing :; for the reason stated above.
#echo off
(
echo hello
:;
:; comment
:; comment
:;
)
echo hello
It is fine as long as there is an even number. This is undoubtedly the best way to comment -- with 4 lines and :;. With :; you don't get any errors that need to be suppressed using 2> nul or subst :: C:\. You could use subst :: C:\ to make the volume not found error go away but it means you will have to also put C: in the code to prevent your working directory from becoming ::\.
To comment at the end of a line you can do
command &:: or command & rem comment, but there still has to be an even number, like so:
#echo off
(
echo hello & :;yes
echo hello & :;yes
:;
)
echo hello
The first echo hello & :;yes has a valid command on the next line but the second & :;yes does not, so it needs one i.e. the :;.
3)Using an invalid environment variable
%= comment =%. In a batch file, environment variables that are not defined are removed from the script. This makes it possible to use them at the end of a line without using &. It is custom to use an invalid environment variable i.e. one that contains an equals sign. The extra equals is not required but makes it look symmetrical. Also, variable names starting with "=" are reserved for undocumented dynamic variables. Those dynamic variables never end with "=", so by using an "=" at both the start and end of the comment, there is no possibility of a name clash. The comment cannot contain % or :.
#echo off
echo This is an example of an %= Inline Comment =% in the middle of a line.
4)As a command, redirecting stderr to nul
#echo off
(
echo hello
;this is a comment 2> nul
;this is another comment 2> nul
)
5)At the end of a file, everything after an unclosed parenthesis is a comment
#echo off
(
echo hello
)
(this is a comment
this is a comment
this is a comment
Sub-string expansion works within the set of a for loop (that is the parenthesised part after in) when immediate expansion is used (write %%I instead of %I in a batch file):
set "X=123"
for %I in (%X:~1,1%) do #echo %I
However, it fails when delayed expansion is applied:
for %I in (!X:~1,1!) do #echo %I
I would expect the output to be:
2
But instead it is:
!X:~1
1!
Why is this and how can I prevent that?
I know I could work around that by quoting the set and using ~, but this is not what I want here:
for %I in ("!X:~1,1!") do #echo %~I
The following command line fails too:
for %I in (!X:*2=!) do #echo %I
The unexpected output is:
!
Also for command lines using the /R, /L and /R switches fail with such sub-string syntax.
It surely has got something to do with the fact that , and = are token separators for cmd, just like SPACE, TAB, ;, etc.
According to the thread How does the Windows Command Interpreter (CMD.EXE) parse scripts? and also numerous comments here, the answer lies in the special way for loops are parsed.
The key is the following excerpt of this answer (see the italic text in particular):
Phase 2) Process special characters, tokenize, and build a cached command block:
[...]
Three commands get special handling - IF, FOR, and REM
[...]
FOR is split in two after the DO. A syntax error in the FOR construction will result in a fatal syntax error.
The portion through DO is the actual FOR iteration command that flows all the way through phase 7
All FOR options are fully parsed in phase 2.
The IN parenthesized clause treats <LF> as <space>. After the IN clause is parsed, all tokens are concatenated together to form a single token.
Consecutive token delimiters collapse into a single space throughout the FOR command through DO.
Due to the fact that delayed expansion happens after parsing of for and the described behaviour, token separators like SPACE, TAB, ,, ;, =, etc. become converted to a single SPACE, hence a sub-string expansion expression like !X:~1,1! is changed to !X:~1 1!, and a sub-string substitution expression like !X:*2=! is changed to !X:*2 !, which both are invalid syntax.
Therefore to solve the issue you need to escape the token separators by ^ like:
for %I in (!X:~1^,1!) do #echo %I
and:
for %I in (!X:*2^=!) do #echo %I
(By the way, there is a very similar problem with if statements.)
I have a simple batch test file test.bat with following lines:
#echo off
REM IF "%~version_info" == "" echo No version information found
echo test
When I run it I expected to get test instead I get:
The following usage of the path operator in batch-parameter
substitution is invalid: %~version_info" == "" echo No version information found
For valid formats type CALL /? or FOR /?
The syntax of the command is incorrect.
Why does batch try to interpret the comment? Or what is happening here? If I take the comment out, the script prints out test as expected.
Also the documentation doesn't mention anything of this.
I believe it's a consequence of the parsing sequence. In this case, it's a problem, but suppose you code (as I have done):
set "debug=rem"
%debug% echo some debug data
First, we replace the values in %vars%, then we interpret the line, using the first token as the command to execute. The above construct allows the command to be varied.
So there is a method to the madness...
The reason for this is the sequence of batch scripts.
The very first thing that happens is the (poor1) %-sign handling, that is the normal variable (%VAR%) and the command line argument (%1, %2, etc., and %*) expansion. Commands, and therefore even rem, are recognised in a later parsing phase.
The string %~ is an invalid argument reference, because there is neither a valid modifier or a combination of such (f, d, p, n, x, s, a, t, z, $PATH:), or a numeric digit following.
Refer to this thread: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
1... The % expansion is faulty in my opinion, because %~ or %VAR:=, %VAR:*=, in case variable VAR is defined, result in an error, and variable expansion like %VAR:[*]search=[replace]% or %VAR:~[position][,[length]]% becomes aborted in case VAR is not defined (so %VAR:~%STR% becomes expanded to ~text when STR is set to text).
It is not ignoring it.
In batch files you need to add %% and not % so it is simply warning you that the substitution is invalid. cmdline still reads comment lines and seeing as you have a valid command in it, but incorrect method, it will warn you.
By doing this, you will not get the warning:
#echo off
REM IF "%%~version_info" == "" echo No version information found
echo test
Why batch interpret comments?
It doesn't interpret comments, but it has to parse lines, that's the problem.
First, the parser reads a line.
Then it expands all percent expressions and then it takes a look at the first token in the line.
If the first token is REM then the remaining stuff will not be interpreted anymore (redirection, delayed expansion, pipes, ampersands, ..., are all ignored)
The problem is, that the parser first expands all percent expresssions, when there is a expression like %~ then the parser throws an error message.
If you do not want to modify the commented code, you can use:
REM %= IF "%~version_info" == "" echo No version information found
Though this is slightly ugly, it will avoid interpreting the %~.
You can find more information on comments in batch in this answer
As to why this is the case, the whole batch thing sounds a bit broken.
Is someone able to explain how cmd handles carats, in the following examples?
C:\>set ^^=test
C:\>echo %^%
test
C:\>echo ^%^%
test
C:\>echo %^^%
%^%
I figured that %^% would be handled as simply %%. I assume that the variable expansion is handled before the carat is considered, however that is a half arsed answer to a question that I'm sure could be more eloquently explained.
In batch -
#echo off
set ^^=test
echo %^%
echo ^%^%
echo %^^%
--
C:\>test.bat
test
test
ECHO is off.
It is because the order of how Batch processes each command line. Simply put, the variable expansion is performed before analyzing special characters. That is why the carat is consumed by the variable expansion before being removed as an escape character. This is also why percent characters have to be escaped by themselves %% instead of the standard carat ^ escape character.
Phase/order
1) Phase(Percent):
A double %% is replaced by a single %
Expansion of argument variables (%1, %2, etc.)
Expansion of %var%, if var does not exists replace it with nothing
For a complete explanation read this from dbenham Same thread: percent expansion
1.5) Remove all <CR> (CarriageReturn 0x0d) from the line
2) Phase(Special chars, " <LF> ^ & | < > ( ): Look at each character
If it is a quote (") toggle the quote flag, if the quote flag is active, the following special characters are no longer special: ^ & | < > ( ).
If it is a caret (^) the next character has no special meaning, the caret itself is removed, if the caret is the last character of the line, the next line is appended, the first charater of the next line is always handled as escaped character.
<LF> stops the parsing immediately, but not with a caret in front
For a full and great explanation (seriously bookmark this link!) see the answers here:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Direct Answer: https://stackoverflow.com/a/4095133/891976
Additionally to the answer of David Ruhrmann...
You create a variable named ^, as the parser will escape the second caret and remove the first one in the statement set ^^=test.
As David explained, the percent expansion phase is the first phase so it can expand even strange expressions like a <CR> character, but it's also the cause why you can't build a multiline percent expansion.
First the percents are expanded (and fails as there is only one) and then the mutliline caret is used to append the next line.
echo %va^
r%
But really confusing is the next caret example
set "^=one caret"
set "^^=two carets"
echo "%^%"
call echo "%%^%%"
The output is
"one caret"
"two carets"
It's because carets will be doubled by a CALL
I have a batch file which moves files from one folder to another. The batch file is generated by another process.
Some of the files I need to move have the string "%20" in them:
move /y "\\myserver\myfolder\file%20name.txt" "\\myserver\otherfolder"
This fails as it tries to find a file with the name:
\\myserver\myfolder\file0name.txt
Is there any way to ignore %? I'm not able to alter the file generated to escape this, such as by doubling percent signs (%%), escaping with / or ^ (caret), etc.
You need to use %% in this case. Normally using a ^ (caret) would work, but for % signs you need to double up.
In the case of %%1 or %%i or echo.%%~dp1, because % indicates input either from a command or from a variable (when surrounded with %; %variable%)
To achieve what you need:
move /y "\\myserver\myfolder\file%%20name.txt" "\\myserver\otherfolder"
I hope this helps!
The question's title is very generic, which inevitably draws many readers looking for a generic solution.
By contrast, the OP's problem is exotic: needing to deal with an auto-generated batch file that is ill-formed and cannot be modified: % signs are not properly escaped in it.
The accepted answer provides a clever solution to the specific - and exotic - problem, but is bound to create confusion with respect to the generic question.
If we focus on the generic question:
How do you use % as a literal character in a batch file / on the command line?
Inside a batch file, always escape % as %%, whether in unquoted strings or not; the following yields My %USERNAME% is jdoe, for instance:
echo My %%USERNAME%% is %USERNAME%
echo "My %%USERNAME%% is %USERNAME%"
On the command line (interactively) - as well as when using the shell-invoking functions of scripting languages - the behavior fundamentally differs from that inside batch files: technically, % cannot be escaped there and there is no single workaround that works in all situations:
In unquoted strings, you can use the "^ name-disrupter" trick: for simplicity, place a ^ before every % char, but note that you're not technically escaping % that way (see below for more); e.g., the following again yields something like My %USERNAME% is jdoe:
echo My ^%USERNAME^% is %USERNAME%
In double-quoted strings, you cannot escape % at all, but there are workarounds:
You can use unquoted strings as above, which then requires you to additionally ^-escape all other shell metacharacters, which is cumbersome; these metacharacters are: <space> & | < > "
Alternatively, unless you're invoking a batch file, , you can individually double-quote % chars as part of a compound argument (most external programs and scripting engines parse a compound argument such as "%"USERNAME"%" as verbatim string %USERNAME%):
some_exe My "%"USERNAME"%" is %USERNAME%
From scripting languages, if you know you're calling a binary executable, you may be able to avoid the whole problem by forgoing the shell-invoking functions in favor of the "shell-free" variants, such as using execFileSync instead of execSync in Node.js.
Optional background information re command-line (interactive) use:
Tip of the hat to jeb for his help with this section.
On the command line (interactively), % can technically not be escaped at all; while ^ is generally cmd.exe's escape character, it does not apply to %.
As stated, there is no solution for double-quoted strings, but there are workarounds for unquoted strings:
The reason that "^ name-disrupter" trick (something like ^%USERNAME^%) works is:
It "disrupts" the variable name; that is, in the example above cmd.exe looks for a variable named USERNAME^, which (hopefully) doesn't exist.
On the command line - unlike in batch files - references to undefined variables are retained as-is.
Technically, a single ^ inside the variable name - anywhere inside it, as long as it's not next to another ^ - is sufficient, so that %USERNAME^%, for instance, would be sufficient, but I suggest adopting the convention of methodically placing ^ before each and every % for simplicity, because it also works for cases such as up 20^%, where the disruption isn't even necessary, but is benign, so you can apply it methodically, without having to think about the specifics of the input string.
A ^ before an opening %, while not necessary, is benign, because ^ escapes the very next character, whether that character needs escaping - or, in this case, can be escaped - or not. The net effect is that such ^ instances are ultimately removed from unquoted strings.
Largely hypothetical caveat: ^ is actually a legal character in variable names (see jeb's example in the comments); if your variable name ends with ^, simply place the "disruptive" ^ somewhere else in the variable name, as long as it's not directly next to another ^ (as that would cause a ^ to appear in the resulting string).
That said, in the (very unlikely) event that your variable has a name such as ^b^, you're out of luck.
In batch files, the percent sign may be "escaped" by using a double percent sign ( %% ).
That way, a single percent sign will be used within the command line. from http://www.robvanderwoude.com/escapechars.php
I think I've got a partial solution working. If you're only looking to transfer files that have the "%20" string in their name and not looking for a broader solution, you can make a second batch file call the first with %%2 as the second parameter. This way, when your program tries to fetch the second parameter when it hits the %2 in the text name, it will replace the %2 with an escaped %2, leaving the file name unchanged.
Hope this works!
How to "escape" inside a batch file withoput modify the file**
The original question is about a generated file, that can't be modified, but contains lines like:
move /y "\\myserver\myfolder\file%20name.txt" "\\myserver\otherfolder"
That can be partly solved by calling the script with proper arguments (%1, %2, ...)
#echo off
set "per=%%"
call generated_file.bat %%per%%1 %%per%%2 %%per%%3 %%per%%4
This simply sets the arguments to:
arg1="%1"
arg2="%2"
...
How to add a literal percent sign on the command line
mklement0 describes the problem, that escaping the percent sign on the command line is tricky, and inside quotes it seems to be impossible.
But as always it can be solved with a little trick.
for %Q in ("%") do echo "file%~Q20name.txt"
%Q contains "%" and %~Q expands to only %, independent of quotes.
Or to avoid the %~ use
for /F %Q in ("%") do echo "file%Q20name.txt"
You should be able to use a caret (^) to escape a percent sign.
Editor's note: The link is dead now; either way: It is % itself that escapes %, but only in batch files, not at the command prompt; ^ never escapes %, but at the command prompt it can be used indirectly to prevent variable expansion, in unquoted strings only.
The reason %2 is disappearing is that the batch file is substituting the second argument passed in, and your seem to not have a second argument. One way to work around that would be to actually try foo.bat ^%1 ^%2... so that when a %2 is encountered in a command, it is actually substituted with a literal %2.