Como o Windows Command Interpreter (CMD.EXE) analisa scripts?

142

Encontrei o ss64.com, que fornece uma boa ajuda sobre como escrever scripts em lote que o Windows Command Interpreter executará.

No entanto, não consegui encontrar uma boa explicação sobre a gramática dos scripts em lote, como as coisas se expandem ou não e como escapar delas.

Aqui estão alguns exemplos de perguntas que não consegui resolver:

  • Como o sistema de cotação é gerenciado? Eu criei um script TinyPerl
    ( foreach $i (@ARGV) { print '*' . $i ; }), compilei e chamei assim:
    • my_script.exe "a ""b"" c" → a saída é *a "b*c
    • my_script.exe """a b c""" → produzi-lo *"a*b*c"
  • Como o echocomando interno funciona? O que é expandido dentro desse comando?
  • Por que preciso usar for [...] %%Iem scripts de arquivo, mas for [...] %Iem sessões interativas?
  • Quais são os caracteres de escape e em que contexto? Como escapar de um sinal de porcentagem? Por exemplo, como posso ecoar %PROCESSOR_ARCHITECTURE%literalmente? Descobri que echo.exe %""PROCESSOR_ARCHITECTURE%funciona, existe uma solução melhor?
  • Como pares de %partida? Exemplo:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Como garantir que uma variável passe para um comando como argumento único, se essa variável contiver aspas duplas?
  • Como as variáveis ​​são armazenadas ao usar o setcomando? Por exemplo, se eu faço set a=a" be então echo.%a%obtenho a" b. No entanto, se eu usar echo.exeo UnxUtils, recebo a b. Como vem a %a%expansão de uma maneira diferente?

Obrigado por suas luzes.

Benoit
fonte
Rob van der Woude tem um excelente script de lote e referência de prompt de comando do Windows em seu site.
precisa saber é o seguinte

Respostas:

200

Realizamos experimentos para investigar a gramática dos scripts em lote. Também investigamos as diferenças entre os modos de lote e de linha de comando.

Analisador de Linha de Lote:

Aqui está uma breve visão geral das fases no analisador de linha de arquivo em lote:

Fase 0) Linha de Leitura:

Fase 1) Porcentagem de expansão:

Fase 2) Processar caracteres especiais, tokenizar e criar um bloco de comando em cache: Esse é um processo complexo que é afetado por coisas como aspas, caracteres especiais, delimitadores de token e escapes de sinal de intercalação.

Fase 3) Repetir o (s) comando (s) analisado (s) Somente se o bloco de comando não tiver começado @e o ECHO estava LIGADO no início da etapa anterior.

Fase 4) %XExpansão da variável FOR : Somente se um comando FOR estiver ativo e os comandos após o DO estiverem sendo processados.

Fase 5) Expansão atrasada: somente se a expansão atrasada estiver ativada

Fase 5.3) Processamento do tubo: somente se os comandos estiverem em ambos os lados de um tubo

Fase 5.5) Executar redirecionamento:

Fase 6) Processamento de CHAMADA / duplicação de intercalação: somente se o token de comando for CHAMADA

Fase 7) Execute: O comando é executado


Aqui estão os detalhes para cada fase:

Observe que as fases descritas abaixo são apenas um modelo de como o analisador de lotes funciona. Os internos reais do cmd.exe podem não refletir essas fases. Mas esse modelo é eficaz em prever o comportamento de scripts em lote.

Fase 0) Read Line: Leia a linha de entrada primeiro <LF>.

  • Ao ler uma linha a ser analisada como um comando, <Ctrl-Z>(0x1A) é lido como <LF>(LineFeed 0x0A)
  • Quando GOTO ou ligue lê linhas durante a digitalização para um: etiqueta, <Ctrl-Z>, é tratado como si - é não convertida em<LF>

Fase 1) Porcentagem de expansão:

  • Um duplo %%é substituído por um único%
  • Expansão de argumentos ( %*, %1, %2, etc.)
  • Expansão de %var%, se var não existir, substitua-o por nada
  • A linha é truncada no início, <LF>não dentro da %var%expansão
  • Para obter uma explicação completa, leia a primeira metade disso em dbenham .

Fase 2) Processar caracteres especiais, tokenizar e criar um bloco de comando em cache: Esse é um processo complexo que é afetado por coisas como aspas, caracteres especiais, delimitadores de token e escapes de sinal de intercalação. O que se segue é uma aproximação deste processo.

Existem conceitos que são importantes ao longo desta fase.

  • Um token é simplesmente uma sequência de caracteres que é tratada como uma unidade.
  • Os tokens são separados por delimitadores de token. Os delimitadores de token padrão são <space> <tab> ; , = <0x0B> <0x0C>e delimitadores <0xFF>
    consecutivos de token são tratados como um - não há tokens vazios entre os delimitadores de token
  • Não há delimitadores de token em uma sequência de caracteres citada. Toda a cadeia de caracteres citada é sempre tratada como parte de um único token. Um único token pode consistir em uma combinação de seqüências de caracteres entre aspas e caracteres não entre aspas.

