Разбор необязательных аргументов файла Windows Bat


84

Мне нужен мой файл bat, чтобы принимать несколько необязательных именованных аргументов.

mycmd.bat man1 man2 -username alice -otheroption

Например, моя команда имеет 2 обязательных параметра и два необязательных параметра (-username) со значением аргумента alice и -otheroption:

Я хотел бы иметь возможность преобразовать эти значения в переменные.

Просто позвоните всем, кто уже решил эту проблему. Эти файлы летучей мыши - это боль.


12
Есть ли причина, по которой вы должны делать это в файле BAT, а не в VBScript? Может быть способ сделать это в BAT-файле, но, придерживаясь подхода BAT-файла, вы «входите в мир боли, сынок». :-)
Алек Дэвис

Еще лучше использовать PowerShell. Он имеет очень продвинутое и встроенное управление параметрами.
Bill_Stewart

2
@AlekDavis, ты, наверное, прав, но я очень боюсь VBScript. Если бы я когда-нибудь обнаружил, что использую VBScript, я бы уже был в мире боли. Я с удовольствием напишу себе командный файл, чтобы что-то автоматизировать, а затем, возможно, если я хочу сделать его более полезным для людей, я добавлю несколько аргументов. Часто некоторые из них необязательны. Ни разу с тех пор, как я покинул мир банковского дела, я не думал или не предлагал, чтобы «вам для этого понадобится VBScript».
icc97

@chickeninabiscuit: Ссылка больше не работает. Версия машины Wayback есть. Я не знаю , если я изменить свой комментарий , если он изменится, чтобы выглядеть , как я сделал работу или нет я просто хочу , чтобы вставить его здесь вместо: http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Брент Риттенхаус

Ответы:


113

Хотя я склонен согласиться с комментарием @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. Он не сдвигается два раза. Разве его нельзя заменить на SHIFT & SHIFT?
dbenham

2
Хорошо - я понимаю, почему код обычно работает - SHIFT в цикле в конечном итоге перемещает параметры на arg 1, и никакого вреда (обычно) не происходит. Но первоначальный SHIFT / 2 определенно является ошибкой, которая испортит результаты, если значение arg 1 совпадет с одним из имен опций. Попробуйте бежать mycmd.bat -username anyvalue -username myName -otheroption othervalue. Результатом должно быть user = myName, other = othervalue; но код дает user = -username, other = othervalue. Ошибка исправляется заменой SHIFT /2на SHIFT & SHIFT.
dbenham

2
@Christian - ewall неверно о ;- его нельзя использовать вместо &.
dbenham

1
Это отличное начало, но я вижу 3 проблемы. Первое: а) После строки 10 %1и %2были «Использованы». b) Одиночное значение SHIFTв строке 11 перемещает значение %2на 1 позицию вниз и затем становится доступным (снова) как %1, а значение «Неиспользовано» %3доступно как %2. c) В IFстроке 13 отображается это уже «Используемое» значение, из %2которого сейчас находится %1. d) Если «имя пользователя» из строки 10 выглядит так "-otheroption", оно будет использовано снова и otherбудет установлено на новое значение в %2, которое, вероятно, было именем следующей опции. --->
Кевин Феган

2
Вторая проблема: в вопросе OP за символом -otheroptionне следовало значение, поэтому оно, вероятно, подразумевалось как -Flagпараметр типа и не должно использовать второй аргумент. 3-й: если аргумент %1не обрабатывается внутренними IFоператорами, он игнорируется. Код, вероятно, должен отображать сообщение о том, что это недопустимый вариант. Это может быть сделано путем добавления требуемого SHIFTи GOTO :loopзаявление во внутренние IFзаявления, либо путем добавления ELSEзаявления.
Кевин Феган

75

Выбранный ответ работает, но его можно улучшить.

  • Параметры, вероятно, следует инициализировать значениями по умолчанию.
  • Было бы неплохо сохранить% 0, а также необходимые аргументы% 1 и% 2.
  • Становится больно иметь блок IF для каждого варианта, особенно когда количество вариантов растет.
  • Было бы неплохо иметь простой и лаконичный способ быстрого определения всех параметров и значений по умолчанию в одном месте.
  • Было бы хорошо поддерживать автономные параметры, которые служат в качестве флагов (без значения после параметра).
  • Мы не знаем, заключен ли аргумент в кавычки. Мы также не знаем, было ли передано значение arg с использованием экранированных символов. Лучше получить доступ к аргументу, используя% ~ 1, и заключить присвоение в кавычки. Тогда пакет может полагаться на отсутствие заключительных кавычек, но специальные символы, как правило, безопасны без экранирования. (Это не пуленепробиваемое устройство, но оно подходит для большинства ситуаций)

Мое решение основано на создании переменной OPTIONS, которая определяет все параметры и их значения по умолчанию. OPTIONS также используется для проверки допустимости предоставленной опции. Огромный объем кода сохраняется, просто сохраняя значения параметров в переменных, названных так же, как параметр. Объем кода постоянен независимо от того, сколько опций определено; только определение OPTIONS должно измениться.

РЕДАКТИРОВАТЬ - Кроме того, код цикла: должен измениться, если количество обязательных позиционных аргументов изменится. Например, часто все аргументы имеют имена, и в этом случае вы хотите проанализировать аргументы, начинающиеся с позиции 1, а не 3. Итак, в цикле: все 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!


Это решение предоставляет аргументы стиля Unix в пакете Windows. Это не является нормой для Windows - пакетная программа обычно имеет параметры, предшествующие требуемым аргументам, а параметры имеют префикс /.

Методы, используемые в этом решении, легко адаптируются к стилю опций Windows.

  • Цикл синтаксического анализа всегда ищет параметр в %1и продолжается до тех пор, пока аргумент 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!
Роберт Смит

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
Я не думаю, что это работает, если пользователь не знает, как использовать "" вместо аргумента. В противном случае, как я могу установить имя БД, но оставить сервер БД пустым?
Sean Long

Как еще, по вашему мнению, это достигается другим ответом? @SeanLong
sehe

@SeanLong Правильно, поэтому пользователь должен подчиняться приказу. Итак, на первое место следует поставить самые необходимые аргументы.
Мартин Браун

Для меня это был определенно самый простой вариант. Спасибо за публикацию :)
Стив Бауман

3

Создание динамических переменных

введите описание изображения здесь

Плюсы

  • Работает для> 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чтобы сделать его доступным и для других сценариев.
Саймон Штрейхер

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

Escape char определяется как "_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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.