I've recently been trying to make a program to simply store text to a file for later viewing, storing it as a .rar file for security against those who don't understand how to extract the text from the .rar (i.e. the less "techy" people)...
I have, however, encountered an error in the program that results in the <word> not expected at this time followed by the .exe closing when I input add/<word> <word>... (i.e. any multi-word string with spaces in between the words [add/<word>, however, does function properly]).
Is there a special rule that must be followed for storing multi-word strings to a .rar or a file in general (I do, however, know that there is a rule for creating/renaming folders/directories)?
The Program Segment:
:command
cls
set /p journal=<journal.rar
echo %journal%
echo.
set /p command=What would you like to do?
cls
if %command%==exit exit
if %command%==help goto help
if %command%==delete echo START; > journal.rar
if %command:~0,4%==add/ echo "%journal%""%command:~4%;" > journal.rar
if %command:~0,5%==edit/ echo %journal:%command:~5%=%;" > journal.rar
goto command
Excuse me. Your question is not clear. There are several confusing points in it, like "followed by the .exe closing" (which .exe is closing?), and the fact that your question is NOT related to .rar files in any way, nor to "storing multi-word strings". However, I can see the following points in it:
When a variable value is expanded with percent signs this way: %command% you must be aware that the variable is first expanded and then the resulting line is parsed. This mean that the value of the variable may introduce errors in the line. For example, in this line: if %command%==exit exit, if the value of command variable is add/one two three, then the line that is parsed is this: if add/one two three==exit exit that, of course, issue an error! (type if /? for further details).
The way to avoid this problem is enclosing both the variable and the comparison value in quotes; this way, if the value have several words with spaces, the whole value is grouped in the IF command for comparison purposes: if "%command%" == "exit" exit. This must be done in every IF command that use the value of the variable.
In the following line:
if %command:~0,5%==edit/ echo %journal:%command:~5%=%;" > journal.rar
you must be aware that the line is parsed from left to right; this means that you can not nest a %variable% expansion inside another one. The way to solve this problem is first complete a %normal% variable expansion, and then a !delayed! variable expansion that will take the previous expanded value. To do that, insert this line at beginning of your program:
setlocal EnableDelayedExpansion
and change previous line by this one:
if "%command:~0,5%" == "edit/" echo !journal:%command:~5%=!;" > journal.rar
For further details, type set /? and carefully read the sections about "delayed expansion".
Here is a sample that can accept multiple words:
set "command="
set /p "command=What would you like to do? "
cls
if /i "%command%"=="have lunch" goto :food
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
I'm using windows 10, running batch files through the command prompt window.
I can make things work, but I don't know why it works or why I can't do certain things:
set "file_list=a1 a2"
for %%a in (%file_list%) do (
echo %%a.py
)
This little piece of code works. I can build on it, BUT
Q1: I want to change the variable %%a to %%filename... but that doesn't work! I wondered if maybe filename were reserved, so I tried %%fname .
In this case I get the error:
%fname was unexpected at this time.
I can do a set fromm the command line and use a descriptive variable name, but it doesn't seem to work when looping. (I did it with the %file_list% variable above!) So how come I can only use a single character for a loop variable? Is there some way around that?
Q1a. This makes me think that the loop index variable is a different kind of variable that the ones in set commands. Is that correct? If so, is there a link that clearly and concisely explains the difference?
Q2. I notice the loop index variable is %%a, instead of a or %a or %a% . I never would have guessed this. The web sites I've looked at have just said, do this. But I can't see any explanation of why, except that the first percent is an escape. Okay. That doesn't really explain anything. It just means "this is how you do it." The error message when I use one percent sign is interesting.
set "file_list=a1 a2"
for %a in (%file_list%) do (
echo %a.py
)
"file_list) was unexpected at this time."
So I can vaguely see that maybe something isn't being escaped correctly. Why does that % in the %a need to be escaped, so it becomes %%a ?
A for meta-variable must consist of % (in Command Prompt) or %% (in a batch file) and a single character (case-sensitive letter), like %a or %%a. You cannot define %filename or %%filename.
Loop variables only exist within the respective for loop. Do not confuse such loop variables with normal environment variables, like %TEMP%, for example, which are available globally.
There are these things marked by %-signs:
%-escaping (only applicable for batch files), so %% denotes one literal %-sign;
command line arguments/parameters (only applicable for batch files, obviously), like %1;
immediately expanded environment variables*, like %TEMP%;
for meta-variables, like %a (in Command Prompt) or %%a (in batch files), which are specific to the for command, so they do not exist outside of the related loop context;
%-escaping (1.) happens before expanding for meta-variables (4.), hence actually the for command receives a loop variable like %a.
Then environment variables (3.) are treated differently in Command Prompt and in batch files: the former keeps undefined variables literally, the latter removes them.
The detailed parsing rules can be found in this post, which have been implemented by Microsoft (or IBM?) that way in order to be able to distinguish between the different %-things, so at the end it was their decision, therefore you have to ask them for the exact reason…
*) There is also something like delayed environment variable expansion, but this uses !-signs to mark the variables, like !TEMP!, and this is something that happens after all the %-sign expressions have been parsed.
I have a batch file which is calling another batch file. batch file 1 is parsing 5 parameters to batch file 2
When I parse the parameters from batch file 1 to batch file 2, it parses correctly but when I assigned those parsed parameters to use them in batch file 2, it breaks.
batch file 1:
ECHO
SET sql=SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
SET pref=W
SET num=0
SET day=Friday
SET config=SampleSuperStore.txt
CALL Z:\XXX\RunTableauRefreshAutomatic.bat %sql% %pref% %num% %day% %config%
batch file 2:
CALL C:\XXX\anaconda3\Scripts\activate.bat
SET sql=%~1
SET pref=%~2
SET num=%~3
SET day=%~4
SET config=%~5
C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py %sql% %pref% %num% %day% %config%
PAUSE
Response from batch file 2:
Z:\XXX>CALL
C:\XXX\anaconda3\Scripts\activate.bat
(base) Z:\XXX>SET sql=SELECT
(base) Z:\XXX>SET
pref=MAX("Date")
(base) Z:\XXX>SET num=FROM
(base) Z:\XXX>SET
day=SQ_TEST."Sample - Superstore Ran"
(base) Z:\XXX>SET config=W
(base)
Z:\XXX>C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py
SELECT MAX("Date") FROM DL_SQ_TEST."Sample - Superstore Ran" W
(base) Z:\XXX>PAUSE Press any
key to continue . . .
Update: When I remove the double quotes in sql, it works as expected but I need them in it.
Additionally I tried using ^ but the batch file 2 still breaks it differently
#echo off
setlocal
SET "sql=SELECT MAX("Date") FROM SQ_TEST.""Sample - Superstore Ran"";"
call :otherbatch "%sql%" two three
goto :eof
:otherbatch
#echo off
set "var=%~1"
set "var=%var:""="%"
echo one :%var%
echo two :%~2
echo thre:%~3
goto :eof
(it works the same whether calling a separate batch file or a subroutine, so I worked with the latter to show the principle.)
The trick is to ensure the parts with spaces (or other poisonous characters) are properly quoted.
Sadly, this involves some thinking, counting and trying. I see no safe way to automate this (besides a lot of code).
For this special string
SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
the string "%sql%" becomes:
"SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";"
SiiiiiiiiiiiEooooSiiiiiiiiiiiiiiiEoooooooooooooooooooooooSiE
The line below shows, whether a part of the string is inside or outside of proper quoting with Start and End of quoting.
The next step is to ensure each SPACE (or any other delimiter) is quoted (inside) by adding quotes where needed (not around the spaces themselves, but around the substrings that contains them, using the positions of already present quotes).
This changes the string to :
"SELECT MAX("Date") FROM SQ_TEST.""Sample - Superstore Ran"";"
SiiiiiiiiiiiEooooSiiiiiiiiiiiiiiiESiiiiiiiiiiiiiiiiiiiiiiiESoE
Now every space is properly quoted (and you can see, it's difficult as other "unrelated" parts may change their inside/outside status). So it can be passed as a single parameter.
Of course that means, at the receiving side there are some superfluent quotes, which have to be deleted. We can easily (at least for this example) do that by replacing each "" with ":
set "var=%~1"
set "var=%var:""=%"
Note: that's very specific to the string to process, so I tried to explain step by step, what's to be done. You'll have to rethink the whole process for each specific string and I'm sure there are combinations where this approach would be useless.
You have potentially more problems than just spaces!
Poison characters like &, |, >, and < are another problem when passing parameters to a CALLed script. If the desired value is &"&" then it is impossible to pass that value as a literal string without escaping as the unquoted ^&"&" or as a quoted "&"^&"". But escaping dynamic strings is difficult / totally impractical. And sometimes a value must pass through multiple CALLs, each one needing its own round of escaping.
The situation is even worse when passing a caret - It is impossible to pass ^"^". You can double the unquoted ^, but the quoted "^" is a problem because the CALL command doubles quoted ^, and there is absolutely nothing you can do to prevent it. for example CALL ECHO ^^"^" yields ^"^^"!
For anyone doing any advanced scripting, one of the first tricks to learn is to pass values by reference instead of as literals. The calling script stores the value in a variable, and passes the name of the variable to the CALLed script. The CALLed script then uses delayed expansion to access the value. All the nasty batch problems are solved very simply :-)
modified batch file 1:
ECHO
SET sql=SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
SET pref=W
SET num=0
SET day=Friday
SET config=SampleSuperStore.txt
CALL Z:\XXX\RunTableauRefreshAutomatic.bat sql %pref% %num% %day% %config%
modified batch file 2:
setlocal enableDelayedExpansion
CALL C:\XXX\anaconda3\Scripts\activate.bat
SET sql=!%~1!
SET pref=%~2
SET num=%~3
SET day=%~4
SET config=%~5
:: I doubt this next line works properly.
C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py !sql! %pref% %num% %day% %config%
:: You probably need to change your python script to read the sql value from an environment
:: variable so you can then pass the value by reference just as we did with batch.
::
:: C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py sql %pref% %num% %day% %config%
::
:: Otherwise you will need to escape your quote literals - I believe python uses `\"`
PAUSE
One additional thing to be wary of - String literals containing ! will be corrupted if accessed while delayed expansion is enabled. So in addition to all the "problem" cases listed above, you also should pass by reference if the value may contain !.
Note that your python script also has potential issues with parameters containing " literals. I discuss that in the comments in modified batch 2.
The Problem
In a main batch file, values are pulled from a .txt file (and SET as values of variables within this batch file). These values may each contain % characters.
These are read from the .txt file with no issues. However, when a variable with a value containing a % character is passed to a second batch file, the second batch file interprets any % characters as a variable expansion. (Note: There is no control over the second batch file.)
Example
echo %PERCENTVARIABLE%
Output: I%LOVE%PERCENT%CHARACTERS%
When passed to a second file and then echo'ed, would (probably) become IPERCENT, as it interprets %LOVE% and %CHARACTERS% as unset variables.
Research
I found the syntax to find and replace elements within a string in a batch file, as I thought I could potentially replace a % character with %% in order to escape it. However I cannot get it to work.
The syntax is -
set string=This is my string to work with.
set string=%string:work=play%
echo %string%
Where the output would then be This is my string to play with..
Questions
Is it possible to escape % characters using the find and replace syntax
in a variable? (If not, is there another way?)
Is it advisable to do so? (Could using these escape characters cause any issue in the second batch file which (as mentioned above) we would have no control over?)
Is there another way to handle this issue, if the above is not possible?
There are no simple rules that can be applied in all situations.
There are a few issues that make working with string literals in parameters difficult:
Poison characters like &, |, etc. must be escaped or quoted. Escaping is difficult because it can be confusing as to how many times to escape. So the recommendation is to usually quote the string.
Token delimiters like <space>, <tab>, =, ; and , cannot be included in a parameter value unless it is quoted.
A CALL to a script will double any quoted % characters, and there is no way to prevent this. Executing a script without CALL will not double the % characters. But if a script calls another script and expects control to be returned, then CALL must be used.
So we have a catch-22: On the one hand, we want to quote parameters to protect against poison characters and spaces (token delimiters). But to protect percents we don't want to quote.
The only reliable method to reliably pass string literals without concern of value corruption is to pass them by reference via environment variables.
The value to be passed should be stored in an environment value. Quotes and/or escapes and/or percent doubling is used to get the necessary characters in the value, but it is very manageable.
The name of the variable is passed in as a parameter.
The script accesses the value via delayed expansion. For example, if the first parameter is the name of a variable containing the value, then it is accessed as !%1!. Delayed expansion must be enabled before that syntax can be used - simply issue setlocal enableDelayedExpansion.
The beauty of delayed expansion is you never have to worry about corruption of poison characters, spaces, or percents when the variable is expanded.
Here is an example that shows how the following string literal can be passed to a subroutine
"<%|,;^> This & that!" & the other thing! <%|,;^>
#echo off
setlocal enableDelayedExpansion
set "parm1="^<%%^|,;^^^^^> This ^& that^^!" & the other thing^! <%%|,;^^^>"
echo The value before CALL is !parm1!
call :test parm1
exit /b
:test
echo The value after CALL is !%1!
-- OUTPUT --
The value before CALL is "<%|,;^> This & that!" & the other thing! <%|,;^>
The value after CALL is "<%|,;^> This & that!" & the other thing! <%|,;^>
But you state that you have no control over the 2nd called script. So the above elegant solution won't work for you.
If you were to show the code of the 2nd script, and show exactly what value you were trying to pass, then I might be able to give a solution that would work in that isolated situation. But there are some values that simply cannot be passed unless delayed expansion is used with variable names. (Actually, another option is to put the value in a file and read the value from the file, but that also requires change to your 2nd script)
may be...?
input.txt
I%LOVE%PERCENT%CHARACTERS%
batch1.bat
#echo off
setlocal enableDelayedExpansion
set/P var=<input.txt
echo(In batch 1 var content: %var%
set "var=!var:%%=%%%%!"
call batch2.bat "%var%"
endlocal
exit/B
batch2.bat
#echo off
set "var=%~1"
echo(In batch 2 var content: %var%
exit/B
Within my batch file I have a variable that contains a file path:
SET VAR1=C:\Folder1\Folder2\File.txt
I would like to extract on the directory structure and retreive:
C:\Folder1\Folder2\
I have read threads like this where I need to use %~dp0 where 0 I believe is passed as a parameter. I have tried %~dpVAR1 but that doesn't work. How can I get the output I'm looking for, but with a variable containing the file path?
Also, to make matters difficult, I have to perform all of this within an IF condition which means that once the variable is declared, I will need to refer to it with ! instead of % (I have declared setlocal enableextensions enabledelayedexpansion at the beginning of my script to allow for this).
Any help is much appreciated!
Thanks!
Andrew
You are attempting to use parameter expansion syntax on an environment variable - that cannot work. But it is relatively easy to do what you want.
Using a CALL (relatively slow):
(...
call :getPath "!var!" var
...
)
exit /b
:getPath
set "%2=%~dp1"
exit /b
Using FOR, assuming the variable does not contain any wildcards (fast)
(...
for %%F in ("!var!") do set "var=%%~dpF"
...
)
Using FOR, if the variable may contain wildcards (also fast)
(...
for /f "delims=" %%F in ("!var!") do set "var=%%~dpF"
...
)
Note 1: If the variable does not contain the full path, then all the solutions will attempt to resolve the name into an absolute path and will return the full absolute path. For example, if var contains foobar\test.txt, then the solutions will include the full path to the current directory, even if the file is not found. Something like c:\pathToCurrentDirectory\foobar\.
Note 2: All solutions above will remove all quotes from the path.
Note 3: A path could include the ! character, which will cause problems when expanding %~dp1 or %%~dpF because you have delayed expansion enabled. The delayed expansion will corrupt both ^ and ! if the value contains !. There is a solution that involves protecting both ! and ^. Here is a demonstration applied to the last solution above. The protection requires normal expansion, and since you are within a code block, it requires at least one CALL. It could be done without a subroutine, but it is easier with a subroutine. The subroutine assumes the variable is named var.
(...
call :getPath
...
)
exit /b
:getPath
set "var=!var:"=!"
set "var=!var:^=^^^^!"
set "var=%var:!=^^^!%" !
for /f "delims=" %%F in ("!var!") do set "var=%%~dpF" !
exit /b
I do believe (once again) many questions are on the same topic (string constraints, or splitting strings).
Instead of giving you the whole code, I'm going to give you a template and explain why %~dpVAR! didn't work.
Firstly, why %~dpVAR! did't work.
Before I get into modifiers, let's discuss parameters. You may know that batch files can parse parameters to each other. These parameters can be called by using a single percent sign (%) in front of the numbers 0-9. As far as I'm aware (someone might have made a way for more to be parsed), only 9 parameters can be parsed. You may think that is wrong (there's 10 parameters right?). Parameters 1-9 are parsed to the batch file (or function within one), %0 is the file path of the batch file (or function name). If you look, %~dp0 shares some (not really) resemblance to %0. This will be discussed below.
Secondly, the term %~dp0 has modifiers in it. Modifiers are things that modify variables (only in the case of parameters and those declared in for loops, you know the ones with double percent signs like %%i) and parameters. The modifier d expands the parameter to a drive letter only while p expands the parameter to a path only. You may think that these would contradict themselves, but parameters can be combined to create extremely wacky formats.
So, as you can see, you attempt at replacing 0 with your variable name failed because it's not specified for those sort of things.
Now, on to the template.
You can constrain variables (and put them into other variables) like this:
set variable=!variable:~offset,amount!
Don't worry if that seems confusing, I'm about to explain the components.
Firstly, notice that there is no /a switch. This is because this is not a mathematical function (don't really know why I added this). So, before I explain it, here's an example of what it would do to a variable name numbers that has the value of 0123456789.
set numbers=!numbers:~5,1!
By using that line of code, numbers would now equal 5. This is because it is recreating the variable with a smaller version of the original value (gee this is hard to explain). As you can see, there is a 5 where offset was on the template above. This is because it is skipping the first 5 characters and setting the variable as the next amount, or 1 character (I really hope you're getting this).
So basically, it sets a variable as a shorter value of a different (or the same) variable determined by the offset and the amount of characters to contain in it.
I really hope this helps because I probably wouldn't understand a word of this.
Can someone redirect this poor guy to a link explaining this better (I tried, ok!)?
Complete example of extracting paths from variable:
#echo off
set /p Fullpath="Specify full path: "
call :getPath %Fullpath% filename folder
echo %filename%
echo %folder%
pause
exit /b
:getPath
set "%2=%~nx1"
set "%3=%~dp1"
exit /b
Would this work:
SET VAR1=C:\Folder1\Folder2\File.txt
echo %var1%
Where Echo is the name of your exe.
%CD% may work as well: Echo %CD%