Os seguintes caracteres podem ter um significado especial nesta fase, dependendo do contexto: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Observe cada caractere da esquerda para a direita:

  • Se, em <CR>seguida, remova-o, como se nunca estivesse lá (exceto pelo comportamento de redirecionamento estranho )
  • Se um sinal de intercalação ( ^), o próximo caractere é escapado e o cursor de intercalação é removido. Caracteres escapados perdem todo significado especial (exceto <LF>).
  • Se uma citação ( "), alterne o sinalizador de citação. Se o sinalizador de cotação estiver ativo, apenas "e <LF>será especial. Todos os outros caracteres perdem seu significado especial até que a próxima citação desative o sinalizador de citação. Não é possível escapar da cotação de fechamento. Todos os caracteres citados estão sempre no mesmo token.
  • <LF>sempre desativa o sinalizador de cotação. Outros comportamentos variam dependendo do contexto, mas as aspas nunca alteram o comportamento de <LF>.
    • Escapou <LF>
      • <LF> é despojado
      • O próximo caractere é escapado. Se no final do buffer da linha, a próxima linha é lida e processada pelas fases 1 e 1.5 e anexada à atual antes de escapar do próximo caractere. Se o próximo caractere for <LF>, ele será tratado como um literal, o que significa que esse processo não é recursivo.
    • Sem escape <LF>entre parênteses
      • <LF> é retirado e a análise da linha atual é encerrada.
      • Quaisquer caracteres restantes no buffer de linha são simplesmente ignorados.
    • Sem escape <LF>dentro de um bloco entre parênteses FOR IN
      • <LF> é convertido em um <space>
      • Se no final do buffer da linha, a próxima linha será lida e anexada à atual.
    • Sem escape <LF>dentro de um bloco de comando entre parênteses
      • <LF>é convertido em <LF><space>e <space>é tratado como parte da próxima linha do bloco de comando.
      • Se no final do buffer da linha, a próxima linha será lida e anexada ao espaço.
  • Se um dos caracteres especiais & | <ou >, dividir a linha neste ponto, a fim de manipular pipes, concatenação de comandos e redirecionamento.
    • No caso de um pipe ( |), cada lado é um comando (ou bloco de comando) separado que recebe tratamento especial na fase 5.3
    • No caso de &, &&ou ||concatenação de comando, cada lado da concatenação é tratado como um comando separado.
    • No caso de <, <<, >, ou >>redirecionamento, a cláusula de redireccionamento é analisado, temporariamente removido e, em seguida acrescentado ao final da corrente de comando. Uma cláusula de redirecionamento consiste em um dígito de identificador de arquivo opcional, o operador de redirecionamento e o token de destino do redirecionamento.
      • Se o token que precede o operador de redirecionamento for um único dígito sem escape, o dígito especificará o identificador do arquivo a ser redirecionado. Se o token de identificador não for encontrado, o padrão de redirecionamento de saída será 1 (stdout) e o padrão de redirecionamento de entrada será 0 (stdin).
  • Se o primeiro token para esse comando (antes de mover o redirecionamento para o final) começar com @, ele @terá um significado especial. ( @não é especial em nenhum outro contexto)
    • O especial @é removido.
    • Se ECHO estiver LIGADO, esse comando, juntamente com os seguintes comandos concatenados nesta linha, serão excluídos do eco da fase 3. Se @antes de uma abertura (, todo o bloco entre parênteses é excluído do eco da fase 3.
  • Parênteses de processo (fornece instruções compostas em várias linhas):
    • Se o analisador não estiver procurando por um token de comando, ele (não será especial.
    • Se o analisador estiver procurando por um token de comando e localizar (, inicie uma nova instrução composta e aumente o contador de parênteses
    • Se o contador de parênteses for> 0, )encerre a instrução composta e diminua o contador de parênteses.
    • Se o final da linha for alcançado e o contador de parênteses for> 0, a próxima linha será anexada à instrução composta (inicia novamente com a fase 0)
    • Se o contador de parênteses for 0 e o analisador estiver procurando por um comando, )funcionará de maneira semelhante a uma REMinstrução, desde que seja imediatamente seguido por um delimitador de token, caractere especial, nova linha ou fim de arquivo
      • Todos os caracteres especiais perdem o significado, exceto ^(é possível concatenação de linha)
      • Quando o final da linha lógica é alcançado, todo o "comando" é descartado.
  • Cada comando é analisado em uma série de tokens. O primeiro token é sempre tratado como um token de comando (depois que o especial @é retirado e o redirecionamento movido para o final).
    • Os delimitadores de token iniciais antes do token de comando são removidos
    • Ao analisar o token de comando, ele (funciona como um delimitador de token de comando, além dos delimitadores de token padrão
    • A manipulação de tokens subsequentes depende do comando.
  • A maioria dos comandos simplesmente concatena todos os argumentos após o token de comando em um único token de argumento. Todos os delimitadores de token de argumento são preservados. As opções de argumento geralmente não são analisadas até a fase 7.
  • Três comandos recebem tratamento especial - IF, FOR e REM
    • O IF é dividido em duas ou três partes distintas que são processadas independentemente. Um erro de sintaxe na construção SE resultará em um erro fatal de sintaxe.
      • A operação de comparação é o comando real que flui até a fase 7
        • Todas as opções de FI são totalmente analisadas na fase 2.
        • Delimitadores consecutivos de tokens são recolhidos em um único espaço.
        • Dependendo do operador de comparação, haverá um ou dois tokens de valor que são identificados.
      • O bloco de comandos True é o conjunto de comandos após a condição e é analisado como qualquer outro bloco de comandos. Se ELSE deve ser usado, o bloco True deve estar entre parênteses.
      • O bloco de comandos False opcional é o conjunto de comandos após ELSE. Novamente, esse bloco de comando é analisado normalmente.
      • Os blocos de comando Verdadeiro e Falso não fluem automaticamente para as fases subseqüentes. O processamento subsequente é controlado pela fase 7.
    • FOR é dividido em dois após o DO. Um erro de sintaxe na construção FOR resultará em um erro de sintaxe fatal.
      • A parte através do DO é o comando de iteração FOR real que flui por toda a fase 7
        • Todas as opções FOR são totalmente analisadas na fase 2.
        • A cláusula entre parênteses IN trata <LF>como <space>. Após a análise da cláusula IN, todos os tokens são concatenados juntos para formar um único token.
        • Delimitadores de token consecutivos sem escape / sem aspas são recolhidos em um único espaço em todo o comando FOR através do DO.
      • A parte após DO é um bloco de comando que é analisado normalmente. O processamento subsequente do bloco de comando DO é controlado pela iteração na fase 7.
    • O REM detectado na fase 2 é tratado dramaticamente diferente de todos os outros comandos.
      • Apenas um token de argumento é analisado - o analisador ignora caracteres após o primeiro token de argumento.
      • O comando REM pode aparecer na saída da fase 3, mas o comando nunca é executado e o texto do argumento original é repetido - os pontos de intercalação que escapam não são removidos, exceto ...
        • Se houver apenas um token de argumento que termina com um sem escape ^que termina a linha, o token de argumento é jogado fora e a linha subseqüente é analisada e anexada ao REM. Isso se repete até que haja mais de um token ou o último caractere não ^.
  • Se o token de comando começar com :e esta for a primeira rodada da fase 2 (não uma reinicialização devido a CALL na fase 6),
    • O token é normalmente tratado como um rótulo não executado .
      • O restante da linha é analisado, no entanto ), <, >, &e |já não têm significado especial. O restante da linha é considerado parte do rótulo "comando".
      • O ^continua a ser especial, o que significa que a continuação da linha pode ser usada para acrescentar a linha subseqüente ao rótulo.
      • Um Rótulo Não Executado dentro de um bloco entre parênteses resultará em um erro fatal de sintaxe, a menos que seja imediatamente seguido por um comando ou Rótulo Executado na próxima linha.
        • (não tem mais um significado especial para o primeiro comando que segue o Rótulo Não Executado .
      • O comando é interrompido após a conclusão da análise de rótulo. As fases subsequentes não ocorrem para o rótulo
    • Há três exceções que podem fazer com que um rótulo encontrado na fase 2 seja tratado como um rótulo executado que continua analisando a fase 7.
      • Há redirecionamento que precede o rótulo de símbolo, e há um |tubo ou &, &&ou ||concatenação de comando na linha.
      • Há um redirecionamento que precede o token do rótulo e o comando está dentro de um bloco entre parênteses.
      • O token do rótulo é o primeiro comando em uma linha dentro de um bloco entre parênteses, e a linha acima terminou com um Rótulo Não Executado .
    • O seguinte ocorre quando um rótulo executado é descoberto na fase 2
      • O rótulo, seus argumentos e seu redirecionamento são todos excluídos de qualquer saída de eco na fase 3
      • Quaisquer comandos concatenados subsequentes na linha são totalmente analisados ​​e executados.
    • Para mais informações sobre etiquetas Executados vs. Labels não executadas , consulte https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Fase 3) Repetir o (s) comando (s) analisado (s) Somente se o bloco de comando não tiver começado @e o ECHO estava LIGADO no início da etapa anterior.

Fase 4) %XExpansão da variável FOR : Somente se um comando FOR estiver ativo e os comandos após o DO estiverem sendo processados.

  • Neste ponto, a fase 1 do processamento em lote já terá convertido uma variável FOR como %%Xem %X. A linha de comando possui regras de expansão percentual diferentes para a fase 1. Esse é o motivo pelo qual as linhas de comando usam, %Xmas os arquivos em lote usam %%Xpara variáveis ​​FOR.
  • Os nomes de variáveis ​​FOR diferenciam maiúsculas de minúsculas, mas ~modifiersnão diferenciam maiúsculas de minúsculas.
  • ~modifierstem precedência sobre nomes de variáveis. Se um caractere a seguir ~for um modificador e um nome de variável FOR válido, e existir um caractere subsequente que seja um nome de variável FOR ativo, o caractere será interpretado como um modificador.
  • Os nomes de variáveis ​​FOR são globais, mas apenas dentro do contexto de uma cláusula DO. Se uma rotina for CHAMADA de dentro de uma cláusula FOR DO, as variáveis ​​FOR não serão expandidas dentro da rotina CALLed. Mas se a rotina tiver seu próprio comando FOR, todas as variáveis ​​FOR atualmente definidas estarão acessíveis aos comandos DO internos.
  • Os nomes de variáveis ​​FOR podem ser reutilizados em FORs aninhados. O valor interno de FOR tem precedência, mas quando o INNER FOR é fechado, o valor externo de FOR é restaurado.
  • Se o ECHO estava LIGADO no início desta fase, a fase 3) é repetida para mostrar os comandos DO analisados ​​após a expansão das variáveis ​​FOR.

