Escape characters of a file path argument for a batch file - batch-file

I was making a batch file to take dragged-and-dropped folders for program input. Everything was working fine until I passed a folder, which for the sake of this post, called foo&bar.
Checking what %1 contained inside the batch file looked like C:\path\to\foo or C:\path\to\foo\foo. If the file path were in quotes it would work, so the only working code that slightly takes this into effect is :
set arg1=%1
cd %arg1%*
set arg1="%CD%"
Which changes directory to the passed argument using wildcards. However this only works once for if there is another folder with un-escaped characters inside the parent folder, passing the child folder would result in the parent folders' value.
I tried the answer of this post, which suggests to output the argument using a remark and redirection statement during an #echo on sequence. However no progress occurred in rectifying the problem. Any suggestions?
To recap, I am looking for ways to pass folders with un-escaped characters as arguments to a batch file. The implementation should preferably be in a batch file, but answers using VBScript are welcome. However the starting program must be in batch as this is the only program of the 3 that accepts files as arguments.
To test this, create a batch file with following code:
#echo off
set "arg1=%~1"
echo "the passed path was %arg1%"
pause
Then create folders called foobar and foo&bar. Drag them onto the batch file to see their output. foo&bar will only return C:\path\to\foo.

OK, so the problem is that Explorer is passing this as the command line to cmd.exe:
C:\Windows\system32\cmd.exe /c ""C:\path\test.bat" C:\path\foo&bar"
The outermost quotes get stripped, and the command becomes
"C:\working\so46635563\test.bat" C:\path\foo&bar
which cmd.exe interprets similarly to
("C:\working\so46635563\test.bat" C:\path\foo) & bar
i.e., bar is considered to be a separate command, to be run after the batch file.
The best solution would be to drag-and-drop not directly onto the batch file but onto, say, a vbscript or a Powershell script or a plain old executable. That script could then run the batch file, either quoting the argument appropriately or putting the directory path into an environment variable rather than on the command line.
Alternatively, you can retrieve the original command string from %CMDCMDLINE% like this:
setlocal EnableDelayedExpansion
set "dirname=!CMDCMDLINE!"
set "dirname=%dirname:&=?%"
set "dirname=%dirname:" =*%"
set "dirname=%dirname:"=*%"
set "dirname=%dirname: =/%"
for /F "tokens=3 delims=*" %%i in ("%dirname%") do set dirname=%%i
set "dirname=%dirname:/= %"
set "dirname=%dirname:?=&%"
set dirname
pause
exit
Note the exit at the end; that is necessary so that cmd.exe doesn't try to run bar when it reaches the end of the script. Otherwise, if the part of the directory name after the & happens to be a valid command, it could cause trouble.
NB: I'm not sure how robust this script is.
I've tested it with the most obvious combinations, but YMMV. [It might be more sensible to use delayed expansion exclusively, I'm not sure. It doesn't seem to be necessary except in the first set command. Jeb's answer here might be a better choice if you're going this route.]
For the curious, the script works like this:
Load the original command line into dirname [necessary for the reason pointed out by jeb]
Replace all the & characters with ?
Replace all the quote marks with *
If a quote mark is followed by a space, suppress the space.
NB: it is necessary to suppress the space to deal with both the case where the path contains a space (in which case Explorer adds quote marks around it) and the case where it doesn't.
Replace all remaining spaces with /
NB: ? * and / are illegal in file names, so these replacements are safe.
At this point the string looks like this:
C:\Windows\system32\cmd.exe//c/**C:\path\test.bat**C:\path\foo?bar**
So we just need to pull out the third asterisk-delimited element, turn any forward slashes back into spaces and any question marks back into ampersands, and we're done. Phew!

Related

Batch Scripting percent tilde string and starting a process syntax understanding

