Batch file: parsing command line inputs - windows

I am working on a batch file to use as a wrapper for some data processing modules that are already written, with the goal of being able to run some in parallel and others serially as needed/processing power allows. The underlying modules require one input (a path contained in double quotes), and optionally another path contained in double quotes and/or a set of flags, and I am trying to figure out how to test these inputs. I currently have
:TEST_PARAMS
REM Make sure parameters are correct; if not, display a usage message
IF NOT "%1"=="" (SET SUBBASENAME=%1
) ELSE (
GOTO :USAGE
)
IF NOT "%2"=="" (SET ATLASBASENAME=%2
) ELSE (
SET ATLASBASENAME=%DEFAULTATLAS%
)
IF NOT "%3"=="" (SET FLAGS=%3
) ELSE (
SET FLAGS=""
)
GOTO :START_SVREG
This seems to parse everything correctly if things are input in the correct order. However, I also wanted to check if the flags (which will be preceeded by '-') are popping up as the first or second inputs, and if they are, to display a usage message/set the variables correctly as needed. I figured the easiest way to do this would be to see if the first character of these strings is a '-', but I can't find any way to do this. I found a snippet of code that checks if a string contains a certain substring by replacing the substring with empty strings and then seeing if the resulting string is the same as the original, but this wouldn't work as people might legitimately have hyphens somewhere else in their path(s). Is there a way to check whether the first character of a string matches a given character in a batch file, or a better way of doing what I want?

You really don't need to check "-" in any batch file argument because as a batch file creator you will know what commands can be processed or not. Means if use "-b" then you know how to process it or if user passed "-t" you will know how to process. In your batch file you just need to process all the arguments, no matter in which order they are, and in your batch file you will have sections to process it. Here is an example Batch file which can process any number of arguments with "-" or "/" used with them:
#echo off
if [%1]==[] goto usage
:CHECKFORSWITCHES
#echo Execute the Command
IF '%1'=='/param1' GOTO PARAM1
IF '%1'=='-param2' GOTO PARAM2
IF '%1'=='/param3' GOTO PARAM3
IF '%1'=='-param4' GOTO PARAM4
IF '%1'=='/param5' GOTO PARAM5
goto :eof
:PARAM1
#echo This is Param1
set var=%1
set var2=%var:~0,1%
echo %var2%
SHIFT
goto :CHECKFORSWITCHES
:PARAM2
#echo This is Param2
SHIFT
goto :CHECKFORSWITCHES
:PARAM3
#echo This is Param3
SHIFT
goto :CHECKFORSWITCHES
:PARAM4
#echo This is Param4
set var=%1
set var2=%var:~0,1%
echo %var2%
SHIFT
goto :CHECKFORSWITCHES
:PARAM5
#echo This is Param5
SHIFT
goto :CHECKFORSWITCHES
:usage
#echo Usage: %0 ^<Write your command here^>
exit /B 1
Above script also checks for very first character of any parameter so if you need to use that code separately, you sure can use it however I don't think it is a good way to do it.
Above if you need to write some logic based in "-" or "/", just use IF %var2%=='/" GOTO XXX and you can do whatever you are looking for.

You can transfer the argument to an environment variable, and then use a SET substring operation to look at the first character. (Type HELP SET from the command line to get info about substring operations)
#echo off
set "var=%~1"
if defined var if "%var:~0,1%" equ "-" echo arg 1 is a flag

Related

How to obtain optional command line argument value as a string in windows batch script?