---- A partir deste momento, cada comando identificado na fase 2 é processado separadamente.
---- As fases 5 a 7 são concluídas para um comando antes de passar para o próximo.

Fase 5) Expansão atrasada: somente se a expansão atrasada estiver ativada, o comando não estará em um bloco entre parênteses nos dois lados de um canal e o comando não será um script em lote "nu" (nome do script sem parênteses, CALL, concatenação de comando, ou tubo).

  • Cada token para um comando é analisado para expansão atrasada independentemente.
    • A maioria dos comandos analisa dois ou mais tokens - o token de comando, o token de argumentos e cada token de destino de redirecionamento.
    • O comando FOR analisa apenas o token da cláusula IN.
    • O comando SE analisa apenas os valores de comparação - um ou dois, dependendo do operador de comparação.
  • Para cada token analisado, verifique primeiro se ele contém algum !. Caso contrário, o token não será analisado - importante para os ^caracteres. Se o token contiver !, verifique cada caractere da esquerda para a direita:
    • Se for um sinal de intercalação ( ^), o próximo caractere não tem significado especial, o próprio intercalação é removido
    • Se for um ponto de exclamação, procure o próximo ponto de exclamação (os sinais de intercalação não são mais observados), expanda para o valor da variável.
      • A abertura consecutiva !é recolhida em um único!
      • Qualquer restante não pareado !é removido
    • A expansão de vars nesse estágio é "segura", porque caracteres especiais não são mais detectados (pares <CR>ou <LF>)
    • Para uma explicação mais completa, leia a segunda metade disso no dbenham same thread - Fase do ponto de exclamação

Fase 5.3) Processamento do tubo: Somente se os comandos estiverem em ambos os lados de um tubo.
Cada lado do tubo é processado de forma independente e assíncrona.

  • Se o comando for interno ao cmd.exe, ou se for um arquivo em lotes ou se for um bloco de comando entre parênteses, ele será executado em um novo encadeamento do cmd.exe %comspec% /S /D /c" commandBlock", para que o bloco de comando obtenha uma reinicialização de fase, mas desta vez no modo de linha de comando.
    • Se um bloco de comando entre parênteses, todos <LF>com um comando antes e depois serão convertidos em <space>&. Outros <LF>são despidos.
  • Este é o fim do processamento para os comandos de canal.
  • Consulte Por que a expansão atrasada falha quando dentro de um bloco de código canalizado? para saber mais sobre análise e processamento de tubos

Fase 5.5) Executar redirecionamento: Qualquer redirecionamento descoberto na fase 2 agora é executado.

