Pliki wsadowe - liczba argumentów wiersza poleceń

99

Po prostu przekonwertowałem niektóre skrypty powłoki na pliki wsadowe i wydaje mi się, że jest jedna rzecz, której nie mogę znaleźć ... a jest to prosta liczba argumentów wiersza poleceń.

na przykład. Jeśli masz:

myapp foo bar

W skorupkach:

  • $ # -> 2
  • $ * -> foo bar
  • $ 0 -> myapp
  • $ 1 -> foo
  • 2 $ -> bar

W partii

  • ?? -> 2 <---- jakie polecenie ?!
  • % * -> foo bar
  • % 0 -> myapp
  • % 1 -> foo
  • % 2 -> bar

Rozejrzałem się więc dookoła i albo szukam w złym miejscu, albo jestem ślepy, ale nie mogę znaleźć sposobu na obliczenie liczby przekazanych argumentów wiersza poleceń.

Czy istnieje polecenie podobne do polecenia powłoki „$ #” dla plików wsadowych?

ps. najbliższe, jakie znalazłem, to iteracja przez% 1s i użycie 'shift', ale muszę ponownie sprawdzić% 1,% 2 itd. w dalszej części skryptu, więc to nie jest dobre.

pyko
źródło
twój ciąg to 2 myapp foo bar?
PsychoData,
2
rada: nie zamieniaj sh na BAT. zamiast tego pobierz cygwin i użyj go zamiast tego. tzn. twoje skrypty sh będą działać na komputerze z systemem Windows. i nie będziesz musiał tłumaczyć każdego sh na BAT!
Marty McGowan

Odpowiedzi:

104

Trochę googlując daje następujący wynik z wikibooków :

set argC=0
for %%x in (%*) do Set /A argC+=1

echo %argC%

Wygląda na to, że cmd.exe wyewoluował trochę ze starego DOSa :)

nimrodm
źródło
7
Zauważ, że ten wariant fordziała tylko dla argumentów, które wyglądają jak nazwy plików, a nie dla łańcuchów opcji, takich jak -?. Używanie cudzysłowów ( for %%i in ("%*") ...) działa w przypadku argumentów podobnych, -?ale znowu kończy się niepowodzeniem dla argumentów cytowanych w cudzysłowie z powodu zagnieżdżonych cudzysłowów. Wydaje się, że jedyną solidną metodą jest shift
Ferdinand Beyer
1
MyBatchFile "*.*" "Abc"zgłasza, że ​​argC == 9. - Głosowano w dół.
BrainSlugs83
Przetestowałem to na 2500 argumentach jako funkcję i użyłem go do zdefiniowania tablic o tym samym rozmiarze. Nie pytaj mnie dokładnie, dlaczego. Głównie po prostu uczenie się, do czego jest zdolna partia.
T3RR0R
44

Masz tendencję do obsługi wielu argumentów za pomocą tego rodzaju logiki:

IF "%1"=="" GOTO HAVE_0
IF "%2"=="" GOTO HAVE_1
IF "%3"=="" GOTO HAVE_2

itp.

Jeśli masz więcej niż 9 argumentów, to takie podejście nie daje Ci rady. Istnieją różne sposoby tworzenia liczników, które można znaleźć tutaj , ale ostrzegam, że nie są one dla osób o słabych nerwach.

Dean Povey
źródło
6
Nadal możesz shiftliczyć więcej niż 9 ... i bez 10 linii równie wyglądającego kodu.
Joey
19

Poniższa funkcja :getargcmoże być tym, czego szukasz.

@echo off
setlocal enableextensions enabledelayedexpansion
call :getargc argc %*
echo Count is %argc%
echo Args are %*
endlocal
goto :eof

:getargc
    set getargc_v0=%1
    set /a "%getargc_v0% = 0"
:getargc_l0
    if not x%2x==xx (
        shift
        set /a "%getargc_v0% = %getargc_v0% + 1"
        goto :getargc_l0
    )
    set getargc_v0=
    goto :eof

Zasadniczo wykonuje iterację raz po liście (która jest lokalna dla funkcji, więc przesunięcia nie wpłyną na listę z powrotem w głównym programie), zliczając je, aż się wyczerpie.

Używa również sprytnej sztuczki, przekazując nazwę zmiennej zwracanej, która ma być ustawiona przez funkcję.

Główny program po prostu ilustruje, jak go wywołać, i wyświetla później argumenty, aby upewnić się, że są nietknięte:

C:\Here> xx.cmd 1 2 3 4 5
    Count is 5
    Args are 1 2 3 4 5
C:\Here> xx.cmd 1 2 3 4 5 6 7 8 9 10 11
    Count is 11
    Args are 1 2 3 4 5 6 7 8 9 10 11
C:\Here> xx.cmd 1
    Count is 1
    Args are 1
C:\Here> xx.cmd
    Count is 0
    Args are
C:\Here> xx.cmd 1 2 "3 4 5"
    Count is 3
    Args are 1 2 "3 4 5"
paxdiablo
źródło
jak możesz na tej podstawie zmienić przepływ? (może być inne pytanie, ale myślę, że krótki przykład byłby tu również bardzo wygodny!)
n611x007
@ n611x007, gdzie echo Count is %argc%można wstawić własny kod, który mógłby sprawdzić, czy ta liczba jest poprawna, a następnie przejść dalej.
kenny
7

Spróbuj tego:

SET /A ARGS_COUNT=0    
FOR %%A in (%*) DO SET /A ARGS_COUNT+=1    
ECHO %ARGS_COUNT%
David
źródło
6
czy ta odpowiedź różni się w jakiś sposób od @nimrod one ? ...
niebieskawe
1
sprawdź komentarz @FerdinandBeyer w nimrodm's. nie zamierzam głosować przeciw, ponieważ masz 21 rep 8)
n611x007
6