Given an optional port argument, where the port number can vary in length, how do I obtain the port number from the batch script's command line arguments?
Example:
foo.bat --foo bar --port 80 --bar foo
Should output:
80
I got this far, trying to use substring.
set CMD_LINE_ARGS=%*
#rem Remove all chars and port arg
set PORT_ARG_REMOVED=%CMD_LINE_ARGS:*-port =%
#rem Obviously, this is where I fail to remove trailing chars
set PORT_NUM=%PORT_ARG_REMOVED: *=%
echo %PORT_NUM%
Edit
The answer I chose is because it fits with my very particular use case, where all arguments were being passed through to the command that I was wrapping. And, I only needed the value for a particular optional argument. No looping required.
There are some very nice answers here for dealing with optional argument parsing in general. So, feel free to upvote everyone.
Seeing as you 'would have really have liked to see the substring work', here's your script structured and coded as you'd intended.
#Set "CMD_LINE_ARGS=%*"
#Rem Remove all chars and port arg
#Set "PORT_ARG_REMOVED=%CMD_LINE_ARGS:*-port =%"
#Rem Remove trailing chars
#Set "PORT_NUM=%PORT_ARG_REMOVED: ="&:"%"
#Echo %PORT_NUM%
#Pause
You can use %1, %2 etc for the separate command line arguments. In your case --port would be %3 and its value would be %4.
Fortunately, there is also the shift command, which shifts all arguments, to 2 becomes 1, 3 becomes 2, etc.
That means that you can 'scrape' all command line parameters in a loop. You keep shifting and when you encounter --port, you know that the next one is going to be the port number, which you can store in an appropriate variable (using set) for later use.
If you implement it like that, you can implement a bunch of optional parameters and the order won't matter either.
So in code, your 'foo.bat' could look like this:
#echo off
:: No parameters given? Then run ourselves with the defaults from the question.
if "%1"=="" goto none
:: Actual reading of parameters starts here.
:loop
if "%1"=="--port" set port=%2
:: Of course you need the other ifs only if you're interested in these parameters
if "%1"=="--foo" set foo=%2
if "%1"=="--bar" set bar=%2
:: Shift all parameters, except %0. Do it twice to skip the parameter and its value.
shift /1
shift /1
:: As long as there are more, keep looping. You can even parse more than 10 parameters this way!
if not "%1"=="" goto loop
:: Output what we've found
echo The specified port is %port%
echo Foo is set to %foo%
echo The bar is at %bar%
pause
exit /b
:: If no parameters are given, call ourselves with example parameters.
:none
call %0 --foo bar --port 80 --bar foo
Dressed down version without the demo crap that only displays the port number. I think this is a drop-in replacement for your current code.
#echo off
:loop
if "%1"=="--port" set port=%2
shift /1
shift /1
if not "%1"=="" goto loop
echo %port%
A simpler and obvious approach that get all arguments no matters how many:
#echo off
setlocal EnableDelayedExpansion
set "arg="
for %%a in (%*) do (
if not defined arg (
set "arg=%%a"
) else (
set "!arg:~2!=%%a"
set "arg="
)
)
echo foo = %foo%
echo port = %port%
echo bar = %bar%
If you like short code, the version below do the same task in less lines:
#echo off
set "args= %*"
set "args=%args: --=+%"
set "args=%args: ==%"
set args=%args:+=&set %
echo foo = %foo%
echo port = %port%
echo bar = %bar%
If you want to review how the shorter version works, just remove the #echo off line and execute it.
I would first remove the --port part using sub-string replacement, then get the value by a for /F loop, like this:
set CMD_LINE_ARGS=%*
#rem Remove everything up to `--port` + a space:
set "PORT_ARG_REMOVED=%CMD_LINE_ARGS:*--port =%"
#rem Extract first space- (or tab-)separated item:
for /F "tokens=1" %%A in ("%PORT_ARG_REMOVED%") do set "PORT_NUM=%%A"
echo/%PORT_NUM%
The for /F approach has got the great advantage that it even works when multiple consecutive SPACEs are provided to separate the command line arguments.
If you want the code to allow all standard token separators (which are SPACE, TAB, ,, ;, = and the character with code 0xFF) rather than only the SPACE, you can change the code to this:
set "FLAG="
#rem Use a standard `for` loop to walk through the arguments:
for %%A in (%*) do (
#rem Capture the argument following the `--port` item:
if defined FLAG if not defined PORT_NUM set "PORT_NUM=%%~A"
#rem Use a `FLAG` variable to delay capturing argument by an iteration;
#rem to match `--port` case-sensitively, remove the `/I` option:
if /I "%%~A"=="--port" set "FLAG=#"
)
echo/%PORT_NUM%
Actually I prefer that way, because there is no string manipulation involved, because the list of arguments is parsed in the nature of cmd.

