WindowsBatファイルのオプションの引数の解析


84

複数のオプションの名前付き引数を受け入れるには、batファイルが必要です。

mycmd.bat man1 man2 -username alice -otheroption

たとえば、私のコマンドには2つの必須パラメーターと、引数値がaliceである2つのオプションパラメーター(-username)、および-otheroptionがあります。

これらの値を変数に取り込めるようにしたいと思います。

すでにこれを解決した人に電話をかけるだけです。これらのバットファイルは苦痛です。


12
VBScriptで言うのではなく、BATファイルでこれを行う必要がある理由は何ですか?BATファイルでこれを行う方法があるかもしれませんが、BATファイルのアプローチに固執すると、「あなたは苦痛の世界に入っています、息子」。:-)
Alek Davis

さらに良いのは、PowerShellを使用することです。非常に高度な組み込みのパラメータ管理があります。
Bill_Stewart 2016年

1
@AlekDavis、あなたはおそらく正しいですが、私は非常にVBScript恐怖症です。VBScriptを使用しなければならないことに気付いた場合、私はすでに苦痛の世界にいると思います。何かを自動化するためのバッチファイルを自分で喜んで作成します。それを人々にとってより便利なものにしたい場合は、いくつかの引数を追加します。多くの場合、これらの一部はオプションです。私が銀行の世界を去って以来、私は考えたことも、誰かが「そのためのVBScriptが必要になる」と提案したこともありません。
icc97 2016

@chickeninabiscuit:リンクが機能しなくなりました。ウェイバックマシンバージョンはありません。コメントを編集して、作業を行ったように変更するかどうかはわかりませんが、代わりにここに貼り付けますhttp://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323
ブレントリッテンハウス

回答:


112

@AlekDavisのコメントに同意する傾向がありますが、それでもNTシェルでこれを行うにはいくつかの方法があります。

SHIFTコマンドとIF条件分岐を利用するアプローチは、次のようなものです...

@ECHO OFF

SET man1=%1
SET man2=%2
SHIFT & SHIFT

:loop
IF NOT "%1"=="" (
    IF "%1"=="-username" (
        SET user=%2
        SHIFT
    )
    IF "%1"=="-otheroption" (
        SET other=%2
        SHIFT
    )
    SHIFT
    GOTO :loop
)

ECHO Man1 = %man1%
ECHO Man2 = %man2%
ECHO Username = %user%
ECHO Other option = %other%

REM ...do stuff here...

:theend

1
SHIFT /2引数2からシフトします。2回シフトしません。に置き換えるべきではありませんSHIFT & SHIFTか?
dbenham 2011年

2
OK-コードが通常機能する理由がわかります-ループ内のSHIFTは、最終的にオプションを引数1に移動し、害はありません(通常)。ただし、最初のSHIFT / 2は間違いなくバグであり、arg 1の値がオプション名の1つと一致すると、結果が破損します。実行してみてくださいmycmd.bat -username anyvalue -username myName -otheroption othervalue。結果はuser = myName、other = othervalueになります。しかし、コードはuser = -username、other = othervalueを与えます。に置き換えることSHIFT /2でバグが修正されましたSHIFT & SHIFT
dbenham 2011年

2
@ Christian-ewallは正しくあり;ません-の代わりに使用することはできません&
dbenham 2012年

1
これは素晴らしいスタートですが、3つの問題があります。まず:10行目の後に)、%1および%2「使用」されています。b)SHIFT11行目のシングルは、値を%21つ下に移動してから、(再び)として使用可能%1になり、の「未使用」値は%3として使用可能になります%2。c)IF行13には、このすでに「使用済み」の値が表示され、%2そこから現在はになってい%1ます。d)10行目の「user-name」がのよう"-otheroption"に見える場合、それは再び使用otherされ、%2おそらく次のオプションの名前であるの新しい値に設定されます。--->
ケビンフェガン2016