Fase 6) Processamento de CHAMADA / duplicação de intercalação: Somente se o token de comando for CALL ou se o texto antes do primeiro delimitador de token padrão ocorrer for CALL. Se CALL for analisado a partir de um token de comando maior, a parte não utilizada será anexada ao token de argumentos antes de continuar.

  • Analise o token de argumentos em busca de aspas /?. Se encontrado em qualquer lugar dentro dos tokens, aborte a fase 6 e prossiga para a fase 7, onde a HELP for CALL será impressa.
  • Remova a primeira CALL, para que várias CALLs possam ser empilhadas
  • Dobrar todos os pontos de intercalação
  • Reinicie as fases 1, 1,5 e 2, mas não continue para a fase 3
    • Qualquer sinal de intercalação dobrado é reduzido novamente para um sinal de intercalação, desde que não esteja entre aspas. Infelizmente, porém, os circuitos cotados permanecem dobrados.
    • A fase 1 muda um pouco
      • Os erros de expansão na etapa 1.2 ou 1.3 abortam a CHAMADA, mas o erro não é fatal - o processamento em lote continua.
    • As tarefas da fase 2 são um pouco alteradas
      • Qualquer novo redirecionamento sem aspas e sem aspas que não foi detectado na primeira rodada da fase 2 é detectado, mas é removido (incluindo o nome do arquivo) sem realmente executar o redirecionamento
      • Qualquer novo sinal de intercalação sem aspas e sem aspas no final da linha é removido sem executar a continuação da linha
      • A CHAMADA é interrompida sem erro se alguma das seguintes situações for detectada
        • Aparecendo recentemente sem aspas, sem escape &ou|
        • O token de comando resultante começa com sem aspas, sem escape (
        • O primeiro token após a chamada removida começou com @
      • Se o comando resultante for um IF ou FOR aparentemente válido, a execução falhará subsequentemente com um erro informando que IFou FORnão é reconhecido como um comando interno ou externo.
      • É claro que o CALL não será abortado nesta 2ª rodada da fase 2 se o token de comando resultante for um rótulo começando com :.
  • Se o token de comando resultante for CALL, reinicie a Fase 6 (repete até que não haja mais CALL)
  • Se o token de comando resultante for um script em lote ou um rótulo:, a execução do CALL será totalmente tratada pelo restante da Fase 6.
    • Empurre a posição atual do arquivo de script em lote na pilha de chamadas para que a execução possa retomar da posição correta quando a CHAMADA for concluída.
    • Configure os tokens de argumento% 0,% 1,% 2, ...% N e% * para o CALL, usando todos os tokens resultantes
    • Se o token de comando for um rótulo que comece com :,
      • Reinicie a Fase 5. Isso pode afetar o que: o rótulo é CHAMADO. Mas como os tokens% 0 etc. já foram configurados, ele não alterará os argumentos passados ​​para a rotina CALLed.
      • Execute o rótulo GOTO para posicionar o ponteiro do arquivo no início da sub-rotina (ignore outros tokens que possam seguir o: rótulo). Consulte a Fase 7 para obter regras sobre como o GOTO funciona.
        • Se o token: label estiver ausente ou o label: não for encontrado, a pilha de chamadas será imediatamente exibida para restaurar a posição do arquivo salvo e a CALL será abortada.
        • Se o rótulo: contiver / ?, a ajuda do GOTO será impressa em vez de procurar o rótulo:. O ponteiro do arquivo não se move, de modo que o código após a CALL seja executado duas vezes, uma vez no contexto CALL e depois novamente após o retorno da CALL. Consulte Por que o CALL imprime a mensagem de ajuda do GOTO neste script e por que o comando depois é executado duas vezes? para mais informações.
    • Caso contrário, transfira o controle para o script em lote especificado.
    • A execução do rótulo ou script CALLed: continua até que EXIT / B ou final do arquivo seja alcançado; nesse momento, a pilha CALL é exibida e a execução é retomada a partir da posição do arquivo salvo.
      A fase 7 não é executada para scripts CALLed ou: labels.
  • Caso contrário, o resultado da fase 6 entra na fase 7 para execução.

Fase 7) Execute: O comando é executado

  • 7.1 - Executar comando interno - Se o token de comando estiver entre aspas, pule esta etapa. Caso contrário, tente analisar um comando interno e executar.
    • Os seguintes testes são feitos para determinar se um token de comando não citado representa um comando interno:
      • Se o token de comando corresponder exatamente a um comando interno, execute-o.
      • Caso + / [ ] <space> <tab> , ;contrário, quebre o token de comando antes da primeira ocorrência de ou =
        Se o texto anterior for um comando interno, lembre-se desse comando
        • Se estiver no modo de linha de comando ou se o comando for de um bloco entre parênteses, se for verdadeiro ou falso, for FOR, ou for envolvido na concatenação de comandos, execute o comando interno
        • Caso contrário (deve ser um comando independente no modo em lote), verifique a pasta atual e o PATH em busca de um arquivo .COM, .EXE, .BAT ou .CMD cujo nome base corresponda ao token de comando original
          • Se o primeiro arquivo correspondente for .BAT ou .CMD, vá para 7.3.exec e execute esse script
          • Caso contrário (a correspondência não encontrada ou a primeira correspondência é .EXE ou .COM), execute o comando interno lembrado
      • Caso . \contrário, quebre o token de comando antes da primeira ocorrência de ou :
        Se o texto anterior não for um comando interno, salte para 7.2. Caso contrário,
        o texto anterior pode ser um comando interno. Lembre-se deste comando.
      • Quebre o token de comando antes da primeira ocorrência de + / [ ] <space> <tab> , ;ou =
        Se o texto anterior for o caminho para um arquivo existente, vá para 7.2 ou então
        execute o comando interno lembrado.
    • Se um comando interno for analisado a partir de um token de comando maior, a parte não utilizada do token de comando será incluída na lista de argumentos
    • Só porque um token de comando é analisado como um comando interno não significa que ele será executado com êxito. Cada comando interno possui suas próprias regras sobre como os argumentos e as opções são analisados ​​e qual sintaxe é permitida.
    • Todos os comandos internos imprimirão ajuda em vez de desempenhar suas funções se /?forem detectados. A maioria reconhece /?se aparece em algum lugar nos argumentos. Mas alguns comandos, como ECHO e SET, só imprimem ajuda se o primeiro token de argumento começar /?.
    • SET possui algumas semânticas interessantes:
      • Se um comando SET tiver uma cotação antes que o nome da variável e as extensões sejam ativadas
        set "name=content" ignored -> value = content
        , o texto entre o primeiro sinal de igual e a última cotação será usado como conteúdo (primeira igual e última cotação excluídas). O texto após a última citação é ignorado. Se não houver aspas após o sinal de igual, o restante da linha será usado como conteúdo.
      • Se um comando SET não tiver uma citação antes do nome
        set name="content" not ignored -> valor = "content" not ignored
        , todo o restante da linha após o igual será usado como conteúdo, incluindo toda e qualquer citação que possa estar presente.
    • Uma comparação IF é avaliada e, dependendo se a condição é verdadeira ou falsa, o bloco de comando dependente já analisado apropriado é processado, iniciando na fase 5.
    • A cláusula IN de um comando FOR é iterada adequadamente.
      • Se este for um FOR / F que itera a saída de um bloco de comando, então:
        • A cláusula IN é executada em um novo processo de cmd.exe via CMD / C.
        • O bloco de comando deve passar por todo o processo de análise pela segunda vez, mas desta vez em um contexto de linha de comando
        • O ECHO iniciará ON, e a expansão atrasada geralmente iniciará desativada (dependendo da configuração do registro)
        • Todas as alterações de ambiente feitas pelo bloco de comando da cláusula IN serão perdidas quando o processo filho cmd.exe terminar
      • Para cada iteração:
        • Os valores da variável FOR são definidos
        • O bloco de comando DO já analisado é processado, iniciando na fase 4.
    • O GOTO usa a seguinte lógica para localizar o: label
      • O rótulo é analisado a partir do primeiro token de argumento
      • O script é verificado para a próxima ocorrência do rótulo
        • A digitalização começa na posição atual do arquivo
        • Se o final do arquivo for alcançado, a varredura retornará ao início do arquivo e continuará até o ponto inicial original.
      • A digitalização para na primeira ocorrência do rótulo que encontra e o ponteiro do arquivo é definido para a linha imediatamente após o rótulo. A execução do script continua a partir desse ponto. Observe que um GOTO verdadeiro com êxito abortará imediatamente qualquer bloco de código analisado, incluindo os loops FOR.
      • Se o rótulo não for encontrado ou o token do rótulo estiver ausente, o GOTO falhará, uma mensagem de erro será impressa e a pilha de chamadas será exibida. Isso funciona efetivamente como um EXIT / B, exceto que os comandos já analisados ​​no bloco de comando atual que seguem o GOTO ainda são executados, mas no contexto do CALLer (o contexto que existe após EXIT / B)
      • Consulte https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 para obter uma descrição mais precisa das regras usadas para analisar rótulos.
    • RENAME e COPY aceitam curingas para os caminhos de origem e destino. Mas a Microsoft faz um péssimo trabalho ao documentar como os curingas funcionam, especialmente para o caminho de destino. Um conjunto útil de regras de caracteres curinga pode ser encontrado em Como o comando RENAME do Windows interpreta caracteres curinga?
  • 7.2 - Executar alteração de volume - Caso contrário, se o token de comando não começar com uma citação, tiver exatamente dois caracteres e o segundo caractere for dois pontos, altere o volume
    • Todos os tokens de argumento são ignorados
    • Se o volume especificado pelo primeiro caractere não puder ser encontrado, aborte com um erro
    • Um token de comando ::sempre resultará em um erro, a menos que SUBST seja usado para definir um volume para ::
      Se SUBST for usado para definir um volume para ::, então o volume será alterado, não será tratado como um rótulo.
  • 7.3 - Executar comando externo - Caso contrário, tente tratar o comando como um comando externo.
    • Se em modo de linha de comando e o comando não é citado e não se inicia com uma especificação de volume, branco-espaço, ,, ;, =ou +então quebrar o comando token de na primeira ocorrência de <space> , ;ou =e preceder o restante para o argumento símbolo (s).
    • Se o segundo caractere do token de comando for dois pontos, verifique se o volume especificado pelo primeiro caractere pode ser encontrado.
      Se o volume não puder ser encontrado, aborte com um erro.
    • Se no modo de lote e o token de comando começar :, vá para 7.4.
      Observe que se o token do rótulo começar ::, isso não será alcançado porque a etapa anterior terá sido interrompida com um erro, a menos que SUBST seja usado para definir um volume ::.
    • Identifique o comando externo a ser executado.
      • Esse é um processo complexo que pode envolver o volume atual, o diretório atual, a variável PATH, a variável PATHEXT e / ou as associações de arquivos.
      • Se um comando externo válido não puder ser identificado, aborte com um erro.
    • Se no modo de linha de comando e o token de comando começar :, vá para 7.4.
      Observe que isso raramente é alcançado porque a etapa anterior terá sido interrompida com um erro, a menos que o token de comando comece com ::SUBST e SUBST seja usado para definir um volume para ::e o token de comando inteiro é um caminho válido para um comando externo.
    • 7.3.exec - Execute o comando externo.
  • 7.4 - Ignorar um rótulo - Ignore o comando e todos os seus argumentos se o token de comando começar com :.
    As regras em 7.2 e 7.3 podem impedir que um rótulo atinja esse ponto.

