Windows Bat dosyası isteğe bağlı bağımsız değişken ayrıştırma


84

Birden çok isteğe bağlı adlandırılmış argümanı kabul etmek için yarasa dosyama ihtiyacım var.

mycmd.bat man1 man2 -username alice -otheroption

Örneğin, komutumun 2 zorunlu parametresi ve alice argüman değerine sahip iki isteğe bağlı parametresi (-username) ve -otheroption:

Bu değerleri değişkenlere ayırabilmek istiyorum.

Bunu zaten çözmüş olan birine telefon açıyorum. Adamım bu yarasa dosyaları tam bir acı.


12
Bunu bir BAT dosyasında yapmanız ve VBScript'te söylememeniz için herhangi bir neden var mı? BAT dosyasında bunu yapmanın bir yolu olabilir, ancak BAT dosyası yaklaşımına bağlı kalarak, "bir acı dünyasına giriyorsun oğlum". :-)
Alek Davis

Daha da iyisi, PowerShell kullanmaktır. Çok gelişmiş ve yerleşik parametre yönetimine sahiptir.
Bill_Stewart

2
@AlekDavis, muhtemelen haklısın, ama ben oldukça VBScript fobim. Kendimi VBScript kullanmak zorunda bulursam, sanırım zaten bir acı dünyasında olacağım. Bir şeyi otomatikleştirmek için mutlu bir şekilde kendime bir toplu iş dosyası yazacağım, o zaman belki de onu insanlar için daha yararlı hale getirmek istersem, bazı argümanlar eklerim. Genellikle bunlardan bazıları isteğe bağlıdır. Bankacılık dünyasından ayrıldığımdan beri hiçbir noktada düşünmedim ya da birisi "bunun için biraz VBScript isteyeceksiniz" diye önerdi.
icc97

@chickeninabiscuit: Bağlantı artık çalışmıyor. Wayback makine versiyonu var. Ben çalışma yaptım ya da değil ben sadece yerine buraya yapıştırmak için gidiyorum gibi görünmesini değişecektir eğer Yorumunuzu düzenlerseniz bilmiyorum: http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323.
Brent Rittenhouse

Yanıtlar:


113

@ AlekDavis'in yorumuna katılma eğiliminde olsam da bunu NT kabuğunda yapmanın birkaç yolu var.

SHIFT komutundan ve IF koşullu dallanmadan yararlanacağım yaklaşım, bunun gibi bir şey ...

@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'den başlayarak kayar. İki kez kaymaz. İle değiştirilmesi gerekmiyor SHIFT & SHIFTmu?
dbenham

2
Tamam - Kodun genellikle neden çalıştığını anlıyorum - döngüdeki SHIFT sonunda seçenekleri arg 1'e taşır ve hiçbir zarar verilmez (genellikle). Ancak ilk SHIFT / 2, eğer arg 1 değeri seçenek adlarından biriyle eşleşirse sonuçları bozacak bir hatadır. Koşmayı dene mycmd.bat -username anyvalue -username myName -otheroption othervalue. Sonuç user = myName, other = othervalue olmalıdır; ancak kod user = -username, other = othervalue verir. Hata, SHIFT /2ile değiştirilerek düzeltildi SHIFT & SHIFT.
dbenham

2
@Christian - ewall hakkında yanlış ;- yerine kullanılamaz &.
dbenham

1
Bu harika bir başlangıç, ancak 3 sorun görüyorum. İlk: a) 10. satırdan sonra %1ve %2"Kullanılmış". b) SHIFT11. satırdaki tekli , %21 konumun değerini düşürür ve daha sonra (tekrar) olarak kullanılabilir %1ve "Kullanılmayan" değeri %3olarak kullanılabilir %2. c) IFSatır 13 %2, şimdi içinde bulunan bu zaten "Kullanılmış" değeri görür %1. d) 10. satırdaki "kullanıcı adı" gibi görünüyorsa "-otheroption", tekrar kullanılacak ve otheriçindeki yeni değere ayarlanacaktır %2, bu muhtemelen sonraki seçeneğin adıdır. --->
Kevin Fegan

2
2. sorun: OP Sorusunda, -otheroptionardından bir değer gelmedi, bu nedenle muhtemelen bir -Flagtür seçeneği olarak düşünüldü ve 2. bir bağımsız değişken tüketmemesi gerekiyordu. 3: Argüman %1dahili IFifadeler tarafından ele alınmazsa, sessizce yok sayılır. Kod muhtemelen geçerli bir seçenek olmadığını belirten bir mesaj göstermelidir. Bu, gerekli SHIFTve GOTO :loopifadeleri iç IFifadelere ekleyerek veya bir ELSEifade ekleyerek yapılabilir .
Kevin Fegan

