Programing

배치 스크립트에서 큰 따옴표 이스케이프

lottogame 2020. 9. 23. 08:01
반응형

배치 스크립트에서 큰 따옴표 이스케이프


배치 파일 매개 변수의 모든 큰 따옴표를 이스케이프 된 큰 따옴표로 바꾸려면 어떻게해야합니까? 이것은 현재의 배치 파일이며 문자열 내부의 모든 명령 줄 매개 변수를 확장합니다.

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

그런 다음이 문자열을 사용하여 Cygwin의 bash를 호출하여 Linux 크로스 컴파일러를 실행합니다. 불행히도 다음과 같은 매개 변수가 배치 파일에 전달됩니다.

"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"

전달 된 첫 번째 경로 주변의 첫 번째 따옴표가 GCC로 전달되는 문자열을 조기에 종료하고 나머지 매개 변수를 bash에 직접 전달하는 경우 (놀라게 실패합니다.)

매개 변수를 단일 문자열로 연결 한 다음 따옴표를 이스케이프 처리하면 잘 작동 할 것이라고 상상하지만이 작업을 수행하는 방법을 결정하는 데 어려움이 있습니다. 아는 사람 있나요?


배치 스크립트의 이스케이프 문자는 ^입니다. 그러나 큰 따옴표로 묶인 문자열의 경우 따옴표를 두 배로 늘립니다.

"string with an embedded "" character"

eplawless의 자체 답변은 간단하고 효과적으로 자신의 특정 문제를 해결 "합니다. 전체 인수 목록의 모든 인스턴스를으로 대체합니다. 이는 \"Bash가 큰 따옴표로 묶인 문자열 안에 큰 따옴표를 표시해야하는 방식입니다.

일반적으로 Windows 명령 줄 인터프리터를 사용하여 큰 따옴표로 묶인 문자열 내에서 큰 따옴표를 이스케이프하는 방법에cmd.exe 대한 질문에 대답하려면 Windows 명령 줄 인터프리터 (명령 줄에서 종종 "DOS 프롬프트"라고 잘못 불림-또는 배치 파일에서) : PowerShell 을 보려면 하단을 참조하십시오 .