2
2番目の問題:OP質問では、の-otheroption後に値が続かなかったため、おそらく-Flagタイプオプションとして意図されており、2番目の引数を消費するべきではありません。3番目:引数%1が内部IFステートメントによって処理されない場合、それは黙って無視されます。コードはおそらく、それが有効なオプションではなかったことを示すメッセージを表示するはずです。これは、requiredステートメントSHIFTGOTO :loopステートメントを内部IFステートメントに追加するか、ステートメントを追加することによって実行できますELSE
ケビンフェガン2016

74

選択した回答は機能しますが、多少の改善が必要になる可能性があります。

  • オプションはおそらくデフォルト値に初期化する必要があります。
  • %0と、必要な引数%1および%2を保持しておくと便利です。
  • 特にオプションの数が増えるにつれて、すべてのオプションにIFブロックを設定するのは面倒になります。
  • すべてのオプションとデフォルトを1か所ですばやく定義するためのシンプルで簡潔な方法があると便利です。
  • フラグとして機能するスタンドアロンオプションをサポートするとよいでしょう(オプションの後に値はありません)。
  • 引数が引用符で囲まれているかどうかはわかりません。また、引数値がエスケープ文字を使用して渡されたかどうかもわかりません。%〜1を使用して引数にアクセスし、割り当てを引用符で囲むことをお勧めします。その場合、バッチは引用符で囲まれていないことに依存できますが、特殊文字はエスケープせずに一般的に安全です。(これは防弾ではありませんが、ほとんどの状況を処理します)

私のソリューションは、すべてのオプションとそのデフォルトを定義するOPTIONS変数の作成に依存しています。OPTIONSは、指定されたオプションが有効かどうかをテストするためにも使用されます。オプションと同じ名前の変数にオプション値を格納するだけで、膨大な量のコードが節約されます。コードの量は、定義されているオプションの数に関係なく一定です。OPTIONS定義のみを変更する必要があります。

編集 -また、必須の位置引数の数が変更された場合は、:loopコードを変更する必要があります。たとえば、多くの場合、すべての引数に名前が付けられます。この場合、3ではなく位置1から始まる引数を解析する必要があります。したがって、:loop内では、3つすべてが1になり、4つが2になります。

@echo off
setlocal enableDelayedExpansion

:: Define the option names along with default values, using a <space>
:: delimiter between options. I'm using some generic option names, but 
:: normally each option would have a meaningful name.
::
:: Each option has the format -name:[default]
::
:: The option names are NOT case sensitive.
::
:: Options that have a default value expect the subsequent command line
:: argument to contain the value. If the option is not provided then the
:: option is set to the default. If the default contains spaces, contains
:: special characters, or starts with a colon, then it should be enclosed
:: within double quotes. The default can be undefined by specifying the
:: default as empty quotes "".
:: NOTE - defaults cannot contain * or ? with this solution.
::
:: Options that are specified without any default value are simply flags
:: that are either defined or undefined. All flags start out undefined by
:: default and become defined if the option is supplied.
::
:: The order of the definitions is not important.
::
set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

:: Set the default option values
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:loop
:: Validate and store the options, one at a time, using a loop.
:: Options start at arg 3 in this example. Each SHIFT is done starting at
:: the first option so required args are preserved.
::
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
    rem No substitution was made so this is an invalid option.
    rem Error handling goes here.
    rem I will simply echo an error message.
    echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
    rem Set the flag option using the option name.
    rem The value doesn't matter, it just needs to be defined.
    set "%~3=1"
  ) else (
    rem Set the option value using the option as the name.
    rem and the next arg as the value
    set "%~3=%~4"
    shift /3
  )
  shift /3
  goto :loop
)

:: Now all supplied options are stored in variables whose names are the
:: option names. Missing options have the default value, or are undefined if
:: there is no default.
:: The required args are still available in %1 and %2 (and %0 is also preserved)
:: For this example I will simply echo all the option values,
:: assuming any variable starting with - is an option.
::
set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!

本当にそれほど多くのコードはありません。上記のコードのほとんどはコメントです。これは、コメントなしのまったく同じコードです。

@echo off
setlocal enableDelayedExpansion

set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
:loop
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
      echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
      set "%~3=1"
  ) else (
      set "%~3=%~4"
      shift /3
  )
  shift /3
  goto :loop
)
set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!