75

Seçilen cevap işe yarıyor, ancak biraz iyileştirme yapabilir.

  • Seçenekler muhtemelen varsayılan değerlerle başlatılmalıdır.
  • % 0 ve gerekli bağımsız değişkenler% 1 ve% 2'yi korumak güzel olurdu.
  • Her seçenek için bir IF bloğuna sahip olmak, özellikle seçeneklerin sayısı arttıkça bir acı haline geliyor.
  • Tüm seçenekleri ve varsayılanları tek bir yerde hızlı bir şekilde tanımlamanın basit ve öz bir yoluna sahip olmak güzel olurdu.
  • Bayrak görevi gören bağımsız seçenekleri desteklemek iyi olur (seçeneğin ardından değer yoktur).
  • Bir argümanın tırnak içinde olup olmadığını bilmiyoruz. Kaçış karakterleri kullanılarak bir arg değerinin geçip geçmediğini de bilmiyoruz. % ~ 1 kullanarak bir argümana erişmek ve atamayı tırnak içine almak daha iyidir. Daha sonra grup, tırnak işaretlerinin olmamasına güvenebilir, ancak özel karakterler hala kaçmadan genellikle güvenlidir. (Bu, kurşun geçirmez değildir, ancak çoğu durumu idare eder)

Çözümüm, tüm seçenekleri ve varsayılanlarını tanımlayan bir OPTIONS değişkeninin oluşturulmasına dayanıyor. OPTIONS, sağlanan bir seçeneğin geçerli olup olmadığını test etmek için de kullanılır. Olağanüstü miktarda kod, seçenek değerlerinin seçenekle aynı adı verilen değişkenlerde saklanmasıyla kaydedilir. Kod miktarı, kaç seçeneğin tanımlandığına bakılmaksızın sabittir; yalnızca SEÇENEKLER tanımının değiştirilmesi gerekir.

DÜZENLE - Ayrıca, zorunlu konum bağımsız değişkenlerinin sayısı değişirse: döngü kodu da değişmelidir. Örneğin, çoğu zaman tüm bağımsız değişkenler adlandırılır, bu durumda bağımsız değişkenleri 3 yerine 1. konumda ayrıştırmak istersiniz. Yani: döngüsü içinde 3'ün tamamı 1 olur ve 4, 2 olur.

@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!

Gerçekten o kadar çok kod yok. Yukarıdaki kodun çoğu yorumlardır. İşte yorumlar olmadan aynı kod.

@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!


Bu çözüm, bir Windows grubu içinde Unix stili argümanlar sağlar. Bu, Windows için bir norm değildir - toplu iş, genellikle gerekli bağımsız değişkenlerin önünde seçeneklere sahiptir ve seçeneklerin önünde bulunur /.

Bu çözümde kullanılan teknikler, Windows tarzı seçenekler için kolayca uyarlanabilir.

  • Ayrıştırma döngüsü her zaman için bir seçenek arar %1ve arg 1 ile başlamayana kadar devam eder./
  • İsim ile başlıyorsa SET atamalarının tırnak içine alınması gerektiğini unutmayın /.
    SET /VAR=VALUEbaşarısız olur
    SET "/VAR=VALUE". Bunu zaten çözümümde yapıyorum.
  • Standart Windows stili, ile başlayan ilk gerekli argüman değerinin olasılığını ortadan kaldırır /. Bu sınırlama //, seçenek ayrıştırma döngüsünden çıkmak için bir sinyal görevi gören örtük olarak tanımlanmış bir seçenek kullanılarak ortadan kaldırılabilir . //"Seçenek" için hiçbir şey depolanmaz .


2015-12-28 Güncellemesi: Destek! Seçenek değerleri

Yukarıdaki kodda, her bağımsız değişken, gecikmeli genişleme etkinleştirilirken genişletilir, bu !da büyük olasılıkla kaldırıldığı veya benzer bir !var!şeyin genişletildiği anlamına gelir . Ayrıca ^varsa sıyrılabilir !. Yorumsuz kodda yapılan aşağıdaki küçük değişiklik , seçenek değerlerinde korunan !ve bu sınırlamayı ortadan kaldırır ^.

@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!

Çok güzel çözüm için teşekkürler, bunu cevap olarak kabul edilenden daha çok seviyorum. Kaçırdığınız tek şey, adlandırılmış parametrelerin toplu komut dosyasında nasıl kullanılacağını söylemektir, örneğin echo% -username%
Roboblob