tl; dr :

  • 당신은 해야한다 사용"" 하는 캐릭터 라인을 통과 할 때 A (같아)에 배치 파일 사용 ""하여 만든 응용 프로그램과 함께 마이크로 소프트 의 C / C ++ / NET 컴파일러. (이 또한 동의 \"), 윈도우에있는이 파이썬과 Node.js를 포함합니다 :

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

    • 다음은 배치 파일에만 적용됩니다.

      • ""명령 인터프리터 ( cmd.exe)가 큰 따옴표로 묶인 전체 문자열을 단일 인수 로 처리 하는 유일한 방법 입니다.

      • 그러나 안타깝게도 (평상시처럼) 둘러싸는 큰 따옴표가 유지 될뿐만 아니라 두 배로 이스케이프 된 것도 유지되므로 원하는 문자열을 얻는 것은 두 단계의 과정입니다. 예를 들어, 큰 따옴표로 묶인 문자열이 첫 번째 인수로 전달되었다고 가정하면 다음과 %1같습니다.

      • set "str=%~1"둘러싸는 큰 따옴표를 제거합니다. set "str=%str:""="%"그런 다음 이중 인용 부호를 단일 인용 부호로 변환합니다.
        값의 원치 않는 해석을 방지하기 위해 할당 부분을 묶는 큰 따옴표를 사용해야합니다.

  • \"되는 요구 - 유일한 옵션으로 - 많은 다른 프로그램 (! 예를 들면, 루비, 펄, 심지어 마이크로 소프트의 PowerShell을 ())하지만, 그 사용은 안전하지 않습니다 :

    • \"외부에서 문자열 전달할 때 Microsoft의 자체 PowerShell을 포함하여 많은 실행 파일 및 인터프리터가 필요 로하는 것입니다. 또는 Microsoft 컴파일러 "" 의 경우 대안으로 지원을 지원 하지만 궁극적으로 인수 목록을 구문 분석하는 것은 대상 프로그램에 달려 있습니다. .
      • 예: foo.exe "We had 3\" of rain."
    • 그러나를 사용하면 \"명령 및 / 또는 입력 / 출력 리디렉션의 원치 않는 임의의 실행이 발생할 수 있습니다 .
      • 다음 문자는 이러한 위험을 나타냅니다. & | < >
      • 예를 들어, 다음과 같은 경우 ver명령이 의도하지 않게 실행됩니다 . 해결 방법에 대한 설명과 다음 글 머리 기호는 아래를 참조하십시오.
        • foo.exe "3\" of snow" "& ver."
    • 들어 PowerShell을윈도우 , \""그리고 "^""강력한, 그러나 제한 대안 (아래 "... PowerShell에서의 CLI 호출"절을 참조하십시오).
  • 당신이 사용해야하는 경우 \", 단지 3가 안전한 방법 그러나이다, 아주 성가신 : 팁에 모자 TS 자신의 도움이.

    • 사용 (아마도 선택적 ) 변수 확장을 지연 배치 파일에서, 당신은 수있는 문자 저장 \"A의 변수 , 안쪽 변수 것을 참조 "..."하여 문자열 !var!구문 참조 - TS의 도움이 대답 .

      • 위의 접근 방식은 번거롭지 만 체계적으로 적용 할 수 있고 어떤 입력으로도 견고하게 작동한다는 장점이 있습니다 .
    • LITERAL 문자열 (VARIABLES를 포함하지 않는 문자열)에서만 비슷한 방법을 사용합니까? 범주 ^적으로- 모든 cmd.exe 메타 문자를 이스케이프 : " & | < > 및-변수 확장을 억제하려는 경우- %:
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • 그렇지 않으면 닫는 구분 기호 로 잘못 해석 되어 따옴표가없는 것으로 간주 되는 문자열 부분을 인식하여 문자열을 공식화cmd.exe\" 해야합니다 .

      • 있는 그대로 쉘 메타 함유 부 : ^그들을 -escape; 위의 예를 사용하면- 이스케이프 &해야합니다 ^.
        foo.exe "3\" of snow" "^& ver."

      • 부분에 %...%스타일 변수 참조 : 그 보장 cmd.exe(A)의 그 부분을 고려 "..."문자열을 하고 - 변수 값 자체는 포함하지, 불균형 인용 할 수 있다는 것을 도 항상 가능한 것은 아니다 .

배경 정보는 계속 읽으십시오.


배경

참고 : 이것은 내 실험을 기반으로합니다. 내가 틀렸다면 알려주세요.

Unix 계열 시스템의 Bash와 같은 POSIX 계열은 인수 목록 (문자열)을 토큰 화 한 다음 대상 프로그램에 개별적 으로 인수를 전달 합니다. 다른 확장 중에서 인수 목록을 개별 단어로 분할 (단어 분할)하고 결과 단어 (인용문 제거). 대상 프로그램은 넘겨진다 배열개별 인수 와 함께, 구문 인용 부호를 제거 .

반대로 Windows 명령 인터프리터는 인수 목록을 토큰 화하지 않고 인용 문자를 포함하여 모든 인수로 구성된 단일 문자열을 전달합니다 . -대상 프로그램에.
그러나 일부 전처리는 단일 문자열이 대상 프로그램에 전달되기 전에 발생합니다 : ^이스케이프 문자. 이중 인용 된 문자열의 외부 (그들은 다음과 같은 문자를 탈출.) 제거되고, 변수 참조 (예, %USERNAME%)하는 보간 첫째.

따라서 Unix와 달리 인수 문자열을 구문 분석하고 따옴표를 제거하여 개별 인수로 나누는 것은 대상 프로그램의 책임입니다. 따라서, 다른 프로그램이 가설 탈출 방법을 다른 요구할 수 있습니다입니다 단일 탈출 메커니즘이 없습니다 보장 모든 프로그램 작업에이 - https://stackoverflow.com/a/4094897/45375은 윈도우 명령 줄은 무정부 상태에 우수한 배경을 포함 파싱.

실제로 \" 위에서 언급했듯이 매우 일반적이지만 안전하지 않습니다 .

cmd.exe자체적 \"으로 이스케이프 된 큰 따옴표 인식하지 않기 때문에 명령 줄에서 나중에 토큰을 인용되지 않은 것으로 잘못 해석하고 잠재적으로 명령 및 / 또는 입력 / 출력 리디렉션 으로 해석 할 수 있습니다 .
한마디로 : 다음 문자가 따른다면 문제는, 표면 열거 나 불균형을 \" :& | < > ; 예를 들면 :

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

cmd.exe\"일반 큰 따옴표로 잘못 해석 결과 다음 토큰이 표시 됩니다.

  • "3\"
  • of
  • snow" "
  • 쉬다: & ver.

따옴표붙지 않는다고cmd.exe 생각 하기 때문에 (명령 순서 지정 연산자), 실행할 명령 이름이 뒤 따르는 것으로 해석합니다 ( - 무시 됨, 의 버전 정보 보고 ). 전반적인 효과는 다음과 같습니다.& ver.&ver..vercmd.exe

  • 첫째, foo.exe처음 3 개의 토큰으로 만 호출 됩니다.
  • 그런 다음 명령 ver이 실행됩니다.

우발적 인 명령이 해를 끼치 지 않는 경우에도 모든 인수가 전달되는 것은 아니기 때문에 전체 명령이 설계된대로 작동하지 않습니다.

Many compilers / interpreters recognize ONLY \" - e.g., the GNU C/C++ compiler, Python, Perl, Ruby, even Microsoft's own PowerShell when invoked from cmd.exe - and, except for PowerShell with \"", for them there is no simple solution to this problem.
Essentially, you'd have to know in advance which portions of your command line are misinterpreted as unquoted, and selectively ^-escape all instances of & | < > in those portions.

By contrast, use of "" is SAFE, but is regrettably only supported by Microsoft-compiler-based executables and batch files (in the case of batch files, with the quirks discussed above), which notable excludes PowerShell - see next section.


Calling PowerShell's CLI from cmd.exe or POSIX-like shells:

Note: See the bottom section for how quoting is handled inside PowerShell.

PowerShell, when invoked from the outside - e.g., from cmd.exe, whether from the command line or a batch file - recognizes only \" and, on Windows also """ and the more robust \"" / "^"" (even though internally PowerShell uses ` as the escape character in double-quoted strings and also accepts "" - see bottom section):

On Windows, calling from cmd.exe / a batch file:

  • "" breaks, because it is fundamentally unsupported:

    • powershell -c " ""ab c"".length " -> error "The string is missing the terminator"
  • \" and """ work in principle, but aren't safe:

    • powershell -c " \"ab c\".length " works as intended: it outputs 5 (note the 2 spaces)
    • But it isn't safe, because cmd.exe metacharacters break the command, unless escaped:
      powershell -c " \"a& c\".length " breaks, due to the &, which would have to be escaped as ^&
  • \"" is safe, but normalize interior whitespace, which can be undesired:

    • powershell -c " \""a& c\"".length " outputs 4(!), because the 2 spaces are normalized to 1.
  • "^"" is the best choice for Windows PowerShell specifically, where it is both safe and whitespace-preserving, but with PowerShell Core (on Windows) it is the same as \"", i.e, whitespace-normalizing. Credit goes to Venryx for discovering this approach.

    • powershell -c " "^""a& c"^"".length " works: doesn't break - despite & - and outputs 5, i.e., correctly preserved whitespace.

    • PowerShell Core: pwsh -c " "^""a& c"^"".length " works, but outputs 4, i.e. normalizes whitespace, as \"" does.

On Unix-like platforms (Linux, macOS), calling PowerShell Core's CLI, pwsh from a POSIX-like shell such as bash:

You must use \", which, however is both safe and whitespace-preserving:

$ pwsh -c " \"a&  c|\".length" # OK: 5

Related information

  • ^ can only be used as the escape character in unquoted strings - inside double-quoted strings, ^ is not special and treated as a literal.

    • CAVEAT: Use of ^ in parameters passed to the call statement is broken (this applies to both uses of call: invoking another batch file or binary, and calling a subroutine in the same batch file):
      • ^ instances in double-quoted values are inexplicably doubled, altering the value being passed: e.g., if variable %v% contains literal value a^b, call :foo "%v%" assigns "a^^b"(!) to %1 (the first parameter) in subroutine :foo.
      • Unquoted use of ^ with call is broken altogether in that ^ can no longer be used to escape special characters: e.g., call foo.cmd a^&b quietly breaks (instead of passing literal a&b too foo.cmd, as would be the case without call) - foo.cmd is never even invoked(!), at least on Windows 7.
  • Escaping a literal % is a special case, unfortunately, which requires distinct syntax depending on whether a string is specified on the command line vs. inside a batch file; see https://stackoverflow.com/a/31420292/45375

    • The short of it: Inside a batch file, use %%. On the command line, % cannot be escaped, but if you place a ^ at the start, end, or inside a variable name in an unquoted string (e.g., echo %^foo%), you can prevent variable expansion (interpolation); % instances on the command line that are not part of a variable reference are treated as literals (e.g, 100%).
  • Generally, to safely work with variable values that may contain spaces and special characters:

    • Assignment: Enclose both the variable name and the value in a single pair of double-quotes; e.g., set "v=a & b" assigns literal value a & b to variable %v% (by contrast, set v="a & b" would make the double-quotes part of the value). Escape literal % instances as %% (works only in batch files - see above).
    • Reference: Double-quote variable references to make sure their value is not interpolated; e.g., echo "%v%" does not subject the value of %v% to interpolation and prints "a & b" (but note that the double-quotes are invariably printed too). By contrast, echo %v% passes literal a to echo, interprets & as the command-sequencing operator, and therefore tries to execute a command named b.
      Also note the above caveat re use of ^ with the call statement.
    • External programs typically take care of removing enclosing double-quotes around parameters, but, as noted, in batch files you have to do it yourself (e.g., %~1 to remove enclosing double-quotes from the 1st parameter) and, sadly, there is no direct way that I know of to get echo to print a variable value faithfully without the enclosing double-quotes.
      • Neil offers a for-based workaround that works as long as the value has no embedded double quotes; e.g.:
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exe does not recognize single-quotes as string delimiters - they are treated as literals and cannot generally be used to delimit strings with embedded whitespace; also, it follows that the tokens abutting the single-quotes and any tokens in between are treated as unquoted by cmd.exe and interpreted accordingly.

    • However, given that target programs ultimately perform their own argument parsing, some programs such as Ruby do recognize single-quoted strings even on Windows; by contrast, C/C++ executables, Perl and Python do not recognize them.
      Even if supported by the target program, however, it is not advisable to use single-quoted strings, given that their contents are not protected from potentially unwanted interpretation by cmd.exe.

Quoting from within PowerShell:

Windows PowerShell is a much more advanced shell than cmd.exe, and it has been a part of Windows for many years now (and PowerShell Core brought the PowerShell experience to macOS and Linux as well).

PowerShell works consistently internally with respect to quoting:

  • inside double-quoted strings, use `" or "" to escape double-quotes
  • inside single-quoted strings, use '' to escape single-quotes

This works on the PowerShell command line and when passing parameters to PowerShell scripts or functions from within PowerShell.

(As discussed above, passing an escaped double-quote to PowerShell from the outside requires \" or, more robustly, \"" - nothing else works).

Sadly, when invoking external programs from PowerShell, you're faced with the need to both accommodate PowerShell's own quoting rules and to escape for the target program:

This problematic behavior is also discussed and summarized in this GitHub docs issue

Double-quotes inside double-quoted strings:

Consider string "3`" of rain", which PowerShell-internally translates to literal 3" of rain.

If you want to pass this string to an external program, you have to apply the target program's escaping in addition to PowerShell's; say you want to pass the string to a C program, which expects embedded double-quotes to be escaped as \":

foo.exe "3\`" of rain"

Note how both `" - to make PowerShell happy - and the \ - to make the target program happy - must be present.

The same logic applies to invoking a batch file, where "" must be used:

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

By contrast, embedding single-quotes in a double-quoted string requires no escaping at all.

Single-quotes inside single-quoted strings do not require extra escaping; consider '2'' of snow', which is PowerShell' representation of 2' of snow.

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

PowerShell translates single-quoted strings to double-quoted ones before passing them to the target program.

However, double-quotes inside single-quoted strings, which do not need escaping for PowerShell, do still need to be escaped for the target program:

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

PowerShell v3 introduced the magic --% option, called the stop-parsing symbol, which alleviates some of the pain, by passing anything after it uninterpreted to the target program, save for cmd.exe-style environment-variable references (e.g., %USERNAME%), which are expanded; e.g.:

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

Note how escaping the embedded " as \" for the target program only (and not also for PowerShell as \`") is sufficient.

However, this approach:

  • does not allow for escaping % characters in order to avoid environment-variable expansions.
  • precludes direct use of PowerShell variables and expressions; instead, the command line must be built in a string variable in a first step, and then invoked with Invoke-Expression in a second.

Thus, despite its many advancements, PowerShell has not made escaping much easier when calling external programs. It has, however, introduced support for single-quoted strings.

I wonder if it's fundamentally possible in the Windows world to ever switch to the Unix model of letting the shell do all the tokenization and quote removal predictably, up front, irrespective of the target program, and then invoke the target program by passing the resulting tokens.


Google eventually came up with the answer. The syntax for string replacement in batch is this:

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

Which produces "replicate me". My script now looks like this:

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

Which replaces all instances of " with \", properly escaped for bash.


As an addition to mklement0's excellent answer:

Almost all executables accept \" as an escaped ". Safe usage in cmd however is almost only possible using DELAYEDEXPANSION.
To explicitely send a literal " to some process, assign \" to an environment variable, and then use that variable, whenever you need to pass a quote. Example:

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

Note SETLOCAL ENABLEDELAYEDEXPANSION seems to work only within batch files. To get DELAYEDEXPANSION in an interactive session, start cmd /V:ON.

If your batchfile does't work with DELAYEDEXPANSION, you can enable it temporarily:

::region without DELAYEDEXPANSION

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

::region without DELAYEDEXPANSION

If you want to pass dynamic content from a variable that contains quotes that are escaped as "" you can replace "" with \" on expansion:

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

This replacement is not safe with %...% style expansion!

In case of OP bash -c "g++-linux-4.1 !v_params:"=\"!" is the safe version.


If for some reason even temporarily enabling DELAYEDEXPANSION is not an option, read on:

Using \" from within cmd is a little bit safer if one always needs to escape special characters, instead of just sometimes. (It's less likely to forget a caret, if it's consistent...)

To achieve this, one precedes any quote with a caret (^"), quotes that should reach the child process as literals must additionally be escaped with a backlash (\^"). ALL shell meta characters must be escaped with ^ as well, e.g. & => ^&; | => ^|; > => ^>; etc.

Example:

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

Source: Everyone quotes command line arguments the wrong way, see "A better method of quoting"


To pass dynamic content, one needs to ensure the following:
The part of the command that contains the variable must be considered "quoted" by cmd.exe (This is impossible if the variable can contain quotes - don't write %var:""=\"%). To achieve this, the last " before the variable and the first " after the variable are not ^-escaped. cmd-metacharacters between those two " must not be escaped. Example:

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

This isn't safe, if %dynamic_content% can contain unmatched quotes.


For example for Unreal engine Automation tool run from batch file - this worked for me

eg: -cmdline=" -Messaging" -device=device -addcmdline="-SessionId=session -SessionOwner='owner' -SessionName='Build' -dataProviderMode=local -LogCmds='LogCommodity OFF' -execcmds='automation list; runtests tests+separated+by+T1+T2; quit' " -run

Hope this helps someone, worked for me.

참고URL : https://stackoverflow.com/questions/562038/escaping-double-quotes-in-batch-script

반응형