Analisador de Linha de Comando:

Funciona como o BatchLine-Parser, exceto:

Fase 1) Porcentagem de expansão:

  • Não %*, %1etc. expansão de argumentos
  • Se var for indefinido, ele %var%permanecerá inalterado.
  • Nenhuma manipulação especial de %%. Se var = content, %%var%%expande para %content%.

Fase 3) Ecoar o (s) comando (s) analisado (s)

  • Isso não é realizado após a fase 2. Somente é executado após a fase 4 para o bloco de comandos FOR DO.

Fase 5) Expansão atrasada: somente se a expansão atrasada estiver ativada

  • Se var for indefinido, ele !var!permanecerá inalterado.

Fase 7) Executar comando

  • Tentativas de CALL ou GOTO a: resultam em um erro.
  • Como já documentado na fase 7, um rótulo executado pode resultar em um erro em diferentes cenários.
    • Os rótulos executados em lote só podem causar um erro se começarem com ::
    • Os rótulos executados pela linha de comando quase sempre resultam em um erro

Análise de valores inteiros

Existem muitos contextos diferentes em que o cmd.exe analisa valores inteiros de seqüências de caracteres e as regras são inconsistentes:

  • SET /A
  • IF
  • %var:~n,m% (expansão de substring variável)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Detalhes para essas regras podem ser encontrados em Regras sobre como o CMD.EXE analisa números


Para quem deseja melhorar as regras de análise do cmd.exe, há um tópico de discussão no fórum do DosTips, no qual os problemas podem ser relatados e as sugestões feitas.

Espero que ajude
Jan Erik (jeb) - Autor original e descobridor de fases
Dave Benham (dbenham) - Muito conteúdo e edição adicionais

dbenham
fonte
4
Olá jeb, obrigado pela sua compreensão ... Pode ser difícil de entender, mas vou tentar pensar sobre isso! Você parece ter realizado muitos testes! Obrigado por traduzir ( administrator.de/… )
Benoit
2
Fase 5 do lote) - %% a já foi alterada para% a na Fase 1, portanto, a expansão do loop for expande realmente% a. Além disso, eu adicionei uma explicação mais detalhada da fase Lote 1 em uma resposta abaixo (Eu não tenho o privilégio Edit)
dbenham
3
Jeb - talvez a fase 0 possa ser movida e combinada com a fase 6? Isso faz mais sentido para mim, ou há uma razão pela qual eles são separados assim?
dbenham
1
@aschipfl - atualizei essa seção. O )realmente funciona quase como um REMcomando quando o contador parêntese é 0. Tente ambos a partir da linha de comando: ) Ignore thiseecho OK & ) Ignore this
dbenham
1
@aschipfl sim, está correto, portanto, às vezes, você vê 'set "var =% expr%"! 'o último ponto de exclamação será removido, mas força a fase 5'
jeb
62

Ao invocar um comando a partir de uma janela de comando, a tokenização dos argumentos da linha de comando não é realizada por cmd.exe(também conhecido como "o shell"). Geralmente, a tokenização é feita pelo tempo de execução C / C ++ dos processos recém-formados, mas isso não é necessariamente o caso - por exemplo, se o novo processo não foi gravado em C / C ++ ou se o novo processo optar por ignorar argve processar a linha de comando bruta para si mesma (por exemplo, com GetCommandLine ()) No nível do sistema operacional, o Windows passa linhas de comando simbolizadas como uma única sequência para novos processos. Isso contrasta com a maioria dos shells * nix, onde o shell simboliza os argumentos de maneira consistente e previsível antes de passá-los para o processo recém-formado. Tudo isso significa que você pode experimentar um comportamento de tokenização de argumento bastante divergente em diferentes programas no Windows, pois programas individuais geralmente levam a tokenização de argumento em suas próprias mãos.