Pass some positional parameters to subcommand in CMD

I have a CMD script that receives some parameters. It is called like this:
C:\myscript -A value -B value -C value -D -E -F value path1 path2 path3
It may receive several option-type parameters (not always the same number, may be none; some with argument, some without) and several path-type parameters (not always the same number, but always at least one). It is not an option to change that, because it is called by a program I can not control.
myscript must extract the path-type parameters only and pass them to one of its subcommands, like this:
#echo off
rem this is C:\myscript
C:\otherscript path1 path2 path3
I can not use numbered positional parameters like %1 %2 or %3 because I do not know in advance how many option-type parameters will there be, nor how many path-type options will there be.
With the additional benefit that the paths can show up anywhere in the command line, not just at the end. Assumption is per your example, each option and its immediately following parameter are being ignored, and anything that shows up without an option preceding is a path you want to use in the call.
#echo off
rem this is C:\myscript too :)
rem do this so it's repeatable, or use setlocal/endlocal
set parmlist=
:loop
set parm=%1
if '%parm:~0,1%'=='-' goto option
set parmlist=%parmlist% %parm%
shift
if '%1'=='' goto done
goto loop
:option
shift
if '%1'=='' goto done
set parm=%1
if not '%parm:~0,1%'=='-' shift
if not '%1'=='' goto loop
:done
c:\otherscript %parmlist%
Tested with this command line:
myscript -a fred -b wilma firstpath -c -d barney secondpath thirdpath
result:
c:\otherscript firstpath secondpath thirdpath
(yeah there's an extra space there but it's easier to read not to mash otherscript and %parmlist% right together)
After grokking a while with this issue, and thanks to some insight from Torqane's answer, I've developed a script that works:
SET SAVEPATH=
:loop
SET var1=%1
SET VAR1NOQUOTE=%~1
SET VAR1FIRST=%VAR1NOQUOTE:~0,1%
if "%VAR1NOQUOTE%"=="" goto continue
rem test known no-argument options
if "%VAR1NOQUOTE%"="-A" goto shift1
if "%VAR1NOQUOTE%"="-B" goto shift1
rem etc... as many as needed
rem test options with arguments
if "%VAR1FIRST%"="-" goto shift2
rem if not an aption, it is a path
SET SAVEPATH=%SAVEPATH% %VAR1%
shift
goto loop
:shift1
shift
goto loop
:shift2
shift
shift
goto loop
:continue
C:\otherscript %SAVEPATH%

Trying to conditionally change a file path in a windows batch script

