Unikanie podwójnych cudzysłowów w skrypcie wsadowym

Jak mógłbym zastąpić wszystkie podwójne cudzysłowy w parametrach pliku wsadowego podwójnymi cudzysłowami? Jest to mój bieżący plik wsadowy, który rozszerza wszystkie parametry wiersza poleceń wewnątrz łańcucha:

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

Następnie używa tego ciągu do wywołania bash Cygwina, wykonując linuksowy kompilator krzyżowy. Niestety, dostaję parametry takie jak te przekazywane do mojego pliku wsadowego:

"launch-linux-g++.bat" -ftemplate-depth-128 -O3 -finline-functions 
-Wno-inline -Wall  -DNDEBUG   -c 
-o "C:\Users\Me\Documents\Testing\SparseLib\bin\Win32\LinuxRelease\hello.o" 
"c:\Users\Me\Documents\Testing\SparseLib\SparseLib\hello.cpp"

Gdzie pierwszy cytat wokół pierwszej przekazanej ścieżki jest przedwcześnie zakończenie łańcucha przekazywanego do GCC i przekazanie reszty parametrów bezpośrednio do bash (co nie udaje się spektakularnie.)

Wyobrażam sobie, że jeśli Mogę połączyć parametry w pojedynczy ciąg znaków, a następnie uciec cudzysłowów powinno działać dobrze, ale mam trudności z określeniem, jak to zrobić. Czy ktoś wie?

Author: eplawless, 2009-02-18

4 answers

Znakiem escape w skryptach wsadowych jest ^. Ale dla ciągów podwójnie cytowanych, podwojenie cudzysłowów:

"string with an embedded "" character"
 84
Author: Eclipse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 17:31:53

Odpowiedź Eplawlessa w prosty i efektywny sposób rozwiązuje jego specyficzny problem: zastępuje wszystkie " instancje z całej listy argumentów \", co jest sposobem, w jaki Bash wymaga reprezentowania podwójnych cudzysłowów wewnątrz podwójnego cytowanego ciągu.

Aby ogólnie odpowiedzieć na pytanie jak uniknąć podwójnego cudzysłowu wewnątrz podwójnego cytowanego ciągu za pomocą cmd.exe, interpreter wiersza poleceń systemu Windows (czy to w wierszu poleceń-często błędnie nazywany "Dos prompt" - lub w pliku wsadowym): Zobacz na dole PowerShell .