Se parece anarquia, é o que é. No entanto, como um grande número de programas do Windows utiliza o tempo de execução do Microsoft C / C ++ argv, pode ser útil entender como o MSVCRT tokeniza argumentos. Aqui está um trecho:

  • Os argumentos são delimitados por espaço em branco, que é um espaço ou uma guia.
  • Uma cadeia de caracteres entre aspas duplas é interpretada como um único argumento, independentemente do espaço em branco contido nele. Uma cadeia de caracteres citada pode ser incorporada em um argumento. Observe que o sinal de intercalação (^) não é reconhecido como um caractere de escape ou delimitador.
  • Aspas duplas precedidas por uma barra invertida, \ ", são interpretadas como aspas duplas literais (").
  • As barras invertidas são interpretadas literalmente, a menos que precedam imediatamente as aspas duplas.
  • Se um número par de barras invertidas for seguido por aspas duplas, uma barra invertida () será colocada na matriz argv para cada par de barras invertidas (\) e as aspas duplas (") serão interpretadas como um delimitador de seqüência de caracteres.
  • Se um número ímpar de barras invertidas for seguido por aspas duplas, uma barra invertida () será colocada na matriz argv para cada par de barras invertidas (\) e as aspas duplas serão interpretadas como uma sequência de escape pela barra invertida restante, causando aspas duplas literais (") a serem colocadas em argv.

A "linguagem de lote" da Microsoft ( .bat) não é exceção a esse ambiente anárquico e desenvolveu suas próprias regras exclusivas para tokenização e escape. Também parece que o prompt de comando do cmd.exe realiza algum pré-processamento do argumento da linha de comandos (principalmente para substituição e escape de variáveis) antes de passar o argumento para o processo que está sendo executado recentemente. Você pode ler mais sobre os detalhes de baixo nível do idioma do lote e do cmd escapando nas excelentes respostas de jeb e dbenham nesta página.


Vamos criar um simples utilitário de linha de comando em C e ver o que diz sobre seus casos de teste:

int main(int argc, char* argv[]) {
    int i;
    for (i = 0; i < argc; i++) {
        printf("argv[%d][%s]\n", i, argv[i]);
    }
    return 0;
}

(Notas: argv [0] é sempre o nome do executável e é omitido abaixo por questões de brevidade. Testado no Windows XP SP3. Compilado com o Visual Studio 2005.)

> test.exe "a ""b"" c"
argv[1][a "b" c]

> test.exe """a b c"""
argv[1]["a b c"]

> test.exe "a"" b c
argv[1][a" b c]

E alguns dos meus próprios testes:

> test.exe a "b" c
argv[1][a]
argv[2][b]
argv[3][c]

> test.exe a "b c" "d e
argv[1][a]
argv[2][b c]
argv[3][d e]

> test.exe a \"b\" c
argv[1][a]
argv[2]["b"]
argv[3][c]
Mike Clark
fonte
Obrigado pela sua resposta. Isso me intriga ainda mais ao ver que o TinyPerl não produzirá o que o seu programa produz, e eu tenho dificuldades para entender como [a "b" c]poderia se tornar o [a "b] [c]pós-processamento.
Benoit
Agora que penso nisso, essa tokenização da linha de comando provavelmente é feita inteiramente pelo tempo de execução C. Um executável pode ser escrito de tal forma que nem use o tempo de execução C; nesse caso, acho que teria que lidar com a linha de comando literalmente e ser responsável por fazer sua própria tokenização (se quisesse). se seu aplicativo usa o tempo de execução C, você pode optar por ignorar argc e argv e obter a linha de comando bruta via, por exemplo, Win32 GetCommandLine. Talvez o TinyPerl esteja ignorando o argv e simplesmente tokenizando a linha de comando bruta por suas próprias regras.
Mike Clark
4
"Lembre-se de que, do ponto de vista do Win32, a linha de comando é apenas uma string que é copiada no espaço de endereço do novo processo. Como o processo de inicialização e o novo processo interpretam essa string são governados não por regras, mas por convenções." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark
2
Obrigado por essa resposta realmente agradável. Isso explica muito na minha opinião. E isso também explica por que às vezes acho isso realmente ruim para trabalhar com o Windows ...
Benoit
Eu achei isso sobre barras invertidas e aspas durante a transformação da linha de comando para o argv, para programas Win32 C ++. A contagem de barras invertidas é dividida apenas por dois quando a última barra invertida é seguida por um dblquote, e o dblquote termina uma string quando há um número par de barras invertidas antes.
Benoit
47

Regras de porcentagem de expansão

Aqui está uma explicação expandida da Fase 1 na resposta do jeb (válida para o modo em lote e o modo de linha de comando).

Fase 1) Porcentagem de expansão A partir da esquerda, procure cada caractere em busca de %ou <LF>. Se encontrado, então

  • 1.05 (linha truncada em <LF>)
    • Se o personagem é <LF>então
      • Solte (ignore) o restante da linha a <LF>partir de
      • Ir para a Fase 1.5 (Faixa <CR>)
    • Caso contrário, o personagem deve ser %, então vá para 1.1
  • 1.1 (escape %) ignorado se o modo de linha de comando
    • Se o modo em lote for seguido por outro %,
      substitua %%por um %e continue a digitalização
  • 1.2 (argumento de expansão) pulado se o modo de linha de comando
    • Caso contrário, se o modo em lote for
      • Se as *extensões seguidas e de comando estiverem ativadas,
        substitua %*pelo texto de todos os argumentos da linha de comando (Substitua por nada se não houver argumentos) e continue a varredura.
      • Else if seguido por <digit>, em seguida,
        substituir %<digit>com valor de argumento (substitua com nada se não for definido) e continuar digitalização.
      • Caso contrário, se as ~extensões seguidas e de comando estiverem ativadas,
        • Se seguido pela lista válida opcional de modificadores de argumento seguida por necessário <digit>,
          substitua %~[modifiers]<digit>pelo valor do argumento modificado (substitua por nada se não estiver definido ou se especificado $ PATH: o modificador não estiver definido) e continue a varredura.
          Nota: os modificadores não diferenciam maiúsculas de minúsculas e podem aparecer várias vezes em qualquer ordem, exceto $ PATH: o modificador pode aparecer apenas uma vez e deve ser o último modificador antes do<digit>
        • Outra sintaxe de argumento modificada inválida gera erro fatal: Todos os comandos analisados ​​são abortados e o processamento em lote é interrompido se estiver no modo em lote!
  • 1.3 (variável de expansão)
    • Caso contrário, se as extensões de comando estiverem desabilitadas,
      observe a próxima sequência de caracteres, quebrando antes %ou no final do buffer e chame-os de VAR (pode ser uma lista vazia)
      • Se no próximo personagem é %, em seguida,
        • Se VAR estiver definido,
          substitua %VAR%pelo valor de VAR e continue a varredura
        • Caso contrário, em modo de lote,
          remova %VAR%e continue a digitalização
        • Mais ir para 1.4
      • Mais ir para 1.4
    • Caso contrário, se as extensões de comando estiverem ativadas, verifique
      a próxima sequência de caracteres, quebrando antes % :ou no final do buffer, e chame-os de VAR (pode ser uma lista vazia). Se o VAR quebrar antes :e o caractere subsequente for %incluído :como o último caractere no VAR e quebrar antes %.
      • Se no próximo personagem é %, em seguida,
        • Se VAR estiver definido,
          substitua %VAR%pelo valor de VAR e continue a varredura
        • Caso contrário, em modo de lote,
          remova %VAR%e continue a digitalização
        • Mais ir para 1.4
      • Else if próximo caractere é :então
        • Se VAR for indefinido, então
          • Se o modo for lote,
            remova %VAR:e continue a digitalização.
          • Mais ir para 1.4
        • Else if próximo caractere é ~então
          • Se a próxima sequência de caracteres corresponder ao padrão de [integer][,[integer]]%,
            substitua %VAR:~[integer][,[integer]]%por substring do valor de VAR (possivelmente resultando em sequência vazia) e continue a varredura.
          • Mais ir para 1.4
        • Else if seguido por =ou *=então
          busca variável inválido e substituir sintaxe levanta erro fatal: Todos analisado comandos são abortados, e processamento em lote aborta se na modalidade de grupo!
        • Caso contrário, se a próxima sequência de caracteres corresponder ao padrão de [*]search=[replace]%, onde a pesquisa pode incluir qualquer conjunto de caracteres =, exceto , e replace pode incluir qualquer conjunto de caracteres %, exceto ,
          Substitua %VAR:[*]search=[replace]%pelo valor de VAR após executar a pesquisa e a substituição (possivelmente resultando em uma sequência vazia) e continue Varredura
        • Mais ir para 1.4
  • 1,4 (faixa%)
    • Caso contrário, em lote,
      remova %e continue a digitalização começando com o próximo caractere após o%
    • Caso contrário, preserve a liderança %e continue a varredura começando com o próximo caractere após a liderança preservada%

O texto acima ajuda a explicar por que esse lote

@echo off
setlocal enableDelayedExpansion
set "1var=varA"
set "~f1var=varB"
call :test "arg1"
exit /b  
::
:test "arg1"
echo %%1var%% = %1var%
echo ^^^!1var^^^! = !1var!
echo --------
echo %%~f1var%% = %~f1var%
echo ^^^!~f1var^^^! = !~f1var!
exit /b

Dá estes resultados:

%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB

Nota 1 - A fase 1 ocorre antes do reconhecimento das instruções REM. Isso é muito importante porque significa que mesmo uma observação pode gerar um erro fatal se tiver sintaxe de expansão de argumento inválida ou pesquisa de variável inválida e substituir a sintaxe!

@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached

Nota 2 - Outra consequência interessante das regras de análise de%: Variáveis ​​que contêm: no nome podem ser definidas, mas não podem ser expandidas, a menos que as extensões de comando estejam desabilitadas. Há uma exceção - um nome de variável que contém dois pontos no final pode ser expandido enquanto as extensões de comando estão ativadas. No entanto, você não pode executar substring ou procurar e substituir operações em nomes de variáveis ​​que terminam com dois pontos. O arquivo em lotes abaixo (cortesia da jeb) demonstra esse comportamento

@echo off
setlocal
set var=content
set var:=Special
set var::=double colon
set var:~0,2=tricky
set var::~0,2=unfortunate
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
echo Now with DisableExtensions
setlocal DisableExtensions
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%

Nota 3 - Um resultado interessante da ordem das regras de análise que jeb apresenta em seu post: Ao executar localizar e substituir por expansão atrasada, caracteres especiais nos termos de localização e substituição devem ser escapados ou citados. Mas a situação é diferente para a expansão percentual - o termo de busca não deve ser escapado (embora possa ser citado). A porcentagem de substituição de string pode ou não exigir escape ou citação, dependendo da sua intenção.

@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"

Regras de expansão atrasada

Aqui está uma explicação expandida e mais precisa da fase 5 na resposta de jeb (válida para o modo em lote e o modo de linha de comando)

Fase 5) Expansão atrasada

Essa fase é ignorada se qualquer uma das seguintes condições se aplicar:

  • A expansão atrasada está desativada.
  • O comando está dentro de um bloco entre parênteses em ambos os lados de um tubo.
  • O token de comando recebido é um script em lote "nu", o que significa que não está associado a um CALLbloco entre parênteses, a qualquer forma de concatenação de comando ( &, &&ou ||) ou a um canal |.

O processo de expansão atrasada é aplicado aos tokens independentemente. Um comando pode ter vários tokens:

  • O token de comando. Para a maioria dos comandos, o próprio nome do comando é um token. Mas alguns comandos têm regiões especializadas que são consideradas um TOKEN para a fase 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, Em que a comparação é um de ==, equ, neq, lss, leq, gtr, ougeq
  • O token de argumentos
  • O token de destino do redirecionamento (um por redirecionamento)

Nenhuma alteração é feita nos tokens que não contêm !.

Para cada token que contém pelo menos um !, verifique cada caractere da esquerda para a direita em busca de ^ou !, e se encontrado,

  • 5.1 (escape de cursor) Necessário !ou ^literais
    • Se o personagem é um sinal de intercalação, ^então
      • Remova o ^
      • Digitalize o próximo caractere e preserve-o como um literal
      • Continue a verificação
  • 5.2 (variável de expansão)
    • Se o caractere é !, então
      • Se as extensões de comando estiverem desativadas,
        observe a próxima sequência de caracteres, quebrando antes !ou <LF>e chame-os de VAR (pode ser uma lista vazia)
        • Se no próximo personagem é !, em seguida,
          • Se VAR estiver definido,
            substitua !VAR!pelo valor de VAR e continue a varredura
          • Caso contrário, em modo de lote,
            remova !VAR!e continue a digitalização
          • Caso contrário, vá para 5.2.1
        • Caso contrário, vá para 5.2.1
      • Else if extensões de comando estão habilitados, em seguida,
        procure na próxima sequência de caracteres, quebrando antes !, :ou <LF>, e chamá-los VAR (pode ser uma lista vazia). Se o VAR quebrar antes :e o caractere subsequente for !incluído :como o último caractere no VAR e quebrar antes!
        • Se no próximo personagem é !, em seguida,
          • Se existir VAR,
            substitua !VAR!pelo valor VAR e continue a varredura
          • Caso contrário, em modo de lote,
            remova !VAR!e continue a digitalização
          • Caso contrário, vá para 5.2.1
        • Else if próximo caractere é :então
          • Se VAR for indefinido, então
            • Se o modo em lote,
              remova !VAR:e continue a digitalização
            • Caso contrário, vá para 5.2.1
          • Else if próximo caractere é ~então
            • Se a próxima sequência de caracteres corresponder ao padrão de [integer][,[integer]]!, substitua !VAR:~[integer][,[integer]]!por substring do valor de VAR (possivelmente resultando em sequência vazia) e continue a varredura.
            • Caso contrário, vá para 5.2.1
          • Caso contrário, se a próxima sequência de caracteres corresponder ao padrão de [*]search=[replace]!, onde a pesquisa pode incluir qualquer conjunto de caracteres =, exceto , e replace pode incluir qualquer conjunto de caracteres !, exceto ,
            Substitua !VAR:[*]search=[replace]!pelo valor de VAR após executar a pesquisa e a substituição (possivelmente resultando em uma sequência vazia) e continuar a digitalização
          • Caso contrário, vá para 5.2.1
        • Caso contrário, vá para 5.2.1
      • 5.2.1
        • Se o modo de lote remover o !
          Else principal, preserve o líder!
        • Continue a varredura iniciando com o próximo caractere após o início preservado !
dbenham
fonte
3
+1, somente a sintaxe cólon e regras estão faltando aqui para %definedVar:a=b%vs %undefinedVar:a=b%e as %var:~0x17,-010%formas
jeb
2
Bom ponto - ampliei a seção de expansão variável para atender às suas preocupações. Também expandi a seção de expansão de argumentos para preencher alguns detalhes ausentes.
dbenham
2
Depois de obter alguns comentários particulares adicionais da jeb, adicionei uma regra para nomes de variáveis ​​que terminam com dois pontos e adicionei a nota 2. Também adicionei a nota 3 simplesmente porque achei interessante e importante.
dbenham
1
@aschipfl - Sim, eu considerei entrar em mais detalhes sobre isso, mas não queria ir pelo buraco do coelho. Fiquei intencionalmente sem compromisso quando usei o termo [número inteiro]. Há mais informações em Regras sobre como o CMD.EXE analisa números .
dbenham
1
Estou sentindo falta das regras de expansão para o contexto cmd, assim não há caracteres reservados para o primeiro caractere do nome da variável como %<digit>, %*ou %~. E o comportamento muda para variáveis ​​indefinidas. Talvez você precisa para abrir uma segunda resposta
jeb
7

Conforme indicado, os comandos passam toda a cadeia de argumentos em μSoft land, e cabe a eles analisá-los em argumentos separados para seu próprio uso. Não há consistência nisso entre diferentes programas e, portanto, não há um conjunto de regras para descrever esse processo. Você realmente precisa verificar cada caixa de canto para qualquer biblioteca C que seu programa use.

No que diz respeito aos .batarquivos do sistema , aqui está esse teste:

c> type args.cmd
@echo off
echo cmdcmdline:[%cmdcmdline%]
echo 0:[%0]
echo *:[%*]
set allargs=%*
if not defined allargs goto :eof
setlocal
@rem Wot about a nice for loop?
@rem Then we are in the land of delayedexpansion, !n!, call, etc.
@rem Plays havoc with args like %t%, a"b etc. ugh!
set n=1
:loop
    echo %n%:[%1]
    set /a n+=1
    shift
    set param=%1
    if defined param goto :loop
endlocal

Agora podemos executar alguns testes. Veja se você consegue descobrir exatamente o que a μSoft está tentando fazer:

C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]

Tudo bem até agora. (Vou deixar de fora o desinteressante %cmdcmdline%e %0de agora em diante.)

C>args *.*
*:[*.*]
1:[*.*]

Nenhuma expansão de nome de arquivo.

C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]