このソリューションは、Windowsバッチ内でUnixスタイルの引数を提供します。これはWindowsの標準ではありません。バッチには通常、必要な引数の前にオプションがあり、オプションの前には/。が付いています。

このソリューションで使用される手法は、Windowsスタイルのオプションに簡単に適合させることができます。

  • 解析ループは常にでオプションを探し、%1引数1がで始まらないまで続きます/
  • 名前が。で始まる場合は、SET割り当てを引用符で囲む必要があることに注意してください/
    SET /VAR=VALUE失敗し
    SET "/VAR=VALUE"ます。とにかく、私はすでに私のソリューションでこれを行っています。
  • 標準のWindowsスタイルでは、最初に必要な引数値が/。で始まる可能性がありません。この制限は//、オプション解析ループを終了するためのシグナルとして機能する暗黙的に定義されたオプションを使用することで解消できます。//「オプション」には何も保存されません。


更新2015-12-28:!オプション値のサポート

上記のコードでは、遅延展開が有効になっているときに各引数が展開されます。これは、!おそらく削除されるか、またはそのようなもの!var!が展開されることを意味します。さらに、存在する^場合!は剥がすこともできます。コメントされていないコードに対する次の小さな変更により、!^がオプション値に保持されるなどの制限がなくなります。

@echo off
setlocal enableDelayedExpansion

set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:"

for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
:loop
if not "%~3"=="" (
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
      echo Error: Invalid option %~3
  ) else if "!test:~0,1!"==" " (
      set "%~3=1"
  ) else (
      setlocal disableDelayedExpansion
      set "val=%~4"
      call :escapeVal
      setlocal enableDelayedExpansion
      for /f delims^=^ eol^= %%A in ("!val!") do endlocal&endlocal&set "%~3=%%A" !
      shift /3
  )
  shift /3
  goto :loop
)
goto :endArgs
:escapeVal
set "val=%val:^=^^%"
set "val=%val:!=^!%"
exit /b
:endArgs

set -

:: To get the value of a single parameter, just remember to include the `-`
echo The value of -username is: !-username!

非常にエレガントな解決策をありがとう、私は答えとして受け入れられたものよりもこれが好きです。見逃したのは、バッチスクリプトで名前付きパラメーターを使用する方法を言うことだけです。たとえば、echo%-username%
Roboblob

1
非常にエレガントなソリューション。これを使用することを検討する場合は、提供されているコードが引数3で解析を開始することに注意してください。これは一般的な場合に必要なことではありません。これを修正するには、「3」を「1」に置き換えます。@dbenham元の投稿でこれをもっと明確にできればいいのですが、最初に混乱したのは私だけではないでしょう。
ocroquette 2016

@ ocroquette-良い点。特定の質問に答えなければならなかったので、私にはあまり選択肢がありませんでした。ただし、必須の位置引数の数が変更された場合に備えて、コードを変更する必要があります。私はそれを明確にするために私の答えを編集しようとします。
dbenham 2016

これ大好き。解決策をありがとう、それはいくつかのIDEスクリプトのために2019年でもかなりきれいに動作します!
ロバートスミス

18

オプションの引数を使用したいが、名前付き引数を使用したくない場合は、このアプローチが役に立ちました。これは従うのがはるかに簡単なコードだと思います。

REM Get argument values.  If not specified, use default values.
IF "%1"=="" ( SET "DatabaseServer=localhost" ) ELSE ( SET "DatabaseServer=%1" )
IF "%2"=="" ( SET "DatabaseName=MyDatabase" ) ELSE ( SET "DatabaseName=%2" )

REM Do work
ECHO Database Server = %DatabaseServer%
ECHO Database Name   = %DatabaseName%

IF "%1"==""引数に引用符自体が含まれている場合は失敗します。代わりに、._ = []#などの他の非特殊記号を使用してください
Fr0sT 2014年

2
ユーザーが引数の代わりに「」を使用することを知らない限り、これは機能しないと思います。それ以外の場合、DB名を設定し、DBサーバーを空白のままにするにはどうすればよいですか?
ショーンロング

他にどのように別の答えがそれを達成すると思いますか?@SeanLong
sehe