Tl; dr :

  • Ty musi używać "" gdy przekazujemy łańcuch do pliku wsadowego (nother) a Ty może używać "" z aplikacjami utworzonymi za pomocą kompilatorów Microsoft C/C++/. Net (które również accept \"), Które W Windows obejmują Pythona i węzeł.js :

    • Przykład: foo.bat "We had 3"" of rain."

    • Poniższe zasady dotyczą tylko plików wsadowych:

      • "" jest jedynym sposobem na uzyskanie interpretera poleceń (cmd.exe), aby traktował cały cytowany łańcuch jako argument jako pojedynczy argument.

      • Niestety, nie tylko zamykające się podwójne cudzysłowy są zachowane (jak zwykle), ale także podwojone znaki ucieczki, więc uzyskanie zamierzonego ciągu jest dwustopniowe proces; np. zakładając, że dwukrotnie cytowany łańcuch jest przekazywany jako pierwszy argument, %1:

      • set "str=%~1" usuwa podwójne cudzysłowy; set "str=%str:""="%" następnie konwertuje podwójne cudzysłowy na pojedyncze.
        Pamiętaj, aby używać podwójnych cudzysłowów wokół części przypisania, aby zapobiec niepożądanej interpretacji wartości.

  • \" jest wymagane - jako jedyna opcja - przez wiele innych programy, (np. Ruby, Perl, a nawet własny PowerShell Microsoftu (!)nie jest to jednak możliwe, ponieważ nie jest to możliwe.]}

    • \" jest tym, co wiele programów wykonywalnych i interpreterów wymaga - włączając w to własny PowerShell Microsoftu po przekazaniu ciągów z zewnątrz - lub, w przypadku Kompilatory Microsoftu, wspierają jako alternatywę dla "" - ostatecznie jednak, to do programu docelowego należy przeanalizowanie argumentu lista.
    • przykład: foo.exe "We had 3\" of rain."
    • Jednak użycie \" może skutkować niechcianym, arbitralnym wykonywaniem poleceń i / lub przekierowaniem wejścia/wyjścia.]}:
      • następujące znaki przedstawiają to ryzyko: & | < >
      • na przykład, następujące wyniki w niezamierzonym wykonaniu ver polecenia; zobacz poniżej wyjaśnienie i następny punkt dla obejście:
        • foo.exe "3\" of snow" "& ver."
    • tylko dla PowerShell tylko dla Windows , \"" jest solidną alternatywą.
  • Jeśli musisz użyć \", są tylko 3 bezpieczne podejścia , które są jednak dość uciążliwe: końcówka kapelusza do T S za jego pomoc.

    • Za pomocą (ewentualnie selektywne ) opóźnione rozszerzenie zmiennej w pliku wsadowym możesz przechowywać literał \" W Zmiennej i odwoływać się do tej zmiennej wewnątrz ciągu "..." używając składni !var! - zobacz pomocną odpowiedź T S .

      • powyższe podejście, mimo że jest uciążliwe, ma tę zaletę, że można je zastosować metodycznie i że działa solidnie, z dowolnym wejście.
    • Tylko z ciągami LITERALNYMI-nie zawierającymi zmiennych - otrzymujesz podobnie metodyczne podejście: kategorycznie ^ - escape wszystkie cmd.exe metacharaktery: " & | < > i-jeśli chcesz również tłumić Rozszerzanie zmiennych- %:
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • W przeciwnym razie musisz sformułować swój łańcuch na podstawie rozpoznania, które części łańcucha cmd.exe uważa za nienotowane ze względu na błędna interpretacja \" jako ograniczniki zamykające:

      • W literalne części zawierające metacharaktery powłoki: ^ - escape them; używając powyższego przykładu, to & musi być ^-Escape:
        foo.exe "3\" of snow" "^& ver."

      • W części z %...%-style odwołania do zmiennej : upewnij się, że cmd.exe uważa je za część "..."string i że wartości zmiennej same w sobie nie mają wbudowanego, niezrównoważonego cytaty - co nie zawsze jest możliwe .

Aby uzyskać podstawowe informacje, Czytaj dalej.


Tło

uwaga: jest to oparte na moich własnych eksperymentach. Daj mi znać, jeśli się mylę.

Powłoki podobne do POSIX, takie jak Bash w systemach uniksopodobnych, tokenizują listę argumentów (string) przed przekazaniem argumentów indywidualnie do programu docelowego. podziel listę argumentów na poszczególne słowa (dzielenie słów) i usuń znaki cytowania z wynikowych słów (usuwanie cytatów). To, co program docelowy podaje, jest koncepcyjnie tablicą pojedynczych argumentów z usuniętymi cudzysłowami (wymaganymi w składni).

Natomiast interpreter poleceń Windows najwyraźniej nie tokenizuje listy argumentów i po prostu przekazuje pojedynczy ciąg zawierający wszystkie argumenty - w tym znaki cytowania. - do program docelowy.
Jednak pewne wstępne przetwarzanie odbywa się przed przekazaniem pojedynczego ciągu znaków do programu docelowego: ^ znaki ucieczki. poza podwójnymi cytowanymi łańcuchami są usuwane (wydostają się z następującego znaku.), a odwołania do zmiennych (np. %USERNAME%) są interpolowane jako pierwsze.

Tak więc, w przeciwieństwie do Uniksa, zadaniem programu docelowego jest analiza ciągu argumentów i rozbicie go na poszczególne argumenty za pomocą cudzysłowów usunięte. Tak więc, różne programy mogą hipotetycznie wymagać różnych metod ucieczki i nie ma jednego mechanizmu ucieczki, który jest gwarantowany do pracy ze wszystkimi programami - https://stackoverflow.com/a/4094897/45375 zawiera doskonałe tło anarchii, jaką jest parsowanie wiersza poleceń systemu Windows.

W praktyce, \" jest bardzo powszechne, ale nie Bezpieczne , jak wspomniano powyżej:

Od cmd.exe nie rozpoznaje \" jako uciekającego podwójnego cudzysłowu, może błędnie interpretować późniejsze tokeny w linii poleceń jako nie cytowane i potencjalnie interpretować je jako polecenia i/lub przekierowania wejścia/wyjścia.
w skrócie: problem pojawia się, jeśli któraś z poniższych znaków podąża za otwierającym lub niezbalansowanym \": & | < >; na przykład:

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

cmd.exe widzi następujące tokeny, wynikające z błędna interpretacja \" jako zwykłego podwójnego cytatu:

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

Ponieważ cmd.exe uważa, że & ver. jest niekanonicznym , interpretuje go jako & (operator sekwencjonujący polecenia), po którym następuje nazwa polecenia do wykonania (ver. - . jest ignorowany; ver przekazuje informacje o wersji cmd.exe).
Ogólny efekt to:

  • pierwszy, foo.exe jest wywoływany z pierwszy 3 tylko żetony.
  • następnie wykonywane jest polecenie ver.

Nawet jeśli przypadkowe polecenie nie zaszkodzi, Twoje ogólne polecenie nie będzie działać tak, jak zostało zaprojektowane, biorąc pod uwagę, że nie wszystkie argumenty są do niego przekazywane.

Wiele kompilatorów / interpreterów rozpoznaje tylko \" - na przykład kompilator GNU C/C++, Python, Perl, Ruby, a nawet własny PowerShell Microsoftu wywołany z cmd.exe - i, z wyjątkiem PowerShell z \"", dla nich Nie ma prostego rozwiązania tego problemu.
Zasadniczo, musisz wiedzieć z góry, które fragmenty Twojej linii poleceń są błędnie interpretowane jako nienotowane i selektywnie ^-unikaj wszystkich instancji & | < > w tych fragmentach.

Dla kontrastu, użycie "" jest bezpieczne, ale jest niestety obsługiwane tylko przez pliki wykonywalne oparte na kompilatorze Microsoft i pliki wsadowe (W przypadku plików wsadowych, z dziwactwami omówionymi powyżej).

By contrast, PowerShell, podczas wywoływania z zewnątrz - np. z cmd.exe, czy to z linii poleceń, czy z pliku wsadowego - rozpoznaje tylko \" a w Windows tym bardziej wytrzymałe \"", pomimo tego, że wewnętrznie PowerShell używa ` jako znaku escape w dwu cytowanych łańcuchach, a także akceptuje ""; np.:

  • powershell -c " \"ab c\".length" działa (wyjścia 4), podobnie jak więcej solidna
    powershell -c " \""ab c\"".length",

  • Ale powershell -c " ""ab c"".length" breaks .


Informacje pokrewne

  • ^ może być używany tylko jako znak escape w unquoted strings - wewnątrz dwucyfrowych łańcuchów ^ nie jest specjalny i traktowany jako literalny.

    • zastrzeżenie: użycie ^ w parametrach przekazywanych do instrukcji call jest złamane (dotyczy to zarówno użycie call: wywołanie innego pliku wsadowego lub binarnego i wywołanie podprogramu w tym samym pliku wsadowym):
      • ^ instancje w Double-quoted wartości są niewytłumaczalnie podwojone, zmiana przekazywanej wartości: na przykład, jeśli zmienna %v% zawiera wartość literałową a^b, call :foo "%v%" "a^^b" (!) do %1 (pierwszy parametr) w podprogramie :foo.
      • użycie ^ Z call jest złamane w sumie w tym ^ nie może być już używany do ucieczki znaków specjalnych : np. call foo.cmd a^&b po cichu łamie (zamiast przekazywać dosłowne a&b zbyt foo.cmd, jak to by było bez call) - foo.cmd nie jest nawet wywoływany (!), przynajmniej na Windows 7.
  • Nie jest to jednak żaden inny przypadek, który może być użyty do określenia, czy dany ciąg znaków jest podany w Komendzie . linia vs. wewnątrz pliku wsadowego; zobacz https://stackoverflow.com/a/31420292/45375

    • Skrót: wewnątrz pliku wsadowego użyj %%. W wierszu poleceń, % nie może być przechowywany, ale jeśli umieścisz ^ na początku, końcu lub w nazwie zmiennej w łańcuchu nienotowanym (np. echo %^foo%), możesz zapobiec ekspansji zmiennej (interpolacji); instancje % w wierszu poleceń, które nie są częścią odniesienia do zmiennej, są traktowane jako literały (np. 100%).
  • Ogólnie rzecz biorąc, aby bezpiecznie pracować z wartościami zmiennych, które mogą zawierać spacje i znaki specjalne :

    • przypisanie: Załącz zarówno nazwę zmiennej, jak i wartość w pojedynczej parze podwójnych cudzysłowów ; np. set "v=a & b" przypisuje wartość literałową a & b do zmiennej %v% (dla kontrastu set v="a & b" uczyniłoby podwójną cudzysłowem część wartości). Ucieczka literalne instancje % jako %% (działa tylko w plikach wsadowych-patrz wyżej).
    • : aby upewnić się, że ich wartość nie jest interpolowana, np. echo "%v%" nie poddaje wartości %v% interpolacji i drukuje "a & b" (należy jednak pamiętać, że podwójne cudzysłowy są również zawsze drukowane). Natomiast echo %v% przekazuje literał a do echo, interpretuje {[39] } jako operator sekwencjonujący polecenia i dlatego próbuje wykonaj polecenie o nazwie b.
      Zwróć również uwagę na powyższe zastrzeżenie ponownego użycia ^ Z call.
    • zewnętrzne programy zazwyczaj zajmują się usuwaniem zamykających się podwójnych cudzysłowów wokół parametrów, ale, jak zauważono, w plikach wsadowych musisz to zrobić sam (np. {[125] } aby usunąć zamykające się podwójne cudzysłowy z pierwszego parametru) i niestety, {159]} nie ma bezpośredniego sposobu, o którym wiem, aby uzyskać {120]} wiernie wydrukować wartość zmiennej bez dołączając podwójne cudzysłowy .
      • Neil oferuje obejście oparte na for, które działa tak długo, jak wartość nie ma osadzonych podwójnych cudzysłowów; np.:
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe czy nie rozpoznaje pojedyncze -cudzysłowy jako ograniczniki łańcuchów-są one traktowane jako literały i nie mogą być ogólnie używane do oddzielania łańcuchów z osadzonymi białymi spacjami; wynika również, że tokeny znajdujące się obok pojedynczych cytatów i wszystkie tokeny pomiędzy są traktowane jako nienotowane przez cmd.exe i odpowiednio interpretowane.

    • jednakże, biorąc pod uwagę, że programy docelowe ostatecznie wykonują własne parsowanie argumentów, niektóre programy, takie jak Ruby, rozpoznają pojedyncze cytowane ciągi nawet w systemie Windows; natomiast pliki wykonywalne C / C++, Perl i Python rozpoznają je , a nie.
      Nawet jeśli program docelowy jest obsługiwany, nie jest jednak wskazane używanie single-quoted ciągi, ponieważ ich zawartość nie jest chroniona przed potencjalnie niechcianą interpretacją przez cmd.exe.

PowerShell

Windows PowerShell jest znacznie bardziej zaawansowaną powłoką niż cmd.exe i jest częścią systemu Windows od wielu lat (i {593]} PowerShell Core przyniósł doświadczenie PowerShell do macOS i Linux, jak również).

PowerShell działa konsekwentnie wewnętrznie w odniesieniu do cytat:

  • wewnątrz cudzysłowów, użyj `" lub "", aby uciec od cudzysłowów
  • wewnątrz cudzysłowów, użyj '', aby uciec od cudzysłowów

Działa to na linii poleceń PowerShell i podczas przekazywania parametrów do skryptów lub funkcji PowerShell z wewnątrz PowerShell.

W przypadku, gdy nie jest to możliwe, nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe., \"" - nic innego nie działa).

