Windows Bat 파일 선택적 인수 구문 분석


84

여러 선택적 명명 된 인수를 허용하려면 내 bat 파일이 필요합니다.

mycmd.bat man1 man2 -username alice -otheroption

예를 들어, 내 명령에는 2 개의 필수 매개 변수와 인수 값이 alice 인 2 개의 선택적 매개 변수 (-username) 및 -otheroption이 있습니다.

이 값을 변수로 추출 할 수 있기를 바랍니다.

이미이 문제를 해결 한 사람에게 전화를 걸면됩니다. 이 박쥐 파일은 고통 스럽습니다.


12
VBScript에서 말하지 않고 BAT 파일에서이 작업을 수행해야하는 이유가 있습니까? BAT 파일에이 작업을 수행하는 방법이있을 수 있지만 BAT 파일 접근 방식을 고수하면 "당신은 고통의 세계로 들어가고 있습니다." :-)
Alek Davis

더 나은 방법은 PowerShell을 사용하는 것입니다. 매우 진보 된 내장 매개 변수 관리 기능이 있습니다.
Bill_Stewart

1
@AlekDavis, 당신 말이 맞을 것입니다.하지만 저는 VBScript를 싫어합니다. 내가 VBScript를 사용해야한다는 것을 알게된다면 나는 이미 고통의 세계에있을 것이라고 생각합니다. 나는 행복하게 무언가를 자동화하기 위해 배치 파일을 작성하고, 아마도 사람들에게 더 유용하게 만들고 싶다면 몇 가지 인수를 추가 할 것입니다. 종종 이들 중 일부는 선택 사항입니다. 은행계를 떠난 이후로 어떤 시점에서도 "당신은 VBScript를 원할 것"이라고 생각하거나 누군가 제안한 적이 없습니다.
icc97

@chickeninabiscuit : 링크가 더 이상 작동하지 않습니다. 웨이 백 머신 버전은 그렇습니다. 내가 작업 한 것처럼 보이도록 댓글을 수정하는지 여부를 모르겠습니다 http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323. 대신 여기에 붙여 넣겠습니다 ..
Brent Rittenhouse

답변:


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 /2arg 2에서 시작하여 이동합니다. 두 번 이동하지 않습니다. 로 바꿔야하지 SHIFT & SHIFT않나요?
dbenham 2011

2
OK-코드가 일반적으로 작동하는 이유를 봅니다. 루프의 SHIFT는 결국 옵션을 arg 1로 이동하고 해를 끼치 지 않습니다 (일반적으로). 그러나 초기 SHIFT / 2는 인수 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

1
이것은 좋은 시작이지만 세 가지 문제가 있습니다. 첫째 : 라인 10 후), %1그리고 %2"사용"되고있다. b) SHIFT11 행 의 싱글 은 %21 위치 아래로 이동 한 다음 (다시)로 사용할 수 %1있으며의 "사용되지 않음"값은 %3로 사용할 수 있습니다 %2. c) IF인라인 13 %2은 현재에있는 이미 "사용됨"값을 확인 %1합니다. d) 10 행의 "user-name"이와 같이 보이면 "-otheroption"다시 사용 되며 다음 옵션의 이름 인 other에서 새 값으로 설정됩니다 %2. --->
Kevin Fegan

2
두 번째 문제 : OP 질문에서 -otheroption뒤에 값이 나오지 않았기 때문에 아마도 -Flag유형 옵션을 의미 하고 두 번째 인수를 사용해서는 안됩니다. 셋째 : 인수 %1가 내부 IF문에 의해 처리되지 않으면 자동으로 무시됩니다. 코드는 유효한 옵션이 아니라는 메시지를 표시해야합니다. 이는 필수 SHIFTGOTO :loop문을 내부 IF문에 추가하거나 문을 추가하여 수행 할 수 있습니다 ELSE.
Kevin Fegan

74

선택한 답변이 작동하지만 약간의 개선이 필요할 수 있습니다.

  • 옵션은 아마도 기본값으로 초기화되어야합니다.
  • % 0과 필수 인수 % 1 및 % 2를 보존하는 것이 좋습니다.
  • 특히 옵션 수가 증가함에 따라 모든 옵션에 대해 IF 블록을 사용하는 것은 고통스러워집니다.
  • 모든 옵션과 기본값을 한곳에서 신속하게 정의 할 수있는 간단하고 간결한 방법이 있으면 좋을 것입니다.
  • 플래그 역할을하는 독립형 옵션을 지원하는 것이 좋습니다 (옵션 다음에 값 없음).
  • 인수가 따옴표로 묶여 있는지 알 수 없습니다. 이스케이프 문자를 사용하여 인수 값이 전달되었는지도 알 수 없습니다. % ~ 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 스타일의 옵션에 쉽게 적용됩니다.

  • 구문 분석 루프는 항상에서 옵션을 찾고 %1arg 1이 다음으로 시작하지 않을 때까지 계속됩니다./
  • 이름이로 시작하는 경우 SET 할당 따옴표로 묶어야합니다 /. 작동
    SET /VAR=VALUE하지 않습니다
    SET "/VAR=VALUE". 어쨌든 내 솔루션에서 이미 이것을하고 있습니다.
  • 표준 Windows 스타일은로 시작하는 첫 번째 필수 인수 값의 가능성을 배제합니다 /. 이 제한은 //옵션 구문 분석 루프를 종료하는 신호 역할 을하는 암시 적으로 정의 된 옵션을 사용하여 제거 할 수 있습니다 . //"옵션" 에 대해서는 아무것도 저장되지 않습니다 .


2015-12-28 업데이트 :! in 옵션 값 지원

위의 코드에서 각 인수는 확장되고 지연된 확장이 활성화되어 있습니다. 즉, !대부분 제거되거나 다른 항목 !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

@ocroquette-좋은 지적입니다. 특정 질문에 답해야했기 때문에 선택의 여지가 많지 않았습니다. 그러나 필수 위치 인수의 수가 변경되면 코드를 수정해야합니다. 내 대답을 명확하게 수정하려고 시도 할 것입니다.
dbenham

나는 이것을 사랑한다. 솔루션에 감사드립니다. 2019 년에도 여전히 일부 IDE 스크립팅에 대해 깔끔하게 작동합니다!
Robert Smith 19

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

2
사용자가 인수 대신 ""를 사용하는 것을 알지 못하면 이것이 작동하지 않는다고 생각합니다. 그렇지 않으면 어떻게 DB 이름을 설정하고 DB 서버를 비워 둘 수 있습니까?
Sean Long

또 다른 답변이 그것을 달성하는 것을 어떻게 볼 수 있습니까? @SeanLong
sehe

@SeanLong 맞습니다. 따라서 사용자는 명령을 따라야합니다. 따라서 가장 필요한 인수를 먼저 배치해야합니다.
Martin Braun

이것은 확실히 나에게 가장 쉬운 옵션이었습니다. 게시 해 주셔서 감사합니다 :)
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'와 같은 인수가없는 옵션에 대한 시프트 연산자.

  • 인수가 필요한 옵션에 대한 두 개의 시간 이동 연산자.

  • 모든 명령 줄 인수를 처리하기 위해 레이블을 반복합니다.

  • '--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.