Échappement des guillemets doubles dans le Script Batch

Comment pourrais-je remplacer tous les guillemets doubles dans les paramètres de mon fichier batch par des guillemets doubles échappés? C'est mon fichier batch actuel, qui développe tous ses paramètres de ligne de commande à l'intérieur de la chaîne:

@echo off
call bash --verbose -c "g++-linux-4.1 %*"

Il utilise ensuite cette chaîne pour faire un appel au bash de Cygwin, en exécutant un compilateur croisé Linux. Malheureusement, je reçois des paramètres comme ceux-ci transmis à mon fichier batch:

"launch-linux-g++.bat" -ftemplate-depth-128 -O3 -finline-functions 
-Wno-inline -Wall  -DNDEBUG   -c 
-o "C:UsersMeDocumentsTestingSparseLibbinWin32LinuxReleasehello.o" 
"c:UsersMeDocumentsTestingSparseLibSparseLibhello.cpp"

Où la première citation autour du premier chemin passé est prématurée mettre fin à la chaîne transmise à GCC, et passer le reste des paramètres directement à bash (qui échoue spectaculairement.)

J'imagine que si je peux concaténer les paramètres en une seule chaîne, puis échapper aux guillemets, cela devrait bien fonctionner, mais j'ai du mal à déterminer comment faire cela. Personne ne sait?

71
demandé sur eplawless 2009-02-18 20:19:38

4 réponses

Le caractère d'échappement dans les scripts batch est ^. Mais pour les chaînes entre guillemets doubles, doublez les guillemets:

"string with an embedded "" character"
84
répondu Eclipse 2009-02-18 17:31:53

La propre réponse d'Eplawless résout simplement et efficacement son problème spécifique: elle remplace toutes les instances " de la liste entière des arguments par \", ce qui explique comment Bash nécessite des guillemets doubles dans une chaîne à guillemets doubles.

Pour répondre généralement à la question de comment échapper des guillemets doubles dans une chaîne entre guillemets doubles en utilisant cmd.exe, l'interpréteur de ligne de commande Windows (que ce soit sur la ligne de commande-souvent encore appelé par erreur le "Invite DOS" - ou dans un fichier batch): voir en bas pour un coup d'oeil à PowerShell.