I need help with understanding the following Batch script structure:
This is named Profile_something_schedule.bat
call somePath\lib.cmd :someLabel reqPath
call somePath\lib.cmd :someLabel reqKey
%reqPath% "%~someFileName" /vv_pwd=%reqKey% /bProfile_something_schedule /min
I have a lot of difficulty understanding why this script works.
I do not know why the 3rd line is valid. The behavior produced is that the someFileName is run. I understand it as starting the file as a process. Then why isn't the start command needed? I don't see any batch documentation saying you can simply run a file by writing its pathed filename.
I do not understand the syntax of "%~someFileName". From online searching about it almost every source shows you the batch call parameter table, saying things like %~1 expands %1..., %~f1 expands %1 some other way, etc. All of them involve some kind of number from 0 to 9 to correspond to the parameter position. However, I cannot find any specification of %~someString being legal. There is no parameter positional information from the someFileName string, it is a filename.extension string. Still, it is quite likely this line is trying to run this format.
What does "/vv_pwd=%reqKey% /bProfile_something_schedule" mean? In the lib.cmd that was called previously, there were variables reqPath and reqKey and I am quite certain it is trying to pass the value of reqPath and reqKey from the lib.cmd into the variables here and then I guess it is trying to use the reqKey value as a parameter, which is a password required to run the file.
Inspecting the file, it contains some script of some paid software specific format, it only has variable name v_pwd inside but not vv_pwd. I do not know what the /bProfile_... is for. The part without the /b is exactly this batch file's name. But together with the /b I don't know what it means. The /v and /b look like some kind of options to me but I cannot see any specification explaining as there is no command beginning line 3 just some path. I guess the /min option refers to starting window minimized which is an option for the command start, yet there is no option of /v. The /B in start means to start application without creating window, which is quite unnecessary to have /min if you are not going to create a window in the first place. And it doesn't make sense to use /B directly followed by some string of Profile_something_Schedule.
FYI, the lib.cmd starts with call %*, which I would consider as trying to call all passed parameters and assuming those parameters are actually batch files that can be called.
Another thought I have is that the 2nd line call connects with the 3rd line so the 3rd line may not need a command. But I can't make sense of it. The someFileName is not of the Batch extension so I doubt it can be called as the call doc says it is for batch programs. If I want to run non-batch programs I need to use start right?
Would greatly appreciate your help!
The variable pathext contains a semicolon-separated list of executable filenames that may be appended as an extension to myexecutable. if the first string on a batch line is not a cmd internal command (like set, for etc.) then cmd tries to find myexecutable + each of the extensions in pathext in turn, first in the current directory, and then in each directory in the path (another semicolon-separated list of directories) and runs the first name found, or fails if none are found. That first string may also have an Associated extension, which then runs the application with which the extension is associated (like .txt runs notepad by default)
Neither do I, and I can't see that even knowing what the actual strings being executed by %reqPath% are would assist. See for /? from the prompt for more documentation on other ~ operators - or search SO for thousands of uses.
vv_pwd=%reqKey% : %reqKey% is replaced by the value of the variable reqKey evidently returned by the previous line. / is used in Windows to mean "here's a switch parameter for the executable", so evidently /vv_pwd=[the contents of reqKey], /bProfile_something_schedule and /min mean something to the executable %reqPath%. Quite what is anyone's guess.
The fact that lib.cmd's first line is call %* would mean that lib.cmd contains a library of routines. Since each call you have shown is of the form :string1 string2 then the resultant command executed would be call :string1 string2. call :string1 will call the routine contained within "lib.cmd" with the label string1: supplying string2 (and presumably optionally string2 string3... as parameters. Evidently, string2 is the name of the variable into which lib.cmd places the required data.
Without the :, string1 would be any executable that cmd can locate using the method in (1). It does not have to be a batch, but commonly is a batch.

Use content of a file in a string