1
Çok zarif çözüm. Bunu kullanmayı düşünüyorsanız, sağlanan kodun 3. bağımsız değişkende ayrıştırılmaya başlayacağını unutmayın. Genel durumda istediğiniz bu değildir. Bunu düzeltmek için "3" ü "1" ile değiştirin. @dbenham bunu orijinal gönderide daha açık hale getirebilirseniz iyi olur, muhtemelen ilk başta kafası karışan tek kişi ben değilim.
ocroquette

@ocroquette - İyi nokta. Belirli bir soruyu cevaplamam gerektiği için fazla seçeneğim yoktu. Ancak, zorunlu konumsal argümanların sayısı değiştiğinde kodun değiştirilmesi gerekir. Netleştirmek için cevabımı düzenlemeye çalışacağım.
dbenham

Bunu seviyorum. Çözüm için teşekkürler, bazı IDE komut dosyaları için 2019'da hala oldukça temiz çalışıyor!
Robert Smith

18

İsteğe bağlı bağımsız değişkenler kullanmak istiyor ancak adlandırılmış bağımsız değişkenler kullanmak istemiyorsanız, bu yaklaşım benim için işe yaradı. Bunun takip edilmesi çok daha kolay bir kod olduğunu düşünüyorum.

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"==""bağımsız değişken tırnak işaretlerini içeriyorsa başarısız olur. Bunun yerine özel olmayan herhangi bir sembol kullanın, örneğin ._ = [] # etc
Fr0sT

2
Kullanıcı argüman yerine "" kullanmayı bilmediği sürece bunun işe yaradığını sanmıyorum. Aksi takdirde, nasıl bir DB adı belirleyebilirim ama DB sunucusunu boş bırakabilirim?
Sean Long

Başka bir yanıtın bunu başardığını başka nasıl görüyorsunuz? @SeanLong
sehe

@SeanLong Right, bu nedenle kullanıcı emre uymalıdır. Bu nedenle, en gerekli argümanları ilk sıralara koymalısınız.
Martin Braun

Bu kesinlikle benim için en kolay seçenekti. Gönderdiğiniz için teşekkürler :)
Steve Bauman

3

Dinamik değişken oluşturma

görüntü açıklamasını buraya girin

Artıları

  • > 9 bağımsız değişken için çalışır
  • Tutar %1, %2...%* inceliğini
  • Her ikisi için çalışır /argve -argstil
  • Tartışmalar hakkında ön bilgi yok
  • Uygulama ana rutinden ayrıdır

Eksileri

  • Eski argümanlar ardışık çalıştırmalara sızabilir, bu nedenle setlocalyerel kapsam için kullanın veya eşlik eden bir :CLEAR-ARGSrutin yazın!
  • Henüz takma desteği (gibi --forcehiç -f)
  • Boş ""argüman desteği yok

Kullanım

Aşağıdaki bağımsız değişkenlerin .bat değişkenleriyle nasıl ilişkili olduğuna bir örnek:

>> 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

Uygulama

@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-PARSERYordamın içeriğini arg-parser.batdiğer komut dosyalarında da kullanılabilir kılmak gibi harici bir .bat dosyasına da taşıyabilirsiniz .
Simon Streicher

1

Bir keresinde toplu iş dosyasındaki kısa (-h), uzun (--help) ve seçenek olmayan argümanları işleyen bir program yazmıştım. Bu teknikler şunları içerir:

  • seçenek olmayan argümanlar ve ardından bir seçenek argümanları.

  • '--help' gibi argümanı olmayan seçenekler için shift operatörü.

  • bağımsız değişken gerektiren seçenekler için iki zaman kaydırma operatörü.

  • tüm komut satırı argümanlarını işlemek için bir etiket boyunca döngü.

  • '--Help' gibi daha fazla işlem gerektirmeyen seçenekler için komut dosyasından çıkın ve işlemeyi durdurun.

  • Kullanıcı rehberliği için yardım fonksiyonları yazdı

İşte kodum.

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

İşte argüman ayrıştırıcısı. Herhangi bir dize argümanını (dokunulmadan tutulur) veya kaçan seçenekleri (tek veya seçenek / değer çiftleri) karıştırabilirsiniz. Test etmek için son 2 ifadeyi geri alın ve şu şekilde çalıştırın:

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

Kaçış karakteri, "_SEP_=/"gerekirse yeniden tanımla olarak tanımlanır .

@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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.