Tl;dr:

  • Vous doit utiliser "" lors du passage d'une chaîne de caractères pour un(autre) fichier de commandes et vous peut - utiliser "", avec les applications créées avec Microsoft's en C/C++/.NET compilateurs (qui aussi accepter \"), qui sur Windows comprend Python et le Nœud.js :

    • Exemple: foo.bat "We had 3"" of rain."

    • Ce qui suit s'applique uniquement aux fichiers batch:

      • "" est le seul moyen d'obtenir l'interpréteur de commandes (cmd.exe) pour traiter toute la chaîne entre guillemets comme un argument unique .

      • Malheureusement, non seulement les guillemets doubles sont-ils conservés (comme d'habitude), mais aussi les guillemets doubles échappés, donc l'obtention de la chaîne prévue est en deux étapes processus; par exemple, en supposant que la chaîne entre guillemets doubles est passée en tant que 1er argument, %1:

      • set "str=%~1" supprime l'affichage de guillemets; set "str=%str:""="%", puis convertit le doublé les guillemets pour simples.
        Assurez-vous d'utiliser les guillemets doubles entourant les parties d'affectation pour éviter une interprétation indésirable des valeurs.

  • \" est nécessaire - comme la seule option par beaucoup d'autres programmes , (par exemple, Ruby, Perl, et même le propre PowerShell de Microsoft(!)), mais son utilisation N'est pas sûre :

    • {[7] } est ce que de nombreux exécutables et interprètes exigent - y compris le propre PowerShell de Microsoft lorsque les chaînes sont passées de l'extérieur - ou, dans le cas des compilateurs Microsoft, prennent en charge comme alternative à "" - en fin de compte, cependant, c'est au programme cible d'analyser l'argument liste.
    • Exemple: foo.exe "We had 3\" of rain."
    • cependant, L'utilisation de \" peut entraîner une exécution arbitraire et non désirée de commandes et/ou des REDIRECTIONS D'entrée / sortie :
      • Les caractères suivants présentent ce risque: & | < >
      • par exemple, les résultats suivants dans l'exécution involontaire de la commande ver; voir plus loin pour une explication et le point suivant pour un contournement:
        • foo.exe "3\" of snow" "& ver."
    • Pour PowerShell sur Windows seulement, \"" est une solide alternative.
  • Si vous devez utiliser \", Il n'y a que 3approches sûres , qui sont cependant assez lourdes: La Pointe du chapeau à T s pour son aide.

    • Utilisation (éventuellement selective ) expansion de variable retardée dans votre fichier batch, vous pouvez stocker le littéral \" dans une variable et référencer cette variable dans une chaîne "..." en utilisant la syntaxe !var! - voir la réponse utile de T S.

      • L'approche ci-dessus, en dépit d'être lourd, a l'avantage que vous pouvez appliquer méthodiquement et qu'il fonctionne robuste, avec tout entrée.
    • Seulement avec des chaînes littérales-celles qui n'impliquent pas de VARIABLES-obtenez-vous une approche méthodique similaire: catégoriquement ^ - escape all cmd.exe métacaractères: " & | < > et-si vous souhaitez également supprimer l'expansion des variables- %:
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • Sinon, vous devez formuler votre chaîne en reconnaissant quelles parties de la chaîne cmd.exe considère non cotées en raison de mauvaise interprétation \" comme délimiteurs de fermeture:

      • Dans les portions littérales contenant des métacharactères shell: ^ - Les échapper; en utilisant l'exemple ci-dessus, c'est & qui doit être ^-échappé:
        foo.exe "3\" of snow" "^& ver."

      • In portions with %...%-style variable references: assurez - vous que cmd.exe les considère comme faisant partie d'une chaîne "..." et que les valeurs des variables ne sont pas elles-mêmes intégrées, déséquilibrées citations - , qui n'est même pas toujours possible.

Pour des informations générales, lisez la suite.


Contexte

Note: Ceci est basé sur mes propres expériences. Ne laissez-moi savoir si je me trompe.

Les shells de type POSIX tels que Bash sur les systèmes de type Unix marquent la liste d'arguments (chaîne) avant de passer les arguments individuellement au programme cible: entre autres extensions, ils divisez la liste d'arguments en mots individuels (fractionnement de mots) et supprimez les caractères de citation des mots résultants (suppression de citation). Ce que le programme cible est remis est conceptuellement un tableau d'arguments individuels avec des guillemets (requis par la syntaxe) supprimés.

En revanche, l'interpréteur de commandes Windows ne tokenise apparemment pas la liste d'arguments et passe simplement la chaîne unique comprenant tous les arguments - y compris les caractères citant. - la la cible du programme.
Cependant, certains prétraitement a lieu avant que la chaîne unique est passée au programme cible: ^ caractères d'échappement. en dehors des chaînes entre guillemets doubles sont supprimés (ils échappent au caractère suivant.), et les références de variables (par exemple, %USERNAME%) sont interpolées en premier.

Ainsi, contrairement à Unix, il incombe au programme cible d'analyser la chaîne d'arguments et de la décomposer en arguments individuels avec des guillemets retiré. Ainsi, différents programmes peuvent hypothétiquement nécessiter des méthodes d'échappement différentes et Il n'y a pas de mécanisme d'échappement unique qui soitgaranti pour fonctionner avec tous les programmes - https://stackoverflow.com/a/4094897/45375 contient un excellent arrière-plan sur l'anarchie qui est L'analyse de ligne de commande Windows.

En pratique, \" est très commun, mais pas sûr , comme mentionné ci-dessus:

Depuis cmd.exe lui-même ne reconnaît pas \" comme un échappé guillemet double, il peut mal interpréter les jetons ultérieurs sur la ligne de commande comme unquoted et potentiellement les interpréter comme commandes et/ou redirections d'entrée/sortie.
en un mot: les surfaces de problème, si l'un des caractères suivants suivent une ouverture ou déséquilibrée \": & | < >; par exemple:

foo.exe "3\" of snow" "& ver."

cmd.exe voit les jetons suivants, résultant de mauvaise interprétation de \" comme une double citation régulière:

  • "3\"
  • of
  • snow" "
  • reste: & ver.

Puisque cmd.exe pense que & ver. est non coté , Il l'interprète comme & (l'opérateur de séquençage de commande), suivi du nom d'une commande à exécuter (ver.-Le . est ignoré; ver rapporte les informations de version de cmd.exe).
L'effet global est:

  • tout d'abord, foo.exe est invoqué avec le premier 3 jetons seulement.
  • Ensuite, la commande ver est exécuté.

Même dans les cas où la commande accidentelle ne fait aucun mal, votre commande globale ne fonctionnera pas comme prévu, étant donné que tous les arguments ne lui sont pas transmis.

De nombreux compilateurs / interprètes reconnaissent seulement \" - par exemple, le compilateur GNU C/C++, Python, Perl, Ruby, même le propre PowerShell de Microsoft lorsqu'il est invoqué à partir de cmd.exe - et, à l'exception de PowerShell avec \"", pour eux il n'y a pas de solution simple à ce problème.
Essentiellement, vous devez savoir à l'avance quelles parties de votre ligne de commande sont mal interprétées comme non citées, et sélectivement ^-échapper toutes les instances de & | < > dans ces parties.

En revanche, l'utilisation de {[9] } est sûre , mais n'est malheureusement prise en charge que par les exécutables et les fichiers batch basés sur le compilateur Microsoft (dans le cas des fichiers batch, avec les bizarreries discutées ci-dessus).

Par contrast, PowerShell , lorsqu'il est invoqué depuis l'extérieur - par exemple, depuis cmd.exe, que ce soit depuis la ligne de commande ou un fichier batch - reconnaît uniquement \" et, sur Windows, le plus robuste \"", même si en interne PowerShell utilise {[76] } comme caractère d'échappement dans les chaînes entre guillemets doubles et accepte également ""; par exemple:

  • powershell -c " \"ab c\".length" œuvres produits (4), comme le fait le plus robuste
    powershell -c " \""ab c\"".length",

  • Mais powershell -c " ""ab c"".length" les pauses.


Informations connexes

  • ^ ne peut être utilisé comme caractère d'échappement dans non cotées cordes - intérieur double-chaînes entre guillemets, ^ n'est pas spécial et traité comme un littéral.

    • mise en garde: L'utilisation de ^ dans les paramètres passés à l'instruction call est interrompue (ceci s'applique aux deux utilisations de call: appel d'un autre fichier batch ou binaire, et appel d'un sous-programme dans le même fichier batch):
      • ^ les instances dans les valeurs entre guillemets doubles sont inexplicablement doublées, modification de la valeur transmise: par exemple, si la variable %v% contient une valeur littérale a^b, call :foo "%v%" assigne "a^^b" (!) à %1 (le premier paramètre) dans le sous-programme :foo.
      • non cotées utiliser ^ avec call est cassé au total en ce que ^ ne peut plus être utilisé pour échapper les caractères spéciaux : par exemple, call foo.cmd a^&b se casse tranquillement (au lieu de passer le littéral a&b aussi foo.cmd, comme ce serait le cas sans call) - foo.cmd n'est même jamais invoqué(!), au moins sur Windows 7.
  • L'échappement d'un littéral % est un cas particulier, malheureusement, qui nécessite une syntaxe distincte selon qu'une chaîne est spécifiée sur la commande ligne vs à l'intérieur d'un fichier de commandes; voir https://stackoverflow.com/a/31420292/45375

    • le court de celui-ci: dans un fichier batch, utilisez %%. Sur la ligne de commande, % ne peut pas être échappé, mais si vous placez un ^ Au début, à la fin ou à l'intérieur d'un nom de variable dans une chaînenon cotée (par exemple, echo %^foo%), vous pouvez empêcher l'expansion de variable (interpolation); % les instances sur la ligne de commande qui ne font pas traités comme littéraux (par exemple, 100%).
  • Généralement, pour travailler en toute sécurité avec des valeurs variables pouvant contenir des espaces et des caractères spéciaux :

    • Affectation: Placez deux le nom de la variable et la valeur dans célibataire paire de guillemets; par exemple, set "v=a & b" attribue de la valeur littérale a & b de la variable %v% (par contraste, set v="a & b" rendrait les guillemets partie de la valeur). Échapper littéral % instances comme %% (fonctionne uniquement dans les fichiers batch-voir ci-dessus).
    • référence: références variables à guillemets doubles pour s'assurer que leur valeur n'est pas interpolée; par exemple, echo "%v%" ne soumet pas la valeur de %v% à l'interpolation et imprime "a & b" (mais notez que les guillemets doubles sont invariablement imprimés aussi). En revanche, echo %v% passe littéral a à echo, interprète {[39] } Comme l'opérateur de séquençage de commande, et tente donc de exécutez une commande nommée b.
      Notez également la mise en garde ci-dessus réutilisation de ^ avec l'instruction call.
    • les programmes externes prennent généralement soin de supprimer les guillemets doubles entourant les paramètres, mais, comme indiqué, dans les fichiers batch, vous devez le faire vous-même (par exemple, %~1 pour supprimer les guillemets doubles du 1er paramètre) et, malheureusement, Il n'y a aucun moyen direct que je connaisse d'obtenir echo pour imprimer une valeur inclure des guillemets doubles .
      • Neil propose une solution de contournement basée sur for qui fonctionne tant que la valeur n'a pas de guillemets doubles intégrés; par exemple:
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe ne reconnaît-il pas single - guillemets comme délimiteurs de chaînes - ils sont traités comme des littéraux et ne peuvent généralement pas être utilisés pour délimiter des chaînes avec des espaces intégrés; en outre, il s'ensuit que les jetons adjacents aux guillemets simples et tous les jetons intermédiaires sont traités comme non cités par cmd.exe et interprétés en conséquence.

    • cependant, étant donné que les programmes cibles effectuent finalement leur propre analyse des arguments, certains programmes tels que Ruby reconnaissent les chaînes entre guillemets simples même sous Windows; en revanche, les exécutables C/C++, Perl et Python ne les reconnaissent pas .
      Même s'il est soutenu par le programme cible, cependant, il n'est pas conseillé d'utiliser des guillemets simples chaînes, étant donné que leur contenu n'est pas protégé contre une interprétation potentiellement indésirable par cmd.exe.

PowerShell

Windows PowerShell est un shell beaucoup plus avancé que cmd.exe, et il fait partie de Windows depuis de nombreuses années (et PowerShell Core a également apporté L'expérience PowerShell à macOS et Linux).

PowerShell fonctionne de manière cohérente en interne en ce qui concerne citation:

  • dans les chaînes entre guillemets doubles, utilisez `" ou "" pour échapper aux guillemets doubles
  • dans les chaînes entre guillemets simples, utilisez '' pour échapper aux guillemets simples

Cela fonctionne sur la ligne de commande PowerShell et lors du passage de paramètres aux scripts ou fonctions PowerShell à partir de dans PowerShell.

(comme discuté ci-dessus, passer une citation double échappée à PowerShell de l'extérieur nécessite \" ou, plus robustement, \"" - rien d'autre ne fonctionne).

Malheureusement, lorsque vous invoquez des programmes externes , vous devez à la fois accepter les propres règles de citation de PowerShell et pour échapper au programme cible {[163]:

ce comportement problématique est également discuté et résumé dans ce problème de docs GitHub

Double-citations à l'intérieur de double-cité les chaînes de:

Considérons la chaîne "3`" of rain", que PowerShell-traduit en interne en littéral 3" of rain.

Si vous voulez passer cette chaîne à un programme externe, vous devez appliquer l'échappement du programme cible en plus à de PowerShell; disons que vous voulez passer la chaîne à un programme C, qui s'attend à ce que les guillemets doubles intégrés soient échappés comme \":

foo.exe "3\`" of rain"

Notez comment les deux `" - pour faire PowerShell heureux - et le \ - pour rendre le programme cible heureux - doit être présent.

La même logique s'applique à l'appel d'un fichier batch, où "" doit être utilisé:

foo.bat "3`"`" of rain"

En revanche, l'intégration de seul-citations dans un double-chaîne entre guillemets nécessite pas de s'échapper à tout.

Seul-citations à l'intérieur de célibataire-chaînes entre guillemets do pas exiger extra - échapper; considérons '2'' of snow', qui est la représentation de PowerShell de 2' of snow.

foo.exe '2'' of snow'
foo.bat '2'' of snow'

PowerShell traduit les chaînes entre guillemets simples en chaînes entre guillemets doubles avant de les transmettre au programme cible.

Cependant, double-citations à l'intérieur de célibataire-chaînes entre guillemets, qui n'ont pas besoin de s'échapper pour PowerShell, n'ont encore besoin d'être échappé de la programme cible:

foo.exe '3\" of rain'
foo.bat '3"" of rain'

PowerShell v3 introduit l' magie --% option , appelée stop-parsing symbol, qui soulage une partie de la douleur, en passant quelque chose après uninterpreted au programme cible, sauf pour les références cmd.exe-style environment-variable (par exemple, %USERNAME%), qui sont expanded; par exemple:

foo.exe --% "3\" of rain" -u %USERNAME%

Notez comment échapper le " incorporé comme \" pour le programme cible uniquement (et pas aussi pour PowerShell comme \`") est suffisant.

Cependant, cette approche:

  • ne permet pas l'échappement de % caractères afin d'éviter les expansions de variables d'environnement.
  • empêche l'utilisation directe des variables et expressions PowerShell; à la place, la ligne de commande doit être construite dans une variable de chaîne dans une première étape, puis invoquée avec Invoke-Expression dans une seconde.

Ainsi, malgré ses nombreux progrès, PowerShell n'a pas rendu l'évasion beaucoup plus facile lors de l'appel de programmes externes. Il a cependant introduit un support pour les chaînes entre guillemets simples.

Je me demande s'il est fondamentalement possible dans le monde Windows de passer au modèle Unix de laisser le shell faire toute la tokenisation et la suppression des citations de manière prévisible, à l'avant, indépendamment du programme cible, puis appelez le programme cible en passant les jetons résultants.

59
répondu mklement0 2018-10-02 02:21:13

Google a finalement trouvé la réponse. La syntaxe pour le remplacement de chaîne dans le lot est la suivante:

set v_myvar=replace me
set v_myvar=%v_myvar:ace=icate%

Qui produit des "répliquer moi". Mon script ressemble maintenant à ceci:

@echo off
set v_params=%*
set v_params=%v_params:"=\"%
call bash -c "g++-linux-4.1 %v_params%"

Qui remplace toutes les instances de " par \", correctement échappé pour bash.

20
répondu eplawless 2009-02-18 17:48:17

En complément de l'excellente réponse de mklement0 :

Presque tous les exécutables acceptent \" comme un " échappé. Une utilisation sûre dans cmd est cependant presque seulement possible en utilisant DELAYEDEXPANSION.
Pour envoyer explicitement un littéral " à un processus, affectez \" à une variable d'environnement, puis utilisez cette variable, chaque fois que vous avez besoin de passer un devis. Exemple:

SETLOCAL ENABLEDELAYEDEXPANSION
set q=\"
child "malicious argument!q!&whoami"

Note SETLOCAL ENABLEDELAYEDEXPANSION semble fonctionner uniquement dans les fichiers batch. Pour obtenir DELAYEDEXPANSION dans un session, début cmd /V:ON.

Si votre fichier batch ne fonctionne pas avec DELAYEDEXPANSION, vous pouvez l'activer temporairement:

::region without DELAYEDEXPANSION

SETLOCAL ENABLEDELAYEDEXPANSION
::region with DELAYEDEXPANSION
set q=\"
echoarg.exe "ab !q! & echo danger"
ENDLOCAL

::region without DELAYEDEXPANSION

Si vous souhaitez transmettre du contenu dynamique à partir d'une variable contenant des guillemets échappés en tant que "", Vous pouvez remplacer "" par \" lors de l'expansion:

SETLOCAL ENABLEDELAYEDEXPANSION
foo.exe "danger & bar=region with !dynamic_content:""=\"! & danger"
ENDLOCAL

Ce remplacement n'est pas sûr avec l'expansion de style %...%!

dans le cas de OP bash -c "g++-linux-4.1 !v_params:"=\"!" est la version sûre.


Si pour une raison quelconque même activer temporairement DELAYEDEXPANSION n'est pas une option, lisez la suite:

Utiliser \" à partir de cmd est un peu plus sûr si l'on a toujours besoin d'échapper des caractères spéciaux, au lieu de juste parfois. (Il est moins probable d'oublier un curseur, s'il est cohérent...)

Pour y parvenir, on précède toute citation avec un caret (^"), les citations qui devraient atteindre le processus enfant en tant que littéraux doivent en outre être échappées avec un jeu (\^"). Tous les caractères META du shell doivent être échappé avec ^, par exemple & => ^&; | => ^|; > => ^>; etc.

Exemple:

child ^"malicious argument\^"^&whoami^"

Source: tout le monde cite les arguments de la ligne de commande dans le mauvais sens , voir "une meilleure méthode de citation"


Pour transmettre un contenu dynamique, il faut s'assurer que:
La partie de la commande qui contient la variable doit être considérée comme "entre guillemets" par cmd.exe (c'est impossible si la variable peut contenir des guillemets - don't write %var:""=\"%). Pour ce faire, le dernier " avant la variable et le premier " après la variable ne sont pas ^-échappés. les métacaractères cmd entre ces deux " ne doivent pas être échappés. Exemple:

foo.exe ^"danger ^& bar=\"region with %dynamic_content% & danger\"^"

Ce n'est pas sûr, si %dynamic_content% peut contenir inégalée citations.

7
répondu T S 2017-06-01 20:35:47