Niestety, wywołując zewnętrzne programy , stajesz przed koniecznością dostosowania zarówno własnych reguł cytowania PowerShella , jak i do ucieczki dla docelowego programu :

to problematyczne zachowanie jest również omówione i podsumowane w ten problem z GitHub docs

Podwójne - cytaty wewnątrz podwójne - cytaty ciągi :

PowerShell-wewnętrznie tłumaczy się na literalne 3" of rain.

Jeśli chcesz przekazać ten łańcuch do zewnętrznego programu, musisz zastosować program docelowy 's escaping dodatkowo do PowerShell' S ; powiedzmy, że chcesz przekazać łańcuch do programu C, który oczekuje osadzonych podwójnych cudzysłowów jako \":

foo.exe "3\`" of rain"

Zauważ jak zarówno `" - aby PowerShell był szczęśliwy - i \ - aby program docelowy był szczęśliwy-musi być obecny.

Ta sama logika dotyczy wywołania pliku wsadowego, gdzie "" musi być użyte:

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

Dla kontrastu, osadzanie pojedynczy - cudzysłów w podwójny -cytowany ciąg nie wymaga w ogóle ucieczki.

pojedyncze - cytaty wewnątrz pojedyncze - cytowane ciągi do nie wymagają extra ; rozważmy '2'' of snow', która jest reprezentacją PowerShella 2' of snow.

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

PowerShell tłumaczy pojedyncze cytowane łańcuchy na podwójne cytowane przed przekazaniem ich do programu docelowego.

Jednakże, podwójne - cudzysłowy wewnątrz pojedyncze - cytowane ciągi , które nie wymagają ucieczki dla PowerShell , nadal muszą być zabezpieczone dla } programu docelowego:

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

PowerShell v3 wprowadził Magia --% opcja , zwana symbolem stop-parsing , która łagodzi ból, przekazując cokolwiek po nim nieinterpretowane do programu docelowego, z wyjątkiem odniesień do zmiennych środowiskowych w stylu cmd.exe (np. %USERNAME%), które rozszerzone; np.:

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

Zauważ, że unikanie wbudowanego " jako \" tylko dla programu docelowego (a nie także dla PowerShell jako \`") jest wystarczające.

Jednak to podejście:

  • nie pozwala na ucieczkę % znaków w celu uniknięcia rozszerzenia o zmienne środowisko.
  • wyklucza bezpośrednie użycie zmiennych i wyrażeń PowerShell; zamiast tego wiersz poleceń musi być zbudowany w zmiennej łańcuchowej w pierwszym kroku, a następnie wywołany z Invoke-Expression w sekundę.

Tak więc, pomimo wielu udoskonaleń, PowerShell nie ułatwił ucieczki podczas wywoływania zewnętrznych programów. Wprowadzono jednak obsługę pojedynczych cytowanych ciągów.

Zastanawiam się, czy jest to zasadniczo możliwe w świecie Windows, aby kiedykolwiek przełączyć się na model Unix pozwalając powłoka zrobić wszystkie tokenizacji i usuwania cytatów przewidywalnie, z przodu, program docelowy, a następnie wywołać program docelowy, przekazując wynikowe tokeny.

 59
Author: mklement0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-02 02:21:13

Google w końcu wymyślił odpowiedź. Składnia zastępowania łańcuchów w partii jest następująca:

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

Który produkuje "Replikuj mnie". Mój skrypt wygląda teraz tak:

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

, który zastępuje wszystkie instancje " na \", odpowiednio zabezpieczone dla Basha.

 20
Author: eplawless,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 17:48:17

Jako dodatek do doskonała odpowiedź mklement0:

Prawie wszystkie pliki wykonywalne akceptują {[5] } jako ucieczkę ". Bezpieczne użycie w cmd jest jednak prawie możliwe tylko przy użyciu DELAYEDEXPANSION.
Aby w jasny sposób wysłać literał " do jakiegoś procesu, Przypisz \" do zmiennej środowiskowej, a następnie użyj tej zmiennej, gdy chcesz przekazać cytat. Przykład:

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

Uwaga SETLOCAL ENABLEDELAYEDEXPANSION wydaje się działać tylko w plikach wsadowych. Aby uzyskać opóźnienie w interaktywnym sesja, start cmd /V:ON.

Jeśli plik Batch nie działa z DELAYEDEXPANSION, możesz go tymczasowo włączyć:

::region without DELAYEDEXPANSION

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

::region without DELAYEDEXPANSION

Jeśli chcesz przekazać dynamiczną zawartość ze zmiennej, która zawiera cudzysłowy, które są unikalne jako "", możesz zastąpić "" \" przy rozbudowie:

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

Ten zamiennik nie jest bezpieczny z %...% rozszerzeniem stylu!

W Przypadku OP bash -c "g++-linux-4.1 !v_params:"=\"!" to bezpieczna wersja.


Jeśli z jakiegoś powodu nawet chwilowe włączenie DELAYEDEXPANSION nie jest opcją, Czytaj dalej:

Używanie \" z cmd jest trochę bezpieczniejsze, jeśli zawsze trzeba unikać znaków specjalnych, a nie tylko czasami. (Mniej prawdopodobne jest, że zapomni o karetce, jeśli jest spójna...)

Aby to osiągnąć, należy poprzedzić dowolny cytat za pomocą caret (^"), cytaty, które powinny dotrzeć do procesu potomnego, ponieważ literały muszą być dodatkowo unikane za pomocą backlash (\^"). wszystkie meta znaki powłoki muszą być również z ^, np. & => ^&; | => ^|; > => ^>; itd.

Przykład:

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

Źródło: każdy cytuje argumenty linii poleceń w niewłaściwy sposób , zobacz "lepsza metoda cytowania"


Aby przekazać dynamiczną zawartość, należy upewnić się, że:
Część polecenia, która zawiera zmienną, musi być uznana za" cytowaną " przez cmd.exe (jest to niemożliwe, jeśli zmienna może zawierać cudzysłowy - nie pisz %var:""=\"%). Aby to osiągnąć, ostatnia " przed zmienną i pierwsza " po zmiennej nie są ^-unikalne. cmd-metacharaktery pomiędzy tymi dwoma " nie mogą być uciekane. Przykład:

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

To nie jest bezpieczne, jeśli {[32] } może zawierać niezrównane cudzysłowy.

 7
Author: T S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-01 20:35:47