Nenhuma remoção de cotação, embora as aspas impeçam a divisão de argumentos.

c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]

As aspas duplas consecutivas fazem com que percam quaisquer habilidades especiais de análise que possam ter. @ Exemplo de Beniot:

C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]

Quiz: Como você passa o valor de qualquer ambiente var como um único argumento (ou seja, como %1) para um arquivo bat?

c>set t=a "b c
c>set t
t=a "b c
c>args %t%
1:[a]
2:["b c]
c>args "%t%"
1:["a "b]
2:[c"]
c>Aaaaaargh!

A análise sã parece interrompida para sempre.

Para o seu entretenimento, tente adicionar diversos ^, \, ', &(& c.) Caracteres a estes exemplos.

bobbogo
fonte
Para passar% t% como argumento único, você pode usar "% t:" = \ "%" Ou seja, use a sintaxe% VAR: str =% de substituição para expansão variável. Metacaracteres do shell como | e & na variável conteúdos podem ainda ser expostos e atrapalhar o shell, porém, a menos que você escapar-los novamente ....
Brutão
@Toughy Então, no meu exemplo, té a "b c. Você tem uma receita para obter esses 6 caracteres ( a, 2 × espaço, ", b, e c) para aparecer como %1dentro de um .cmd? Eu gosto do seu pensamento embora. args "%t:"=""%"é muito perto :-)
bobbogo
5

Você já tem ótimas respostas acima, mas para responder uma parte da sua pergunta:

set a =b, echo %a %b% c% → bb c%

O que está acontecendo lá é que, como você tem um espaço antes de =, uma variável é criada, chamada %a<space>% quando você echo %a %é avaliado corretamente como b.

A parte restante b% c%é então avaliada como texto simples + uma variável indefinida % c%, o que deve ser feito eco como digitado, para mim echo %a %b% c%retornosbb% c%

Suspeito que a capacidade de incluir espaços nos nomes de variáveis ​​seja mais uma supervisão do que um 'recurso' planejado

SS64
fonte
0

editar: veja a resposta aceita, o que segue está errado e explica apenas como passar uma linha de comando para o TinyPerl.


Em relação a aspas, tenho a sensação de que o comportamento é o seguinte:

  • quando a "é encontrado, o globbing das cordas começa
  • quando o globbing da corda ocorre:
    • todo personagem que não "é um é globbed
    • quando a "é encontrado:
      • se for seguido por ""(portanto, um triplo "), uma aspas duplas será adicionada à string
      • se for seguido por "(assim, um duplo "), uma aspas dupla será adicionada às cordas e às extremidades das cordas
      • se o próximo caractere não for ", o globbing da corda termina
    • quando a linha termina, o globbing da corda termina.

Em resumo:

"a """ b "" c"""consiste em duas strings: a " b "ec"

"a"", "a"""e "a""""são todos da mesma sequência se no final de uma linha

Benoit
fonte
o tokenizer e o globbing da string dependem do comando! A "set" funciona diferente, então uma "chamada" ou mesmo um "se"
jeb
sim, mas e os comandos externos? Eu acho que cmd.exe sempre passa os mesmos argumentos para eles?
Benoit
1
O cmd.exe sempre passa o resultado da expansão como uma seqüência de caracteres, não os tokens para um comando externo. Depende do comando externo como escapar e tokenizar isso, usos FINDSTR barra invertida a próxima pode-se usar qualquer outra coisa
jeb
0

Observe que a Microsoft publicou o código-fonte do seu terminal. Pode funcionar de maneira semelhante à linha de comando em relação à análise de sintaxe. Talvez alguém esteja interessado em testar as regras de análise de engenharia reversa de acordo com as regras de análise do terminal.

Link para o código fonte.

user7427029
fonte