Arquivos em lote - número de argumentos de linha de comando

99

Apenas convertendo alguns scripts de shell em arquivos em lote e há uma coisa que não consigo encontrar ... e isso é uma simples contagem do número de argumentos de linha de comando.

por exemplo. se você tem:

myapp foo bar

Em Shell:

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

Em lote

  • ?? -> 2 <---- qual comando ?!
  • % * -> barra de foo
  • % 0 -> myapp
  • % 1 -> foo
  • % 2 -> bar

Eu olhei em volta e ou estou procurando no lugar errado ou estou cego, mas não consigo encontrar uma maneira de obter uma contagem do número de argumentos de linha de comando transmitidos.

Existe um comando semelhante ao "$ #" do shell para arquivos em lote?

ps. o mais próximo que descobri é iterar por% 1s e usar 'shift', mas preciso referir-me a% 1,% 2 etc. posteriormente no script, então isso não é bom.

pyko
fonte
sua string é 2 myapp foo bar?
PsychoData
2
conselho: não converta sh em BAT. em vez disso, baixe o cygwin e use-o. ou seja, seus scripts sh funcionarão em uma máquina Windows. e você não terá que traduzir todos os shops para o BAT!
Marty McGowan

Respostas:

104

Pesquisando um pouco no Google, você obtém o seguinte resultado dos wikibooks :

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

echo %argC%

Parece que o cmd.exe evoluiu um pouco desde os velhos tempos do DOS :)

nimrodm
fonte
7
Observe que esta variante de forfunciona apenas para argumentos que se parecem com nomes de arquivo, não para strings de opção como -?. Usar aspas ( for %%i in ("%*") ...) funciona para argumentos semelhantes, -?mas falha novamente para argumentos entre aspas devido a aspas aninhadas. A única forma robusta parece envolver shift...
Ferdinand Beyer
1
MyBatchFile "*.*" "Abc"relata que argC == 9. - Votos negados.
BrainSlugs83,
Testei 2.500 argumentos como uma função e usei para definir arrays do mesmo tamanho. Não me pergunte por que exatamente. Principalmente aprender do que o batch é capaz.
T3RR0R
44

Você tende a lidar com vários argumentos com este tipo de lógica:

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

etc.

Se você tiver mais de 9 argumentos, então está ferrado com essa abordagem. Existem vários hacks para a criação de contadores que você pode encontrar aqui , mas esteja avisado que eles não são para os medrosos.

Dean Povey
fonte
6
Você ainda pode usar shiftpara contar mais de 9 ... e sem ter 10 linhas de código de aparência igual.
Joey
19

A função :getargcabaixo pode ser o que você está procurando.

@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

Basicamente, ele itera uma vez sobre a lista (que é local para a função para que as mudanças não afetem a lista de volta no programa principal), contando-as até que ela se esgote.

Ele também usa um truque bacana, passando o nome da variável de retorno a ser definida pela função.

O programa principal apenas ilustra como chamá-lo e exibe os argumentos depois para garantir que eles não sejam tocados:

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
fonte
como você pode alterar o fluxo com base nisso? (pode ser uma pergunta diferente, mas acho que um pequeno exemplo também seria muito conveniente aqui!)
n611x007
@ n611x007 onde echo Count is %argc%você deve inserir seu próprio código que pode verificar se a contagem está correta e então seguir em frente.
Kenny
7

Experimente isto:

SET /A ARGS_COUNT=0    
FOR %%A in (%*) DO SET /A ARGS_COUNT+=1    
ECHO %ARGS_COUNT%
David
fonte
6
esta resposta é de alguma forma diferente de @nimrod one ? ...
azulado de
1
verifique o comentário de @FerdinandBeyer no nimrodm's. não vou votar negativamente porque você tem 21 rep 8)
n611x007
6

Se o número de argumentos deve ser um número exato (menor ou igual a 9), esta é uma maneira simples de verificar:

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

Evita usar um shiftou um forciclo à custa do tamanho e da legibilidade.

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

Resultado:

count_cmdline_args.bat testing more_testing arg3 another_arg

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

EDITAR: O "truque" usado aqui envolve:

  1. Construindo uma string que representa uma variável de argumento de linha de comando avaliada atualmente (por exemplo, "% 1", "% 2" etc.) usando uma string que contém um caractere de porcentagem ( %%) e uma variável de contador arg_idxem cada iteração do loop.

  2. Armazenando essa string em uma variável curr_arg_label.

  3. Passar aquela string ( !curr_arg_label!) e o nome de uma variável de retorno ( curr_arg_value) para um subprograma primitivo get_value.

  4. No subprograma, o valor do primeiro argumento ( %1) é usado no lado esquerdo da atribuição ( set) e o valor do segundo argumento ( %2) à direita. No entanto, quando o argumento do segundo subprograma é passado, ele é resolvido no valor do argumento da linha de comando do programa principal pelo interpretador de comandos. Ou seja, o que é passado não é, por exemplo, "% 4", mas qualquer valor que a quarta variável de argumento da linha de comando contenha ("another_arg" no uso de amostra).

  5. Então, a variável fornecida ao subprograma como variável de retorno ( curr_arg_value) é testada para ser indefinida, o que aconteceria se o argumento da linha de comando avaliado atualmente estivesse ausente. Inicialmente, esta foi uma comparação do valor da variável de retorno entre colchetes e colchetes vazios (que é a única maneira que eu conheço de programas de teste ou argumentos de subprograma que podem conter aspas e foi um resto esquecido da fase de tentativa e erro), mas desde então foi corrigido como é agora.

fogo de pântano
fonte
1
Embora este código possa responder à pergunta, fornecer contexto adicional sobre como e / ou por que ele resolve o problema melhoraria o valor da resposta a longo prazo.
thewaywere
@thewaywewere, thank you, eu continuo esquecendo que para muitas pessoas (provavelmente a maioria) é melhor ter uma descrição de texto em vez de um código "autoexplicativo".
fen-fire
@ fen-fire, exceto que o código não é autoexplicativo.
Loduwijk
1

A última resposta foi há dois anos, mas eu precisava de uma versão para mais de nove argumentos de linha de comando. Pode ser que outro também faça ...

@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

A solução são as primeiras 19 linhas e converte todos os argumentos em variáveis ​​no estilo c. Todas as outras coisas apenas investigam o resultado e mostram a conversão para argumentos locais. Você pode fazer referência a argumentos por índice em qualquer função.

Kasimir
fonte
0

Uma solução robusta é delegar a contagem a uma sub - rotina chamada com call; a sub-rotina usa gotoinstruções para emular um loop em que shifté usado para consumir os argumentos (apenas sub-rotina) iterativamente:

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