@SeanLongそうです、ユーザーは注文に従う必要があります。したがって、最初に最も必要な引数を配置する必要があります。
マーティンブラウン

これは間違いなく私にとって最も簡単なオプションでした。投稿していただきありがとうございます:)
Steve Bauman

2

動的変数の作成

ここに画像の説明を入力してください

長所

  • 9を超える引数で機能します
  • 続けて%1%2...%*タクトで
  • /arg-argスタイルの両方で機能します
  • 議論についての予備知識はありません
  • 実装はメインルーチンとは別です

短所

  • 古い引数は連続した実行に漏れる可能性があるためsetlocal、ローカルスコープに使用するか、付随する:CLEAR-ARGSルーチンを作成してください。
  • エイリアスのサポートはまだありません(のよう--force-f
  • 空の""引数のサポートはありません

使用法

次の引数が.bat変数にどのように関連するかの例を次に示します。

>> testargs.bat /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"

echo %*        | /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"
echo %ARG_FOO% | c:\
echo %ARG_A%   |
echo %ARG_B%   | 3
echo %ARG_C%   | 1
echo %ARG_D%   | 1

実装

@echo off
setlocal

CALL :ARG-PARSER %*

::Print examples
echo: ALL: %*
echo: FOO: %ARG_FOO%
echo: A:   %ARG_A%
echo: B:   %ARG_B%
echo: C:   %ARG_C%
echo: D:   %ARG_C%


::*********************************************************
:: Parse commandline arguments into sane variables
:: See the following scenario as usage example:
:: >> thisfile.bat /a /b "c:\" /c /foo 5
:: >> CALL :ARG-PARSER %*
:: ARG_a=1
:: ARG_b=c:\
:: ARG_c=1
:: ARG_foo=5
::*********************************************************
:ARG-PARSER
    ::Loop until two consecutive empty args
    :loopargs
        IF "%~1%~2" EQU "" GOTO :EOF

        set "arg1=%~1" 
        set "arg2=%~2"
        shift

        ::Allow either / or -
        set "tst1=%arg1:-=/%"
        if "%arg1%" NEQ "" (
            set "tst1=%tst1:~0,1%"
        ) ELSE (
            set "tst1="
        )

        set "tst2=%arg2:-=/%"
        if "%arg2%" NEQ "" (
            set "tst2=%tst2:~0,1%"
        ) ELSE (
            set "tst2="
        )


        ::Capture assignments (eg. /foo bar)
        IF "%tst1%" EQU "/"  IF "%tst2%" NEQ "/" IF "%tst2%" NEQ "" (
            set "ARG_%arg1:~1%=%arg2%"
            GOTO loopargs
        )

        ::Capture flags (eg. /foo)
        IF "%tst1%" EQU "/" (
            set "ARG_%arg1:~1%=1"
            GOTO loopargs
        )
    goto loopargs
GOTO :EOF


:ARG-PARSERルーチンの内容を外部の.batファイルに移動して、arg-parser.bat他のスクリプトでも使用できるようにすることもできます。
Simon Streicher

1

バッチファイルで短い(-h)、長い(--help)、およびオプション以外の引数を処理するプログラムを作成した後。この手法には次のものが含まれます。

  • 非オプション引数の後にオプション引数が続きます。

  • '--help'のような引数を持たないオプションのシフト演算子。

  • 引数を必要とするオプションの2つのタイムシフト演算子。

  • すべてのコマンドライン引数を処理するためにラベルをループします。

  • スクリプトを終了し、「-help」のような追加のアクションを必要としないオプションの処理を停止します。

  • ユーザーガイドのためのヘルプ機能を作成

これが私のコードです。

set BOARD=
set WORKSPACE=
set CFLAGS=
set LIB_INSTALL=true
set PREFIX=lib
set PROGRAM=install_boards

:initial
 set result=false
 if "%1" == "-h" set result=true
 if "%1" == "--help" set result=true
 if "%result%" == "true" (
 goto :usage
 )
 if "%1" == "-b" set result=true
 if "%1" == "--board" set result=true
 if "%result%" == "true" (
 goto :board_list
 )
 if "%1" == "-n" set result=true
 if "%1" == "--no-lib" set result=true
 if "%result%" == "true" (
 set LIB_INSTALL=false
 shift & goto :initial
 )
 if "%1" == "-c" set result=true
 if "%1" == "--cflag" set result=true
 if "%result%" == "true" (
 set CFLAGS=%2
 if not defined CFLAGS (
 echo %PROGRAM%: option requires an argument -- 'c'
 goto :try_usage
 )
 shift & shift & goto :initial
 )
 if "%1" == "-p" set result=true
 if "%1" == "--prefix" set result=true
 if "%result%" == "true" (
 set PREFIX=%2
 if not defined PREFIX (
 echo %PROGRAM%: option requires an argument -- 'p'
 goto :try_usage
 )
 shift & shift & goto :initial
 )

:: handle non-option arguments
set BOARD=%1
set WORKSPACE=%2

goto :eof


:: Help section

:usage
echo Usage: %PROGRAM% [OPTIONS]... BOARD... WORKSPACE
echo Install BOARD to WORKSPACE location.
echo WORKSPACE directory doesn't already exist!
echo.
echo Mandatory arguments to long options are mandatory for short options too.
echo   -h, --help                   display this help and exit
echo   -b, --boards                 inquire about available CS3 boards
echo   -c, --cflag=CFLAGS           making the CS3 BOARD libraries for CFLAGS
echo   -p. --prefix=PREFIX          install CS3 BOARD libraries in PREFIX
echo                                [lib]
echo   -n, --no-lib                 don't install CS3 BOARD libraries by default
goto :eof

:try_usage
echo Try '%PROGRAM% --help' for more information
goto :eof

1

これが引数パーサーです。任意の文字列引数(変更されないまま)またはエスケープされたオプション(単一またはオプション/値のペア)を混在させることができます。それをテストするには、最後の2つのステートメントのコメントを外し、次のように実行します。

getargs anystr1 anystr2 /test$1 /test$2=123 /test$3 str anystr3

エスケープ文字はとして定義され"_SEP_=/"、必要に応じて再定義します。

@echo off

REM Command line argument parser. Format (both "=" and "space" separators are supported):
REM   anystring1 anystring2 /param1 /param2=value2 /param3 value3 [...] anystring3 anystring4
REM Returns enviroment variables as:
REM   param1=1
REM   param2=value2
REM   param3=value3
REM Leading and traling strings are preserved as %1, %2, %3 ... %9 parameters
REM but maximum total number of strings is 9 and max number of leading strings is 8
REM Number of parameters is not limited!

set _CNT_=1
set _SEP_=/

:PARSE

if %_CNT_%==1 set _PARAM1_=%1 & set _PARAM2_=%2
if %_CNT_%==2 set _PARAM1_=%2 & set _PARAM2_=%3
if %_CNT_%==3 set _PARAM1_=%3 & set _PARAM2_=%4
if %_CNT_%==4 set _PARAM1_=%4 & set _PARAM2_=%5
if %_CNT_%==5 set _PARAM1_=%5 & set _PARAM2_=%6
if %_CNT_%==6 set _PARAM1_=%6 & set _PARAM2_=%7
if %_CNT_%==7 set _PARAM1_=%7 & set _PARAM2_=%8
if %_CNT_%==8 set _PARAM1_=%8 & set _PARAM2_=%9

if "%_PARAM2_%"=="" set _PARAM2_=1

if "%_PARAM1_:~0,1%"=="%_SEP_%" (
  if "%_PARAM2_:~0,1%"=="%_SEP_%" (
    set %_PARAM1_:~1,-1%=1
    shift /%_CNT_%
  ) else (
    set %_PARAM1_:~1,-1%=%_PARAM2_%
    shift /%_CNT_%
    shift /%_CNT_%
  )
) else (
  set /a _CNT_+=1
)

if /i %_CNT_% LSS 9 goto :PARSE

set _PARAM1_=
set _PARAM2_=
set _CNT_=

rem getargs anystr1 anystr2 /test$1 /test$2=123 /test$3 str anystr3
rem set | find "test$"
rem echo %1 %2 %3 %4 %5 %6 %7 %8 %9

:EXIT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.