I have a simple script that launches an XML file path in Notepad++
Unfortunately, the generated file path for some of the files is incorrect and I'm trying to compensate for it in the script (ideal fix is clearly to resolve the path issue but at the moment this is not an option).
Here is what I have, apologies for the code I'm very new to Batch Script...
set /p filePath= Enter file path:
if "%filePath%" == "1" goto xmlMenu
else if "%filePath%" == "file://path/of/file/*/*/*/*/A/*.XML"
set filePath="file://path/of/file/*/*/*/*/B/*.XML"
goto openXML
I would like the filePath variable to inherit the rest of the path from the user input but at the moment its explicitly setting the path with the wildcards. There also seems to be a problem with the way I have stated the condition as it appears to set the path to /B/*.XML regardless of the else if condition.
There are many errors, but as you say, youre new.
First, if the filepath you enter is "1" then you should arrive safely at xmlmenu.
The next line will generate an error because with an else clause,
the If-true instruction must be (parenthesised)
the ( must occur on the same physical line as the if
the sequence ) else ( must all be on the same physical line (which need not be the same as the if)
In any case, the else is redundant. If the condition is not true, the jump will not happen, hence the next statement line will be executed.
if...==... is true if the string on the left is identical to the sring on the right, hence you would need for the entered string to be absolutely identical to file://path/of/file/*/*/*/*/A/*.XML for the if to be true. The action-statment also must be on the same physical line as the if, so this is a second source of syntax errors.
the set is then executed, assigning that famous string (in quotes) to the variable filepath. And then we're off to openxml.
Note that \ is a path separator in Windows; / is a switch-specifier.
Now - if you were to explain what you want to do - perhaps enter a filename which may be somewhere in a directory structure and if it's found then assign what? to the variable filepath. One or two examples would be a good idea.
This should get a usable system going. The numeric check is incomplete, but would be adequate. I've not made it bullet-proof against a user determined to break it...
#ECHO OFF
SETLOCAL
:again
SET "filepath="
set /p filePath="Please enter filepath as yyyymmdd : "
IF NOT DEFINED filepath GOTO :EOF
if "%filePath%" == "1" goto xmlMenu
:: test to see whether entered data is numeric
SET /a "filepath=%filepath%" 2>nul
IF %filepath%==0 echo invalid entry - numerics only, please&GOTO again
IF %filepath% lss 19800100 GOTO badymd
IF %filepath% gtr 20991231 GOTO badymd
SET filepath=\path\prefix\%filepath:~0,4%\%filepath:~4,2%\%filepath:~6,2%
:: This is just to show the filepath constructed
ECHO filepath is "%filepath%"
IF NOT exist "%filepath%\A\*.xml" ECHO "%filepath%\A\*.xml" does NOT exist&GOTO again
IF NOT exist "%filepath%\B\*.xml" ECHO "%filepath%\B\*.xml" does NOT exist&GOTO again
SET "filepath=%filepath%\B\*.xml"
goto openXML
:badymd
ECHO invalid entry - 4 digits FOR year, 2 FOR month an 2 FOR day, please
GOTO again
:xmlmenu
ECHO AT xmlmenu&GOTO :eof
:openxml
ECHO AT openxml with filepath="%filepath%"&GOTO :eof
GOTO :EOF
Obviously, change the path prefix to suit.
For an input of 20140220, the path ....\2014\02\20 would be constructed. Then there's a check for and this +\B\*.xml - I can only presume both must exist to proceed to openxml.

Using parameters in batch files at Windows command line

In Windows, how do you access arguments passed when a batch file is run?
For example, let's say I have a program named hello.bat. When I enter hello -a at a Windows command line, how do I let my program know that -a was passed in as an argument?
As others have already said, parameters passed through the command line can be accessed in batch files with the notation %1 to %9. There are also two other tokens that you can use:
%0 is the executable (batch file) name as specified in the command line.
%* is all parameters specified in the command line -- this is very useful if you want to forward the parameters to another program.
There are also lots of important techniques to be aware of in addition to simply how to access the parameters.
Checking if a parameter was passed
This is done with constructs like IF "%~1"=="", which is true if and only if no arguments were passed at all. Note the tilde character which causes any surrounding quotes to be removed from the value of %1; without a tilde you will get unexpected results if that value includes double quotes, including the possibility of syntax errors.
Handling more than 9 arguments (or just making life easier)
If you need to access more than 9 arguments you have to use the command SHIFT. This command shifts the values of all arguments one place, so that %0 takes the value of %1, %1 takes the value of %2, etc. %9 takes the value of the tenth argument (if one is present), which was not available through any variable before calling SHIFT (enter command SHIFT /? for more options).
SHIFT is also useful when you want to easily process parameters without requiring that they are presented in a specific order. For example, a script may recognize the flags -a and -b in any order. A good way to parse the command line in such cases is
:parse
IF "%~1"=="" GOTO endparse
IF "%~1"=="-a" REM do something
IF "%~1"=="-b" REM do something else
SHIFT
GOTO parse
:endparse
REM ready for action!
This scheme allows you to parse pretty complex command lines without going insane.
Substitution of batch parameters
For parameters that represent file names the shell provides lots of functionality related to working with files that is not accessible in any other way. This functionality is accessed with constructs that begin with %~.
For example, to get the size of the file passed in as an argument use
ECHO %~z1
To get the path of the directory where the batch file was launched from (very useful!) you can use
ECHO %~dp0
You can view the full range of these capabilities by typing CALL /? in the command prompt.
Using parameters in batch files: %0 and %9
Batch files can refer to the words passed in as parameters with the tokens: %0 to %9.
%0 is the program name as it was called.
%1 is the first command line parameter
%2 is the second command line parameter
and so on till %9.
parameters passed in on the commandline must be alphanumeric characters and delimited by spaces. Since %0 is the program name as it was called, in DOS %0 will be empty for AUTOEXEC.BAT if started at boot time.
Example:
Put the following command in a batch file called mybatch.bat:
#echo off
#echo hello %1 %2
pause
Invoking the batch file like this: mybatch john billy would output:
hello john billy
Get more than 9 parameters for a batch file, use: %*
The Percent Star token %* means "the rest of the parameters". You can use a for loop to grab them, as defined here:
http://www.robvanderwoude.com/parameters.php
Notes about delimiters for batch parameters
Some characters in the command line parameters are ignored by batch files, depending on the DOS version, whether they are "escaped" or not, and often depending on their location in the command line:
commas (",") are replaced by spaces, unless they are part of a string in
double quotes
semicolons (";") are replaced by spaces, unless they are part of a string in
double quotes
"=" characters are sometimes replaced by spaces, not if they are part of a
string in double quotes
the first forward slash ("/") is replaced by a space only if it immediately
follows the command, without a leading space
multiple spaces are replaced by a single space, unless they are part of a
string in double quotes
tabs are replaced by a single space
leading spaces before the first command line argument are ignored
#Jon's :parse/:endparse scheme is a great start, and he has my gratitude for the initial pass, but if you think that the Windows torturous batch system would let you off that easy… well, my friend, you are in for a shock. I have spent the whole day with this devilry, and after much painful research and experimentation I finally managed something viable for a real-life utility.
Let us say that we want to implement a utility foobar. It requires an initial command. It has an optional parameter --foo which takes an optional value (which cannot be another parameter, of course); if the value is missing it defaults to default. It also has an optional parameter --bar which takes a required value. Lastly it can take a flag --baz with no value allowed. Oh, and these parameters can come in any order.
In other words, it looks like this:
foobar <command> [--foo [<fooval>]] [--bar <barval>] [--baz]
Complicated? No, that seems pretty typical of real life utilities. (git anyone?)
Without further ado, here is a solution:
#ECHO OFF
SETLOCAL
REM FooBar parameter demo
REM By Garret Wilson
SET CMD=%~1
IF "%CMD%" == "" (
GOTO usage
)
SET FOO=
SET DEFAULT_FOO=default
SET BAR=
SET BAZ=
SHIFT
:args
SET PARAM=%~1
SET ARG=%~2
IF "%PARAM%" == "--foo" (
SHIFT
IF NOT "%ARG%" == "" (
IF NOT "%ARG:~0,2%" == "--" (
SET FOO=%ARG%
SHIFT
) ELSE (
SET FOO=%DEFAULT_FOO%
)
) ELSE (
SET FOO=%DEFAULT_FOO%
)
) ELSE IF "%PARAM%" == "--bar" (
SHIFT
IF NOT "%ARG%" == "" (
SET BAR=%ARG%
SHIFT
) ELSE (
ECHO Missing bar value. 1>&2
ECHO:
GOTO usage
)
) ELSE IF "%PARAM%" == "--baz" (
SHIFT
SET BAZ=true
) ELSE IF "%PARAM%" == "" (
GOTO endargs
) ELSE (
ECHO Unrecognized option %1. 1>&2
ECHO:
GOTO usage
)
GOTO args
:endargs
ECHO Command: %CMD%
IF NOT "%FOO%" == "" (
ECHO Foo: %FOO%
)
IF NOT "%BAR%" == "" (
ECHO Bar: %BAR%
)
IF "%BAZ%" == "true" (
ECHO Baz
)
REM TODO do something with FOO, BAR, and/or BAZ
GOTO :eof
:usage
ECHO FooBar
ECHO Usage: foobar ^<command^> [--foo [^<fooval^>]] [--bar ^<barval^>] [--baz]
EXIT /B 1
Yes, it really is that bad. See my similar post at https://stackoverflow.com/a/50653047/421049, where I provide more analysis of what is going on in the logic, and why I used certain constructs.
Hideous. Most of that I had to learn today. And it hurt.
Batch Files automatically pass the text after the program so long as their are variables to assign them to. They are passed in order they are sent; e.g. %1 will be the first string sent after the program is called, etc.
If you have Hello.bat and the contents are:
#echo off
echo.Hello, %1 thanks for running this batch file (%2)
pause
and you invoke the batch in command via
hello.bat APerson241 %date%
you should receive this message back:
Hello, APerson241 thanks for running this batch file (01/11/2013)
Use variables i.e. the .BAT variables and called %0 to %9

Brackets aren't working on this Windows File?

I am new here and my english is not very good so first, pardon me.
My problem is that this batch keep reading the strings inside the variable "IF EXIST %FSIZE%"; i meant, in case that variable does not exist, the batch keep reading inside the () brackets instead go on the rest of the stings.
If %FSIZE% exist, the batch perform the 2 task i assign: (1.) If size is equal, goto cifiles5x. (2.) If size is NOT equal it uses 7z to extract the file i want to be there.
If %FSIZE% DOES NOT EXIST, the batch keep saying "765952 was not expect at this moment".
I am taking the advices on ss64.com like don't use brackets or quotes when comparing numeric values (%size% EQU 765952) but i don't understand why it does not continue to where the ) ends.
I have also try to link the commands with "&&" so i can erase the brackets but the results are the same.
I know there's 2 spaced patches without quotes; they are unquoted because if i did the size checker won't work.
Thanks for reading this.
EDIT: Batch modified according to suggestions made.
#ECHO OFF
TITLE Log checker
COLOR 0F
SET FSIZE=%ProgramFiles(x86)%\Ces\Log Files Ver\LogVbReg_r2.dll
SET InsDIR=%ProgramFiles(x86)%\Ces\Log Files Ver\
REM I didn't add "" on FSIZE and InsDIR because if i did, quote the variable will
REM result a doubled quoted patch and won't work.
CLS
ECHO ==============================================================================
ECHO = Log checker =
ECHO ==============================================================================
ECHO Checking if exist:
ECHO "%FSIZE%"
IF EXIST "%FSIZE%" (
ECHO It does exist, checking size...
FOR %%A IN ("%FSIZE%") DO SET SIZE=%%~ZA
IF "%SIZE%" EQU "765952"
ECHO Size is right, jumping to CIFILES5
GOTO CIFILES5x
) ELSE (
ECHO Size is not right, extracting the needed file...
7z.exe e "Data_X.*" -o"%InsDIR%" -y "LogVbReg_r2.dll"
GOTO CIFILES5x)
ECHO Does not exist; extracting file...
REN Data_X.* Data_X.exe
Data_X.exe
TIMEOUT 2>NUL
REN DATA_X.* Data_X.dat
:CIFILES5x
ECHO Reach cifiles5x
PAUSE
IF EXIST "%TEMP%\9513.CES" (GOTO OPS) ELSE (GOTO LNKD)
You have space in your file name of %FSIZE%. As result text after space it is treated as command for if. Try using quotes around "%FSIZE%" or do CD to folder first and than check for just file name.
You have several minor errors:
If the file name may have spaces, it MUST be enclosed in quotes in all cases:
IF EXIST "%FSIZE%" (
FOR /F is used to read file CONTENTS. If you want to process file NAME, don't use /F option, but plain FOR command:
FOR %%A IN ("%FSIZE%") DO SET SIZE=%%~ZA
The ELSE part of an IF command must appear in a line alone:
GOTO CIFILES5x
) ELSE (
The same thing for a parentheses:
GOTO CIFILES5x
)
Excepting if the opening parentheses appear in the same line. This is correct:
IF EXIST "%TEMP%\9513.CES" (GOTO OPS) ELSE (GOTO LNKD)
If you compare numbers for equality (EQU, NEQ) then don't matter if they are enclosed in quotes or not. Just in case of GTR, GEQ, LSS and LEQ they must have NOT quotes. However, in your case, this is not a problem...

Resources