Własna odpowiedź eplawlessa w prosty i skuteczny sposób rozwiązuje jego specyficzny problem: zastępuje wszystkie "wystąpienia na całej liście argumentów \", co jest sposobem, w jaki Bash wymaga podwójnych cudzysłowów w ciągu znaków w podwójnych cudzysłowach, aby były reprezentowane.
Aby ogólnie odpowiedzieć na pytanie, jak uniknąć podwójnych cudzysłowów w ciągu znaków w podwójnych cudzysłowach za pomocącmd.exe interpretera wiersza poleceń systemu Windows (czy to w wierszu poleceń - często nadal błędnie nazywanym „monitem DOS” - czy w pliku wsadowym): Zobacz na dole, aby zapoznać się z programem PowerShell .
tl; dr :
Państwo musi użyć"" podczas mijania ciąg do nother) ( plik wsadowy i mogą korzystać ""z aplikacji utworzonych za pomocą Microsoft C „s / C ++ / net kompilatory. (Które również zaakceptować \"), która w systemie Windows zawiera Python i node.js :
\"jest wymagany - jako jedyna opcja - przez wiele innych programów (np. Ruby, Perl, a nawet Microsoft Windows PowerShell (!)), ale JEGO UŻYCIE NIE JEST BEZPIECZNE :
\"jest tym, czego wymaga wiele plików wykonywalnych i interpreterów - w tym Windows PowerShell - po przekazaniu ciągów z zewnątrz - lub, w przypadku kompilatorów Microsoftu, wsparcie jako alternatywa dla "" - ostatecznie jednak to program docelowy przeanalizuje listę argumentów .
- Przykład:
foo.exe "We had 3\" of rain."
- Jednakże użycie
\"wynik może niepożądanych, arbitralne wykonywania poleceń i / lub przekierowania wejścia / wyjścia :
- Następujące postacie stanowią zagrożenie:
& | < >
- Na przykład następujące wyniki powodują niezamierzone wykonanie
verpolecenia; patrz poniżej, aby uzyskać wyjaśnienie i następny punkt dotyczący obejścia:
foo.exe "3\" of snow" "& ver."
- Dla Windows PowerShell ,
\""i "^""są wytrzymałe, ale ograniczone alternatywy (patrz sekcja „Wywołanie CLI PowerShell za ...” poniżej).
Jeśli musisz użyć \", są tylko 3 bezpieczne podejścia , które są jednak dość uciążliwe : Nachylenie kapelusza do TS za jego pomoc.
Używając (prawdopodobnie selektywnego ) opóźnionego rozwijania zmiennych w pliku wsadowym, możesz przechowywać literał \"w zmiennej i odwoływać się do tej zmiennej w ciągu "..."znaków przy użyciu !var!składni - zobacz pomocną odpowiedź TS .
- Powyższe podejście, mimo że jest uciążliwe, ma tę zaletę, że można je stosować metodycznie i działa solidnie przy każdym wejściu.
Tylko w przypadku łańcuchów LITERALNYCH - NIE ZAWIERAJĄCYCH ZMIENNYCH - otrzymujesz podobne metodyczne podejście: kategorycznie - ^pomiń wszystkie cmd.exe metaznaki: " & | < > i - jeśli chcesz również wyłączyć rozwijanie zmiennych - %:
foo.exe ^"3\^" of snow^" ^"^& ver.^"
W przeciwnym razie musisz sformułować swój ciąg w oparciu o rozpoznanie, które części ciągu są cmd.exeuważane za niecytowane z powodu błędnej interpretacji\" jako ograniczników zamykających:
w dosłownych porcji zawierających metaznaki powłoki: ^-escape im; korzystając z powyższego przykładu, &musi to zostać ^zmienione:
foo.exe "3\" of snow" "^& ver."
w częściach z %...%odwołaniami do zmiennych w stylu : upewnij się, że są cmd.exeone traktowane jako część "..."łańcucha i że wartości zmiennych same w sobie nie mają osadzonych, niezrównoważonych cudzysłowów - co nie zawsze jest możliwe .
Aby uzyskać dodatkowe 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 uniksowych, tokenizują listę argumentów (łańcuch) przed przekazaniem argumentów indywidualnie do programu docelowego: między innymi dzielą listę argumentów na pojedyncze słowa (dzielenie na słowa) i usuwają znaki cudzysłowu z wynikowe słowa (usuwanie cytatów). Program docelowy podał się tablicę z poszczególnych argumentów , z składniowe usunięte cytaty .
Z kolei interpreter poleceń Windows najwyraźniej nie tokenizuje listy argumentów i po prostu przekazuje pojedynczy ciąg zawierający wszystkie argumenty - w tym znaki cudzysłowu. - do programu docelowego.
Jednak pewne wstępne przetwarzanie ma miejsce, zanim pojedynczy łańcuch zostanie przekazany do programu docelowego: ^znaki ucieczki. poza podwójnymi cudzysłowami ciągi są usuwane (unikają następnego znaku), a odniesienia do zmiennych (np. %USERNAME%) są interpolowane jako pierwsze.
Tak więc, w przeciwieństwie do Uniksa, zadaniem programu docelowego jest przeanalizowanie ciągu argumentów i podzielenie go na pojedyncze argumenty z usuniętymi cudzysłowami. Tak więc różne programy mogą hipotetycznie wymagać różnych metod ucieczki i nie ma jednego mechanizmu ucieczki, który gwarantowałby działanie ze wszystkimi programami - https://stackoverflow.com/a/4094897/45375 zawiera doskonałe tło anarchii, jaką jest wiersz poleceń systemu Windows rozbiór gramatyczny zdania.
W praktyce \"jest to bardzo powszechne, ale NIE BEZPIECZNE , jak wspomniano powyżej:
Ponieważ cmd.exesama nie rozpoznaje \"jako uciekł cudzysłów, może błędnie później żetony w linii poleceń, jak cytowane i potencjalnie interpretować je jako komendy i / lub przekierowania wejścia / wyjścia .
W skrócie: problem powierzchnie, czy którykolwiek z następujących znaków śledzić to otwarcie lub asymetryczne \" :& | < > ; na przykład:
foo.exe "3\" of snow" "& ver."
cmd.exewidzi następujące tokeny, wynikające z błędnej interpretacji \"jako zwykłego cudzysłowu:
"3\"
of
snow" "
- reszta:
& ver.
Ponieważ cmd.exeuważa, że nie& ver. jest cytowany , interpretuje to jako &(operator sekwencjonowania poleceń), po którym następuje nazwa polecenia do wykonania ( ver.- .jest ignorowane; verinformacje cmd.exeo wersji raportu ).
Ogólny efekt to:
- Po pierwsze,
foo.exejest wywoływana tylko za pomocą pierwszych 3 tokenów.
- Następnie polecenie
verjest wykonywane.
Nawet w przypadkach, gdy przypadkowe polecenie nie wyrządzi szkody, Twoje ogólne polecenie nie będzie działać zgodnie z przeznaczeniem, biorąc pod uwagę, że nie wszystkie argumenty są do niego przekazywane.
Wiele kompilatorów / interpreterów rozpoznaje TYLKO\" - np. Kompilator GNU C / C ++, Python, Perl, Ruby, a nawet własny Windows PowerShell Microsoftu, gdy jest wywoływany z cmd.exe- i z wyjątkiem (z ograniczeniami) dla Windows PowerShell z \"", dla nich nie ma prostego rozwiązania do tego problemu.
Zasadniczo musiałbyś wiedzieć z góry, które części twojego wiersza poleceń są błędnie interpretowane jako ^niecytowane , i wybiórczo - unikać wszystkich wystąpień & | < >w tych fragmentach.
W przeciwieństwie do tego, użycie ""jest BEZPIECZNE , ale niestety jest obsługiwane tylko przez pliki wykonywalne oparte na kompilatorze firmy Microsoft i pliki wsadowe (w przypadku plików wsadowych, z dziwactwami omówionymi powyżej), co godne uwagi wyklucza PowerShell - patrz następna sekcja.
Wywołanie interfejsu wiersza polecenia programu PowerShell z cmd.exelub powłok podobnych do POSIX:
Uwaga: Zobacz dolną sekcję, aby dowiedzieć się, jak obsługiwane jest cytowanie w programie PowerShell.
Wywołane z zewnątrz - np. Z cmd.exe, czy z wiersza poleceń, czy z pliku wsadowego:
PowerShell [Core] v6 + teraz poprawnie rozpoznaje"" (oprócz\"), co jest zarówno bezpieczne w użyciu, jak i zachowuje białe znaki .
pwsh -c " ""a & c"".length " nie pęka i prawidłowo się ugina 6
Windows PowerShell (starsza wersja, której ostatnia wersja to 5.1) rozpoznaje tylko, \" a w systemie Windows również """i bardziej niezawodne \""/"^"" (nawet jeśli wewnętrznie PowerShell używa`jako znaku ucieczki w podwójnych cudzysłowach, a także akceptuje""- patrz dolna sekcja):
Wywołanie programu Windows PowerShell zcmd.exe / pliku wsadowego:
"" psuje się , ponieważ zasadniczo nie jest obsługiwany:
powershell -c " ""ab c"".length " -> błąd „W ciągu brakuje terminatora”
\"i """ działają w zasadzie , ale nie są bezpieczne :
powershell -c " \"ab c\".length "działa zgodnie z przeznaczeniem: wyprowadza 5(zwróć uwagę na 2 spacje)
- Ale nie jest to bezpieczne, ponieważ
cmd.exemetaznaki łamią polecenie, chyba że uciekły:
powershell -c " \"a& c\".length " przerwy , ze względu na &, które musiałyby zostać usunięte jako^&
\""jest bezpieczny , ale normalizuje wewnętrzne spacje , które mogą być niepożądane:
powershell -c " \""a& c\"".length "wyjścia 4(!), ponieważ 2 przestrzenie są znormalizowane do 1.
"^""jest najlepszym wyborem zwłaszcza dla środowiska Windows PowerShell , gdzie jest zarówno bezpieczny, jak i zachowuje białe znaki, ale w przypadku programu PowerShell Core (w systemie Windows) działa tak samo, jak \""normalizacja białych znaków . Zasługa Venryx do odkrywania tego podejścia.
powershell -c " "^""a& c"^"".length " działa : nie psuje się - pomimo &- i wyświetla 5, czyli poprawnie zachowane białe znaki.
PowerShell Core : pwsh -c " "^""a& c"^"".length " działa , ale generuje 4, tj. Normalizuje białe znaki , tak jak \""robi.
Na platformach typu Unix (Linux, macOS), podczas wywoływania interfejsu wiersza polecenia programu PowerShell [Core]pwsh , z powłoki podobnej do POSIX, takiej jak bash:
Państwo musi użyć\" , która jednak jest zarówno bezpieczna i spacje-konserwujące :
$ pwsh -c " \"a& c|\".length"
Powiązana informacja
^może być używany tylko jako znak ucieczki w niecytowanych ciągach - wewnątrz ciągów z cudzysłowami, ^nie jest specjalny i traktowany jako literał.
- PRZESTROGA : Użycie
^w parametrach przekazanych do callinstrukcji jest zepsute (dotyczy to obu zastosowań call: wywołania innego pliku wsadowego lub pliku binarnego i wywołania podprogramu w tym samym pliku wsadowym):
^instancje w podwójnych cudzysłowach są w niewytłumaczalny sposób podwajane , zmieniając przekazywaną wartość: np. jeśli zmienna %v%zawiera wartość literalną a^b, call :foo "%v%"przypisuje "a^^b"(!) do %1(pierwszego parametru) w podprogramie :foo.
- Nienotowanego stosowanie
^z calljest uszkodzony całkowicie , że ^nie mogą już być stosowane do ucieczki znaków specjalnych : npcall foo.cmd a^&bcicho przerwy (zamiast przechodzenia dosłownea&bteżfoo.cmd, jak byłoby to w przypadku bezcall) -foo.cmdnigdy nie jest nawet powoływać, przynajmniej na Windows (!) 7.
Unikanie literału %to niestety szczególny przypadek , który wymaga odmiennej składni w zależności od tego, czy łańcuch jest określony w wierszu poleceń, czy w pliku wsadowym ; zobacz https://stackoverflow.com/a/31420292/45375
- W skrócie: w pliku wsadowym użyj
%%. W wierszu poleceń %nie można zastosować zmiany znaczenia, ale jeśli umieścisz a ^na początku, końcu lub wewnątrz nazwy zmiennej w niecytowanym ciągu znaków (np. echo %^foo%), Możesz zapobiec interpretacji zmiennej (interpolacji); %wystąpienia w wierszu poleceń, które nie są częścią odwołania do zmiennej, są traktowane jako literały (np 100%.).
Ogólnie, aby bezpiecznie pracować z wartościami zmiennymi, które mogą zawierać spacje i znaki specjalne :
- Przypisanie : Umieść zarówno nazwę zmiennej, jak i wartość w jednej parze podwójnych cudzysłowów ; np.
set "v=a & b"przypisuje wartość dosłowną a & bdo zmiennej %v%(z set v="a & b"kolei podwójne cudzysłowy będą częścią wartości). Escape literal %instances as %%(działa tylko w plikach wsadowych - patrz wyżej).
- Odniesienie : Podwójne cudzysłowy odwołań do zmiennych, aby upewnić się, że ich wartość nie jest interpolowana; np.
echo "%v%"nie poddaje wartości %v%interpolacji i wypisuje "a & b"(ale pamiętaj, że cudzysłowy są zawsze drukowane). W przeciwieństwie do tego echo %v%przekazuje literał ado echo, interpretuje &jako operator sekwencjonowania poleceń i dlatego próbuje wykonać polecenie o nazwie b.
Zwróć również uwagę na powyższe zastrzeżenie dotyczące ponownego użycia ^w calloświadczeniu.
- Zewnętrzne programy zazwyczaj usuwają otaczające cudzysłowy otaczające parametry, ale, jak zauważono, w plikach wsadowych musisz to zrobić samodzielnie (np.
%~1Aby usunąć zamykające cudzysłowy z pierwszego parametru) i niestety nie ma bezpośredniego sposób, który znam, aby echowiernie wydrukować wartość zmiennej bez otaczających ją cudzysłowów .
- Neil oferuje obejście oparte na a
for, które działa, o ile wartość nie ma osadzonych podwójnych cudzysłowów ; na przykład:
set "var=^&')|;,%!"
for /f "delims=" %%v in ("%var%") do echo %%~v
cmd.exenie nie rozpoznaje pojedyncze -quotes jako ograniczniki smyczkowych - są traktowane jak literały i generalnie nie może być używany do oddzielania ciągów z wbudowanymi spacji; wynika również z tego, że tokeny stykające się z pojedynczymi cudzysłowami i wszelkie tokeny pomiędzy nimi są traktowane jako niecytowane przez cmd.exei odpowiednio interpretowane.
- Jednak biorąc pod uwagę, że programy docelowe ostatecznie przeprowadzają analizę własnych argumentów, niektóre programy, takie jak Ruby, rozpoznają ciągi znaków w apostrofach nawet w systemie Windows; natomiast pliki wykonywalne C / C ++, Perl i Python ich nie rozpoznają.
Jednak nawet jeśli jest obsługiwane przez program docelowy, nie zaleca się używania ciągów znaków w pojedynczych cudzysłowach, ponieważ ich zawartość nie jest chroniona przed potencjalnie niechcianą interpretacją przez cmd.exe.
Cytowanie z poziomu PowerShell:
Windows PowerShell jest znacznie bardziej zaawansowaną powłoką cmd.exei jest częścią systemu Windows od wielu lat (a PowerShell Core wprowadził środowisko PowerShell również do systemów macOS i Linux).
PowerShell działa konsekwentnie wewnętrznie w odniesieniu do cytowania:
- wewnątrz ciągów w podwójnych cudzysłowach użyj
`"lub, ""aby uniknąć podwójnych cudzysłowów
- wewnątrz ciągów z pojedynczymi cudzysłowami, użyj
''do zmiany znaczenia w apostrofach
Działa to w wierszu poleceń programu PowerShell i podczas przekazywania parametrów do skryptów lub funkcji programu PowerShell z poziomu programu PowerShell.
(Jak omówiono powyżej, przekazanie z zewnątrz cudzysłowu uciekającego do programu PowerShell wymaga \"lub, bardziej niezawodnie, \""nic innego nie działa).
Niestety, wywołując zewnętrzne programy z PowerShell, stajesz przed koniecznością dostosowania własnych reguł cytowania PowerShell i ucieczki dla programu docelowego :
To problematyczne zachowanie jest również omówione i podsumowane w tej odpowiedzi
Podwójne cudzysłowy w podwójnych cudzysłowach :
Rozważ ciąg "3`" of rain", który PowerShell wewnętrznie tłumaczy na literał 3" of rain.
Jeśli chcesz przekazać ten ciąg do programu zewnętrznego, musisz dodatkowo zastosować znaki ucieczki programu docelowego, oprócz znaków PowerShell ; powiedz, że chcesz przekazać ciąg do programu w C, który oczekuje, że osadzone cudzysłowy zostaną zapisane jako \":
foo.exe "3\`" of rain"
Uwaga jak oboje `" - aby PowerShell Happy - i\ - aby szczęśliwy programu cel - musi być obecny.
Ta sama logika dotyczy wywoływania pliku wsadowego, gdzie ""należy użyć:
foo.bat "3`"`" of rain"
Z kolei osadzanie pojedynczych cudzysłowów w łańcuchu z podwójnymi cudzysłowami nie wymaga żadnego znaku ucieczki.
Pojedyncze -quotes wewnątrz pojedynczych -quoted strun czy nie wymagają dodatkowych pojemników; rozważ'2'' of snow', co jest reprezentacją programu PowerShell2' of snow.
foo.exe '2'' of snow'
foo.bat '2'' of snow'
PowerShell tłumaczy ciągi w apostrofach na znaki w podwójnych cudzysłowach przed przekazaniem ich do programu docelowego.
Jednak podwójne cudzysłowy w ciągach z pojedynczymi cudzysłowami , które nie wymagają zmiany znaczenia dla programu PowerShell , nadal wymagają zmiany znaczenia dla programu docelowego :
foo.exe '3\" of rain'
foo.bat '3"" of rain'
PowerShell v3 wprowadził magiczną --%opcję , zwaną symbolem stop-parsing , która łagodzi część bólu, przekazując cokolwiek po niej niezinterpretowane do programu docelowego, z wyjątkiem cmd.exeodwołań do zmiennych środowiskowych w stylu-style (np. %USERNAME%), Które są rozwijane; na przykład:
foo.exe --% "3\" of rain" -u %USERNAME%
Zwróć uwagę, że ucieczka do osadzonego, "jak \"w przypadku programu docelowego (a nie również w przypadku PowerShell, ponieważ \`") jest wystarczająca.
Jednak takie podejście:
- nie pozwala na unikanie
% znaków, aby uniknąć rozwinięć zmiennych środowiskowych.
- wyklucza bezpośrednie użycie zmiennych i wyrażeń programu PowerShell; zamiast tego wiersz poleceń musi być w pierwszym kroku wbudowany w zmienną łańcuchową, a następnie wywołany za pomocą
Invoke-Expressionw drugim.
Tak więc, pomimo wielu ulepszeń, PowerShell nie znacznie ułatwił ucieczkę podczas wywoływania programów zewnętrznych. Wprowadzono jednak obsługę ciągów w pojedynczych cudzysłowach.
Zastanawiam się, czy w świecie Windows jest zasadniczo możliwe, aby kiedykolwiek przełączyć się na model Unix, pozwalając powłoce wykonać całą tokenizację i wycenić usunięcie w przewidywalny sposób , z góry , niezależnie od programu docelowego , a następnie wywołać program docelowy, przekazując wynikowe tokeny .