Jeśli liczba argumentów powinna być dokładną liczbą (mniejszą lub równą 9), to w prosty sposób można to sprawdzić:

if "%2" == "" goto args_count_wrong
if "%3" == "" goto args_count_ok

:args_count_wrong
echo I need exactly two command line arguments
exit /b 1

:args_count_ok
slu
źródło
2

Unika używania jednego shiftlub forcyklu kosztem rozmiaru i czytelności.

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set /a arg_idx=1
set "curr_arg_value="
:loop1
if !arg_idx! GTR 9 goto :done
set curr_arg_label=%%!arg_idx!
call :get_value curr_arg_value !curr_arg_label!
if defined curr_arg_value (
  echo/!curr_arg_label!: !curr_arg_value!
  set /a arg_idx+=1
  goto :loop1
)
:done
set /a cnt=!arg_idx!-1
echo/argument count: !cnt!
endlocal
goto :eof

:get_value
(
  set %1=%2
)

Wynik:

count_cmdline_args.bat testing more_testing arg3 another_arg

%1: testing
%2: more_testing
%3: arg3
%4: another_arg
argument count: 4

EDYCJA: Zastosowana tutaj „sztuczka” obejmuje:

  1. Skonstruowanie łańcucha, który reprezentuje aktualnie ocenianą zmienną argumentu wiersza polecenia (np. „% 1”, „% 2” itp.) Przy użyciu łańcucha zawierającego znak procentu ( %%) i zmienną licznika arg_idxw każdej iteracji pętli.

  2. Przechowywanie tego ciągu w zmiennej curr_arg_label.

  3. Przekazanie zarówno tego ciągu ( !curr_arg_label!), jak i nazwy zmiennej zwracanej ( curr_arg_value) do podprogramu pierwotnego get_value.

  4. W podprogramie wartość pierwszego argumentu ( %1) jest używana po lewej stronie przypisania ( set), a wartość drugiego argumentu ( %2) po prawej stronie. Jednak przekazanie argumentu drugiego podprogramu powoduje, że interpreter poleceń przekształca go na wartość argumentu wiersza poleceń programu głównego. Oznacza to, że przekazywane jest nie, na przykład, „% 4”, ale jakąkolwiek wartość przechowuje czwarta zmienna argumentu wiersza poleceń (w przykładowym użyciu „inny_arg”).

  5. Następnie zmienna przekazana do podprogramu jako zmienna zwracana ( curr_arg_value) jest testowana pod kątem niezdefiniowania, co mogłoby się zdarzyć, gdyby nie było aktualnie obliczanego argumentu wiersza poleceń. Początkowo było to porównanie wartości zmiennej zwracanej zawiniętej w nawiasy kwadratowe z pustymi nawiasami kwadratowymi (jest to jedyny znany mi sposób testowania argumentów programu lub podprogramu, który może zawierać cudzysłowy i był przeoczoną pozostałością po fazie prób i błędów), ale został od tego czasu naprawiony tak, jak jest teraz.

ognisko
źródło
1
Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
thewaywewewewewewbyed
@thewaywewere dziękuję, ciągle zapominam, że dla wielu osób (prawdopodobnie większości) lepiej jest mieć opis tekstowy zamiast kodu „oczywistego”.
fen-fire,
@ fen-fire poza tym, że kod nie jest zrozumiały.
Loduwijk
1

Ostatnia odpowiedź była dwa lata temu, ale potrzebowałem wersji na więcej niż dziewięć argumentów wiersza poleceń. Może być inny też ...

@echo off
setlocal

set argc_=1
set arg0_=%0
set argv_=

:_LOOP
set arg_=%1
if defined arg_ (
  set arg%argc_%_=%1
  set argv_=%argv_% %1
  set /a argc_+=1
  shift
  goto _LOOP
)
::dont count arg0
set /a argc_-=1
echo %argc_% arg(s)

for /L %%i in (0,1,%argc_%) do (
  call :_SHOW_ARG arg%%i_ %%arg%%i_%%
)

echo converted to local args
call :_LIST_ARGS %argv_%
exit /b


:_LIST_ARGS
setlocal
set argc_=0
echo arg0=%0

:_LOOP_LIST_ARGS
set arg_=%1
if not defined arg_ exit /b
set /a argc_+=1
call :_SHOW_ARG arg%argc_% %1
shift
goto _LOOP_LIST_ARGS


:_SHOW_ARG
echo %1=%2
exit /b

Rozwiązanie to pierwsze 19 wierszy i konwertuje wszystkie argumenty na zmienne w stylu c. Wszystkie inne rzeczy po prostu sondują wynik i pokazują konwersję na lokalne argumenty. Możesz odwoływać się do argumentów według indeksu w dowolnej funkcji.

Kazimierza
źródło
0

Solidnym rozwiązaniem jest delegowanie liczenia do podprogramu wywoływanego za pomocą call; podprogram używa gotoinstrukcji do emulacji pętli, w której shiftużywa się iteracyjnie argumentów (tylko podprogram):

@echo off
setlocal

:: Call the argument-counting subroutine with all arguments received,
:: without interfering with the ability to reference the arguments
:: with %1, ... later.
call :count_args %*

:: Print the result.
echo %ReturnValue% argument(s) received.

:: Exit the batch file.
exit /b

:: Subroutine that counts the arguments given.
:: Returns the count in %ReturnValue%
:count_args
  set /a ReturnValue = 0
  :count_args_for

    if %1.==. goto :eof

    set /a ReturnValue += 1

    shift
  goto count_args_for
mklement0
źródło