I have a file (let's call it version.txt) that contains a version number and some text:
v5.02
Some text explaining
where and how this
number is used
Based on this answer, I use
set /p version=<version.txt
to store the first line of the file in the version variable. Now I'm trying to write a batch script that operates on folders that contain this version number in their name. However, I get unexpected results because something seems to go wrong when I insert the variable in a path. For example, this script
#set /p version=<version.txt
#echo C:\some\folder\%version%\some\file.exe
prints
C:\some\folder\v5.02
instead of
C:\some\folder\v5.02\some\file.exe
What's going on? I have a feeling there are hidden characters of some sort at the end of the text in the variable, because setting the variable by hand to a constant in the script works.
Edit: I'm using Windows 10 with Notepad++ as my editor, if it helps.
I can only replicate your issue, when version.txt uses Unix line endings (LF) instead of Windows (CRLF). for /f is immune to this issue:
for /f "delims=" %%a in (version.txt) do set "verion=%%a" & goto :skip
:skip
echo C:\some\folder\%version%\some\file.exe
goto :skip breaks the loop after reading the first line.
Since everything I tried didn't seem to work, the solution I found in the end is to call the batch script from a Python script. The Python script reads the first line of the version file and passes it as an argument to the batch script. Out of context, it is a bit of an inelegant solution, but in my case the batch script was already called by a Python script, so it's not that terrible.
Here is a minimal example:
version.txt
v5.02
Some text explaining
where and how this
number is used
script.bat
#echo C:\some\folder\release\%1\some\file.exe
script.py
import os
with open("version.txt") as f:
version = f.readline().rstrip()
os.system("cmd /c script.bat %s" % version)
Edit: Following Stephan's comment, I tried to change the line ending in the text file from LF to CRLF and it indeed solves the problem. However, since I don't really have control over everything that writes in that file, the solution above remains the most feasible in my case.
Edit 2: Stephan's answer (with the for loop) is actually a better solution than this one since it avoids having to transfer part of the work to the calling Python script.

CMD file will not run my command in the else block

I'm writing a CMD script to execute a Java program. The program requires at least one argument so I created an if else block to check that argument one %1 is not blank. When I run the script without providing argument one I get the expected you must provide an argument to run. But when I do provide the argument the program does not execute. Additionally if I don't have #echo off set, the entire script prints out as text in the window when I do provide the proper argument.
Here's the full script:
set APP_HOME=C:\Temp\Hammer
rem Set APP_HOME to the place where you installed Hammer
if ["%1"] == [""] (
echo you must provide a python script to run
) else (
java -cp %APP_HOME%\lib\jython.jar;%APP_HOME%\lib\hammer.jar;%CLASSPATH% bridenstine.hammer.main.Main %1 %2
)
I think this is a problem with the script and not the program itself because when I run the line that's in the else block by itself without the script,
java -cp C:\Temp\Hammer\lib\jython.jar;C:\Temp\Hammer\lib\hammer.jar;%CLASSPATH% bridenstine.hammer.main.Main argument1
The program executes normally. I've been looking at example scripts and cross referencing this site for CMD files and what I have seems to be valid. Do I have a syntax error?
Update:
I'm running the script like so,
cd C:\Temp\Hammer
bin\ProgramScript.cmd argument1
Update 2:
Someone pointed out that when they run this script they get an error message saying Java is not recognized as an internal or external command (the expected message when Java is not set on their PATH) But they made a good point that this means the script is in fact getting inside the else block. I then pointed out the following,
After I run the script with a valid argument it prints out the entire script as text on the command prompt. I am then able to mark the line from inside the else statement (that was printed), copy it, paste it, and it runs the program fine. So the Java command seems to be valid. But you make a good point that the script is obviously getting inside the else block...something is still going wrong here and it doesn't seem to be the program.
Note:
If relevant I am using Windows 8.1 and I am using the standard command prompt, not one that has administrative privileges (the results remained the same regardless of using a command prompt with administrative privileges).
I suggest to use
set APP_HOME=C:\Temp\Hammer
rem Set APP_HOME to the place where you installed Hammer.
if "%~1"=="" (
echo You must provide a Python script to run.
pause
) else (
java.exe -cp "%APP_HOME%\lib\jython.jar;%APP_HOME%\lib\hammer.jar;%CLASSPATH%" bridenstine.hammer.main.Main %*
)
It is always better to enclose an argument string in double quotes if it contains environment variables like CLASSPATH which might have a string value containing 1 or more spaces.
%* is expanded by all arguments passed to the batch file as argument, i.e. %1 %2 ...
It is best to always specify an application like java with full path and file extension as otherwise Windows has to search for a file with a file extension as defined in environment variable PATHEXT in current working directory and all directories defined in environment variable PATH. At least the file extension should be in the batch file if the program files directory of the application varies.
I can only offer a suggestion; I've not tried this.
I would try escaping each ; with a caret ^ thus:
java -cp %APP_HOME%\lib\jython.jar^;%APP_HOME%\lib\hammer.jar^;%CLASSPATH% bridenstine.hammer.main.Main %1 %2
(But I'll admit it's clutching at straws...)
You are missing you she-bang at the top of the script
#!/bin/bash

How can I run same exe file consecutively N times?

I have an exe file. I want to run it several times repeatedly one after another. I tried below batch file but couldnt do it is there a way to do it? Sorry but i am a rookie in code writing
#echo off
start ran.exe
start ran.exe
start ran.exe
Assuming that ran.exe is in the current folder or on your path, then you simply write:
#echo off
ran
ran
ran
to invoke it three times. If it is not found on the PATH, then use a fully qualified name like this:
#echo off
c:\path\to\ran
c:\path\to\ran
c:\path\to\ran
Running a program is the normal effect of naming it on a line of a batch file.
Furthermore, because .EXE is listed in the PATHEXT environment variable, you don't need to include that in the name, unless there is also a file name ran.com since .COM is listed in the default value of PATHEXT ahead of .EXE.
The START builtin command is only needed in batch files for handling some special cases. See the output of START /?1 for its documentation. In general, you don't need it just to launch programs.
Update: To generalize this to N invocations, use the FOR command. FOR is extremely powerful, type FOR /? at the command prompt for documentation. For N repeats specified as the argument to the batch file, and passing the current count to the command as its first argument do this:
#echo off
for /L %%N in (1,1,%1) do c:\path\to\ran %%N
The tricky thing to remember with FOR is that the iteration variable must be named with two percent signs in batch files. The help text says that, but only in passing.
Update 2: Some more details and explanation.
In this case, we want to repeat a command N times. FOR supports a variety of kinds of loops, but the easiest way to get exactly N iterations is to use its /L option which uses a starting value, a step size, and an ending value to define the number of iterations.
These are specified in parenthesis, as FOR /L %%N (start,step,end). To get a simple counter of 1 to N, we tell it to start at 1, step by 1, and to stop at the value of the first argument to the batch file which is named %1.
The arguments to the batch file itself are named as %1 through %9, and %* names all of the arguments. Note that there is a vast minefield of subtlety here related to properly quoting file names that contain spaces. To keep life simple, try very hard not to need to do that. Otherwise, CALL /? documents the command line argument conventions, and SET /? documents many things related to general batch file variables.
Other forms of FOR let you iterate over files (no option), directories (/D), directories recursively in a tree (/R), or various parts of the contents of files (/F).

How do I set an environment variable to a value with spaces in a batch file?

I don't know how to describe exactly what I'm trying to do but here's an example batch file that demonstrates what I can't figure out.:
I've got a batch file. Inside that batch file I'm trying to create a directory:
Set CopyFrom = %~dp0
if Exist "%ProgramFiles(x86)" (
Set TargetDir = %ProgramFiles(x86)%\My Directory Name has spaces
)
md %TargetDir%\NewSubFolder
copy %CopyFrom%\SourceFile.zip %TargetDir%\NewSubFolder
My batch file is failing on line 4 Set TargetDir =... with:
\My was unexpected at this time
I'm assuming this is because I have spaces in my path name. I thought I could just wrap my variable with quotes:
Set TargetDir = "%ProgramFiles(x86)%\My Directory Name has spaces"
But then when I get to the line that creates the directory it fails because %TargetDir% is now wrapped in quotes. md "%TargetDir%"\NewSubFolder
Can this be fixed or should I just write a VBScript to sort things out?
Just put your expression in quotes like this:
C:\>Set "TargetDir=%ProgramFiles%\My Directory Name has spaces"
C:\>echo %TargetDir%
C:\Program Files\My Directory Name has spaces
Note: It will expand the variable within the quotes, and if it too has spaces, it will need to be quoted.
Now you can quote it to perform your operation:
md "%TargetDir%\NewSubFolder"
The problem in question here are not the spaces as others suggested, but rather the closing parenthesis in the environment variable ProgramFiles(x86) This causes the parser to think that the block ends prematurely (shameless self-promotion).
Quotes do help in this case because they make the parser jump over the whole quoted part and rightly assume the following parenthesis to be the actual closing one. but the fix might be much easier than that:
if Exist "%ProgramFiles(x86)%" Set TargetDir=%ProgramFiles(x86)%\My Directory Name has spaces
Why use a parenthesized block at all if all you do it put exactly one command into it?
set itself doesn't need any quotes, except when its arguments contain special characters like <, >, |, & which the shell itself aready handles. It isn't a panacea, though which makes handling user input or file contents correctly a pain at times.
Also, please never ever put spaces around the = in a set command. This will cause an environment variable to be created with its name ending in a space and its contents starting with a space. This was partially corrected in Windows 7 by silently creating both the variable with the space at the end and one without:
> set foo = bar
> set foo
foo=bar
foo = bar
But in previous versions of Windows this didn't happen so just never use spaces around the = unless you know this is what you want :-)

Resources