CMD.EXE (Windows Command Interpreter)는 스크립트를 어떻게 구문 분석합니까?


142

나는 우연히 ss64.com 윈도우 명령 인터프리터 실행됩니다 배치 스크립트를 작성하는 방법에 대한 좋은 도움을 제공한다.

그러나 배치 스크립트 문법 , 일이 확장되거나 확장되지 않는 방법 및 일을 피하는 방법에 대한 좋은 설명을 찾을 수 없었습니다 .

해결하지 못한 샘플 질문은 다음과 같습니다.

  • 견적 시스템은 어떻게 관리됩니까? 내가 만든 TinyPerl 스크립트를
    ( foreach $i (@ARGV) { print '*' . $i ; },)를 컴파일하고이 방법이라고 :
    • my_script.exe "a ""b"" c" → 출력 *a "b*c
    • my_script.exe """a b c""" → 출력 *"a*b*c"
  • 내부 echo명령 은 어떻게 작동합니까? 그 명령 안에 무엇이 확장되어 있습니까?
  • 왜 사용해야합니까 for [...] %%I파일 스크립트 for [...] %I에서 대화식 세션에서 입니까?
  • 이스케이프 문자는 무엇이며 어떤 컨텍스트에서? 퍼센트 부호를 피하는 방법? 예를 들어, %PROCESSOR_ARCHITECTURE%문자 그대로 에코하는 방법은 무엇입니까? 나는 그것이 echo.exe %""PROCESSOR_ARCHITECTURE%효과가 있다는 것을 알았 습니다. 더 나은 해결책이 있습니까?
  • 쌍은 어떻게 %일치합니까? 예:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • 이 변수에 큰 따옴표가 포함 된 경우 변수가 명령에 단일 인수로 전달되도록하려면 어떻게해야합니까?
  • set명령을 사용할 때 변수는 어떻게 저장 됩니까? 예를 들어, 내가 할 경우 set a=a" b다음 echo.%a%내가 얻을 a" b. 그러나 echo.exeUnxUtils에서 사용하면 을 얻습니다 a b. 어떻게 %a%다른 방식으로 확장됩니까?

당신의 조명에 감사드립니다.


Rob van der Woude는 그의 사이트에 멋진 Batch 스크립팅 및 Windows 명령 프롬프트 참조 를 가지고 있습니다.
JBR 윌킨슨

답변:


200

배치 스크립트의 문법을 조사하기위한 실험을 수행했습니다. 또한 배치 모드와 명령 행 모드의 차이점을 조사했습니다.

배치 라인 파서 :

다음은 배치 파일 행 구문 분석기의 단계에 대한 간략한 개요입니다.

단계 0) 판독 라인 :

1 단계) 확장 비율 :

2 단계) 특수 문자 처리, 토큰 화 및 캐시 된 명령 블록 작성 : 따옴표, 특수 문자, 토큰 구분 기호 및 캐럿 이스케이프와 같은 항목의 영향을받는 복잡한 프로세스입니다.

단계 3) 구문 분석 된 명령을 에코합니다 . 명령 블록이로 시작하지 않고 @ECHO가 이전 단계의 시작에서 ON 인 경우에만 가능합니다 .

4 단계) %X변수 확장의 경우 : FOR 명령이 활성화되고 DO 이후의 명령이 처리되는 경우에만 해당됩니다.

5 단계) 지연 확장 : 지연 확장이 활성화 된 경우에만

5.3 단계) 파이프 가공 : 양쪽에 명령이있는 경우에만

단계 5.5) 리디렉션 실행 :

6 단계) 통화 처리 / 카페 배가 : 명령 토큰이 CALL 인 경우에만

단계 7) 실행 : 명령이 실행됩니다


각 단계에 대한 세부 정보는 다음과 같습니다.

아래 설명 된 단계는 배치 파서 작동 방식의 모델 일뿐입니다. 실제 cmd.exe 내부에는 이러한 단계가 반영되지 않을 수 있습니다. 그러나이 모델은 배치 스크립트의 동작을 예측하는 데 효과적입니다.

Phase 0) Read Line : first를 통해 입력 라인을 읽습니다 <LF>.

  • 명령으로 구문 분석 할 행을 읽을 때 <Ctrl-Z>(0x1A)는 <LF>(LineFeed 0x0A) 로 읽습니다.
  • A에 대한 검색하는 동안 GOTO 나 CALL이 라인을 읽을 때 : 라벨, <Ctrl-Z>그 자체로 처리됩니다 -이되어 하지 로 변환<LF>

1 단계) 확장 비율 :

  • 더블 %%은 싱글로 대체됩니다%
  • 인수의 확장 ( %*, %1, %2, 등)
  • %var%var의 존재하지 않는 경우의 확장으로 대체하십시오
  • 처음 <LF>에는 %var%확장 되지 않은 줄이 잘립니다.
  • 전체 설명을 보려면 dbenham Same thread : Percent Phase 에서 전반부를 읽으십시오 .

2 단계) 특수 문자 처리, 토큰 화 및 캐시 된 명령 블록 작성 : 따옴표, 특수 문자, 토큰 구분 기호 및 캐럿 이스케이프와 같은 항목의 영향을받는 복잡한 프로세스입니다. 다음은이 과정의 근사치입니다.

이 단계에서 중요한 개념이 있습니다.

  • 토큰은 단순히 단위로 취급되는 문자열입니다.
  • 토큰은 토큰 구분 기호로 구분됩니다. 표준 토큰 구분 기호가 <space> <tab> ; , = <0x0B> <0x0C><0xFF>
    연속 토큰 구분 기호가 하나로 취급됩니다. 토큰 구분 기호 사이에 빈 토큰이 없습니다.
  • 따옴표로 묶인 문자열 내에 토큰 구분 기호가 없습니다. 인용 된 전체 문자열은 항상 단일 토큰의 일부로 취급됩니다. 단일 토큰은 따옴표로 묶인 문자열과 따옴표가없는 문자의 조합으로 구성 될 수 있습니다.

다음 단계는 상황에 따라이 단계에서 특별한 의미를 가질 수 있습니다. <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

각 문자를 왼쪽에서 오른쪽으로보십시오.

  • 그런 <CR>다음 존재하지 않는 것처럼 제거하십시오 (이상한 리디렉션 동작 제외 )
  • 캐럿 ( ^)이면 다음 문자가 이스케이프되고 탈출 캐럿이 제거됩니다. 이스케이프 된 문자는 모든 특수 의미를 잃습니다 (제외 <LF>).
  • 따옴표 ( ")이면 따옴표 플래그를 토글하십시오. 인용 플래그 만, 활성화 된 경우 "<LF>특별하다. 다른 모든 따옴표는 다음 따옴표가 따옴표 플래그를 끌 때까지 특별한 의미를 잃습니다. 닫는 따옴표를 이스케이프 할 수 없습니다. 인용 된 모든 문자는 항상 동일한 토큰 내에 있습니다.
  • <LF>항상 따옴표 플래그를 끕니다. 다른 동작은 상황에 따라 다르지만 따옴표는의 동작을 변경하지 않습니다 <LF>.
    • 탈출 <LF>
      • <LF> 벗겨졌다
      • 다음 문자는 이스케이프됩니다. 행 버퍼의 끝에서 다음 행을 읽고 1 단계와 1.5 단계에서 처리 한 후 다음 문자를 이스케이프하기 전에 현재 행에 추가합니다. 다음 문자가 <LF>이면 리터럴로 취급되므로이 프로세스는 재귀 적이 지 않습니다.
    • <LF>괄호 안에 있지 않은 탈출
      • <LF> 가 제거되고 현재 행의 구문 분석이 종료됩니다.
      • 라인 버퍼에 남아있는 문자는 단순히 무시됩니다.
    • <LF>FOR IN 괄호 안에 블록에서 이스케이프 처리되지 않음
      • <LF> 로 변환 <space>
      • 라인 버퍼의 끝에서 다음 라인을 읽고 현재 라인에 추가합니다.
    • <LF>괄호로 묶은 명령 블록 내에서 이스케이프 처리되지 않음
      • <LF>로 변환되고 <LF><space>, 상기는 <space>커맨드 블록의 다음 라인의 부분으로서 취급된다.
      • 행 버퍼의 끝에서 다음 행을 읽고 공백에 추가합니다.
  • 특수 문자 & | <또는 중 하나 인 경우 >파이프, 명령 연결 및 리디렉션을 처리하기 위해이 지점에서 줄을 분할하십시오.
    • 파이프 ( |) 의 경우 각면은 5.3 단계에서 특수 처리되는 별도의 명령 (또는 명령 블록)입니다.
    • 의 경우에 &, &&또는 ||명령 연결의 연결의 각 측면은 별도의 명령으로 취급된다.
    • 의 경우에 <, <<, >, 또는 >>리디렉션 리디렉션 절 일시적으로 제거되고, 분석되고, 그 후 현재의 명령의 끝 부분에 추가. 리디렉션 절은 선택적 파일 핸들 숫자, 리디렉션 연산자 및 리디렉션 대상 토큰으로 구성됩니다.
      • 리디렉션 연산자 앞에 오는 토큰이 이스케이프 처리되지 않은 단일 숫자 인 경우이 숫자는 리디렉션 할 파일 핸들을 지정합니다. 핸들 토큰을 찾을 수 없으면 출력 리디렉션의 기본값은 1 (stdout)이고 입력 리디렉션의 기본값은 0 (stdin)입니다.
  • 이 명령에 대한 첫 번째 토큰 (리디렉션을 끝으로 이동하기 전에)이로 시작 @하면 @특별한 의미를 갖습니다. ( @다른 상황에서는 특별하지 않습니다)
    • 스페셜 @이 제거됩니다.
    • ECHO가 ON이면이 명령은이 행의 다음에 연결된 명령과 함께 3 단계 에코에서 제외됩니다. @가 여는 앞에 있으면 (괄호 안에있는 전체 블록이 3 단계 에코에서 제외됩니다.
  • 프로세스 괄호 (여러 줄에 걸쳐 복합 문장 제공) :
    • 파서가 명령 토큰을 (찾지 않으면 특별하지 않습니다.
    • 파서가 명령 토큰을 찾고를 찾으면 (새로운 복합 명령문을 시작하고 괄호 카운터를 증가시킵니다.
    • 괄호 카운터가 0보다 )크면 복합 명령문 을 종료하고 괄호 카운터를 줄입니다.
    • 행 끝에 도달하고 괄호 카운터가> 0이면 다음 행이 복합 명령문에 추가됩니다 (0 단계부터 다시 시작).
    • 괄호 카운터가 0이고 구문 분석기가 명령을 찾고있는 경우 즉시 토큰 구분 기호, 특수 문자, 줄 바꾸기 또는 파일 끝이 뒤에 오는 한 명령문 )과 유사한 기능 을 수행 REM합니다.
      • 모든 특수 문자는 제외하고 의미를 잃습니다 ^(행 연결 가능)
      • 논리 행의 끝에 도달하면 전체 "명령"이 삭제됩니다.
  • 각 명령은 일련의 토큰으로 구문 분석됩니다. 첫 번째 토큰은 항상 명령 토큰으로 취급됩니다 (특별 항목 @이 제거되고 리디렉션이 끝으로 이동 한 후).
    • 명령 토큰 이전의 선행 토큰 구분 기호가 제거됩니다.
    • 명령 토큰을 구문 분석 할 때 (표준 토큰 구분 기호 외에도 명령 토큰 구분 기호로 작동
    • 후속 토큰의 처리는 명령에 따라 다릅니다.
  • 대부분의 명령은 단순히 명령 토큰 뒤에있는 모든 인수를 단일 인수 토큰으로 연결합니다. 모든 인수 토큰 분리 문자가 유지됩니다. 인수 옵션은 일반적으로 7 단계까지 구문 분석되지 않습니다.
  • 세 가지 명령은 특별한 처리를합니다-IF, FOR 및 REM
    • IF는 독립적으로 처리되는 2 ~ 3 개의 개별 부품으로 나뉩니다. IF 구성의 구문 오류는 치명적인 구문 오류를 초래합니다.
      • 비교 연산은 7 단계로 진행되는 실제 명령입니다.
        • 모든 IF 옵션은 2 단계에서 완전히 구문 분석됩니다.
        • 연속적인 토큰 구분 기호는 단일 공간으로 축소됩니다.
        • 비교 연산자에 따라 식별되는 하나 또는 두 개의 값 토큰이 있습니다.
      • True 명령 블록은 조건 이후의 명령 집합이며 다른 명령 블록과 마찬가지로 구문 분석됩니다. ELSE를 사용하려면 True 블록을 괄호로 묶어야합니다.
      • 선택적 False 명령 블록은 ELSE 이후의 명령 집합입니다. 이 명령 블록은 정상적으로 구문 분석됩니다.
      • True 및 False 명령 블록은 후속 단계로 자동으로 흐르지 않습니다. 후속 처리는 7 단계에서 제어합니다.
    • FOR는 DO 다음에 2 개로 분할됩니다. FOR 구문에서 구문 오류가 발생하면 심각한 구문 오류가 발생합니다.
      • DO를 통한 부분은 7 단계까지 진행되는 실제 FOR 반복 명령입니다.
        • 모든 FOR 옵션은 2 단계에서 완전히 구문 분석됩니다.
        • IN 괄호로 묶은 절은 <LF>로 취급 됩니다 <space>. IN 절이 구문 분석 된 후 모든 토큰이 함께 연결되어 단일 토큰을 형성합니다.
        • 이스케이프 처리되지 않은 / 따옴표없는 토큰 구분 기호는 DO를 통해 FOR 명령 전체에서 단일 공간으로 축소됩니다.
      • DO 다음 부분은 정상적으로 구문 분석되는 명령 블록입니다. DO 명령 블록의 후속 처리는 7 단계의 반복에 의해 제어됩니다.
    • 2 단계에서 감지 된 REM은 다른 모든 명령과 크게 다르게 처리됩니다.
      • 하나의 인수 토큰 만 구문 분석됩니다. 구문 분석기는 첫 번째 인수 토큰 뒤의 문자를 무시합니다.
      • REM 명령은 3 단계 출력에 나타날 수 있지만 명령이 실행되지 않고 원래 인수 텍스트가 에코됩니다. 이스케이프 캐럿은 제거되지 않습니다.
        • ^줄을 끝내는 이스케이프 처리되지 않은 것으로 끝나는 인수 토큰이 하나만 있으면 인수 토큰이 제거되고 후속 행이 구문 분석되어 REM에 추가됩니다. 토큰이 두 개 이상이거나 마지막 문자가 아닐 때까지 반복됩니다 ^.
  • 명령 토큰이로 시작 :하고 이것이 2 단계의 첫 번째 라운드 인 경우 (6 단계의 CALL로 인해 다시 시작되지 않음)
    • 토큰은 일반적으로 미 실행 레이블 로 취급됩니다 .
      • 라인의 나머지는하지만, 구문 분석 ), <, >, &그리고 |더 이상 특별한 의미가 없습니다. 줄의 나머지 부분은 레이블 "command"의 일부로 간주됩니다.
      • ^계속해서 특별합니다. 즉, 줄 연속을 사용하여 후속 줄을 레이블에 추가 할 수 있습니다.
      • 미 실행 된 라벨 이 바로 다음 명령 또는하지 않는 괄호 내의 블록 치명적인 구문 오류가 발생할 것이다 라벨 실행될 다음 행.
        • (더 이상 실행되지 않은 레이블 다음에 오는 첫 번째 명령에 특별한 의미가 없습니다 .
      • 레이블 구문 분석이 완료되면 명령이 중단됩니다. 레이블에 대해서는 후속 단계가 수행되지 않습니다
    • 2 단계에서 찾은 레이블 이 7 단계를 통해 구문 분석을 계속 하는 실행 된 레이블 로 취급 될 수있는 3 가지 예외가 있습니다 .
      • 선행 라벨이 토큰 것을 재 지정이이며,이 |파이프 나 &, &&또는 ||라인에 명령 연결은.
      • 레이블 토큰 앞에 리디렉션이 있으며 명령은 괄호 안에 있습니다.
      • 레이블 토큰은 괄호 안에있는 행의 첫 번째 명령이며 위의 행 은 실행되지 않은 레이블로 끝났습니다 .
    • 2 단계에서 실행 된 레이블 이 발견 되면 다음이 발생합니다.
      • 레이블, 인수 및 리디렉션은 모두 3 단계의 모든 에코 출력에서 ​​제외됩니다.
      • 라인에서 연결된 모든 후속 명령이 완전히 구문 분석되고 실행됩니다.
    • 에 대한 자세한 내용은 실행 된 레이블실행되지 않은 레이블 을 참조 https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405을

단계 3) 구문 분석 된 명령을 에코합니다 . 명령 블록이로 시작하지 않고 @ECHO가 이전 단계의 시작에서 ON 인 경우에만 가능합니다 .

4 단계) FOR %X변수 확장 : FOR 명령이 활성화되고 DO 이후의 명령이 처리되는 경우에만 해당됩니다.

  • 이 시점에서 일괄 처리 단계 1은 이미 같은 변수에 대한 변환 것이다 %%X%X. 명령 행은 단계 1에 대해 다른 백분율 확장 규칙을 갖습니다. 이것이 명령 행은 사용 %X하지만 배치 파일 %%X은 FOR 변수에 사용 하는 이유입니다 .
  • FOR 변수 이름은 대소 문자를 구분하지만 ~modifiers대소 문자를 구분하지 않습니다.
  • ~modifiers변수 이름보다 우선합니다. 다음 문자 ~가 수정 자 및 유효한 FOR 변수 이름이고 활성 FOR 변수 이름 인 후속 문자가있는 경우 해당 문자는 수정 자로 해석됩니다.
  • FOR 변수 이름은 전역이지만 DO 절의 컨텍스트 내에서만 사용됩니다. FOR DO 절에서 루틴이 호출되면 FOR 변수는 호출 된 루틴 내에서 확장되지 않습니다. 그러나 루틴에 자체 FOR 명령이 있으면 현재 정의 된 모든 FOR 변수가 내부 DO 명령에 액세스 할 수 있습니다.
  • FOR 변수 이름은 중첩 된 FOR 내에서 재사용 할 수 있습니다. 내부 FOR 값이 우선하지만 INNER FOR가 닫히면 외부 FOR 값이 복원됩니다.
  • 이 단계가 시작될 때 ECHO가 ON이면 FOR 변수가 확장 된 후 구문 분석 된 DO 명령을 표시하기 위해 3) 단계가 반복됩니다.

----이 시점부터 2 단계에서 식별 된 각 명령이 별도로 처리됩니다.
---- 다음 명령으로 넘어 가기 전에 한 명령에 대해 5-7 단계가 완료됩니다.

5 단계) 지연 확장 : 지연 확장이 켜져있는 경우에만 명령이 파이프의 양쪽 에있는 괄호로 묶이지 않은 블록에 없고 명령이 "네 이크"배치 스크립트 가 아닙니다 (괄호없는 스크립트 이름, CALL, 명령 연결, 또는 파이프).

  • 명령에 대한 각 토큰은 확장 지연에 대해 독립적으로 구문 분석됩니다.
    • 대부분의 명령은 둘 이상의 토큰 (명령 토큰, 인수 토큰 및 각 리디렉션 대상 토큰)을 구문 분석합니다.
    • FOR 명령은 IN 절 토큰 만 구문 분석합니다.
    • IF 명령은 비교 연산자에 따라 하나 또는 두 개의 비교 값만 구문 분석합니다.
  • 구문 분석 된 각 토큰에 대해 먼저 토큰이 포함되어 있는지 확인하십시오 !. 그렇지 않은 경우 토큰이 구문 분석되지 않으며 ^문자에 중요 합니다. 토큰에가 포함되어 있으면 !왼쪽에서 오른쪽으로 각 문자를 스캔하십시오.
    • 캐럿 ( ^) 인 경우 다음 문자에 특별한 의미가 없으면 캐럿 자체가 제거됩니다.
    • 느낌표 인 경우 다음 느낌표 (카레 트가 더 이상 표시되지 않음)를 검색하고 변수 값으로 확장하십시오.
      • 연속적인 개구부 !가 단일로 축소됩니다!
      • 페어링되지 않은 나머지 !는 제거됩니다
    • 특수 문자가 발견되지 않기 때문에이 단계에서 바르 확장은 더 이상, (심지어 "안전"입니다 <CR>또는 <LF>)
    • 더 완전한 설명을 보려면 dbenham same thread-Exclamation Point Phase 에서 후반부를 읽으십시오.

단계 5.3) 파이프 처리 : 명령이 파이프의 양쪽에있는 경우에만 파이프의
각 측면은 독립적으로 비동기 적으로 처리됩니다.

  • 명령이 cmd.exe의 내부이거나 배치 파일이거나 괄호로 묶인 명령 블록 인 경우을 통해 새 cmd.exe 스레드에서 실행 %comspec% /S /D /c" commandBlock"되므로 명령 블록이 단계를 다시 시작하지만 이번에는 명령 행 모드에서.
    • 괄호로 묶인 명령 블록 인 경우 <LF>이전과 이후에 명령이있는 모든 항목 이로 변환됩니다 <space>&. 다른 사람 <LF>은 제거됩니다.
  • 이것이 파이프 명령 처리의 끝입니다.
  • 파이프 된 코드 블록 내부에서 확장 지연이 실패하는 이유는 무엇입니까?를 참조하십시오 . 파이프 구문 분석 및 처리에 대한 자세한 내용

단계 5.5) 리디렉션 실행 : 이제 2 단계에서 검색된 리디렉션이 실행됩니다.

단계 6) CALL 처리 / 카트 배가 : 명령 토큰이 CALL이거나 첫 번째 표준 토큰 분리 문자 앞의 텍스트가 CALL 인 경우에만 해당됩니다. 더 큰 명령 토큰에서 CALL을 구문 분석하는 경우, 사용하기 전에 사용하지 않은 부분이 인수 토큰 앞에 추가됩니다.

  • 따옴표없는 인수 토큰을 스캔하십시오 /?. 토큰 내에서 발견 된 경우 6 단계를 중단하고 7 단계로 진행하여 CALL HELP for CALL이 인쇄됩니다.
  • 첫 번째를 제거하면 CALL여러 개의 CALL을 쌓을 수 있습니다
  • 모든 캐럿 두 배
  • 1, 1.5 및 2 단계를 다시 시작하지만 3 단계를 계속하지 마십시오
    • 배가 된 캐럿은 인용되지 않은 한 캐럿으로 다시 축소됩니다. 그러나 불행하게도 인용 된 캐럿은 두 배로 남아 있습니다.
    • 1 단계가 약간 변경됨
      • 1.2 또는 1.3 단계의 확장 오류는 CALL을 중단하지만 오류는 치명적이지 않습니다. 일괄 처리가 계속됩니다.
    • 2 단계 작업이 약간 변경됨
      • 2 단계의 첫 번째 라운드에서 발견되지 않은 새로 표시되고 인용되지 않은 이스케이프되지 않은 리디렉션이 감지되지만 실제로 리디렉션을 수행하지 않고 제거됩니다 (파일 이름 포함).
      • 줄 끝에서 새로 표시되고 인용되지 않은 이스케이프되지 않은 캐럿은 줄 연속을 수행하지 않고 제거됩니다.
      • 다음 중 하나라도 감지되면 오류없이 통화가 중단됩니다.
        • 인용되지 않은, 이스케이프되지 않은 &또는|
        • 결과 명령 토큰은 인용되지 않고 이스케이프되지 않은 것으로 시작합니다 (
        • 제거 된 CALL이 시작된 후 첫 번째 토큰 @
      • 결과 명령이 겉보기에 유효한 IF 또는 FOR 인 경우 내부 또는 외부 명령으로 인식 IF되거나 FOR인식되지 않는 오류와 함께 실행이 실패 합니다.
      • 결과 명령 토큰이로 시작하는 레이블 인 경우 2 단계의이 2 차 라운드에서 CALL이 중단되지 않습니다 :.
  • 결과 명령 토큰이 CALL 인 경우 6 단계를 다시 시작하십시오 (CALL이 더 이상 없을 때까지 반복).
  • 결과 명령 토큰이 배치 스크립트 또는 : label 인 경우 CALL 실행은 나머지 6 단계에서 완전히 처리됩니다.
    • 호출이 완료 될 때 올바른 위치에서 실행을 재개 할 수 있도록 호출 스택에서 현재 배치 스크립트 파일 위치를 푸시하십시오.
    • 모든 결과 토큰을 사용하여 CALL에 대해 % 0, % 1, % 2, ... % N 및 % * 인수 토큰을 설정하십시오.
    • 명령 토큰이로 시작하는 레이블 :인 경우
      • 5 단계를 다시 시작하십시오. 이는 : label이 호출되는 대상에 영향을 줄 수 있습니다. 그러나 % 0 등 토큰이 이미 설정되었으므로 CALLed 루틴에 전달되는 인수는 변경되지 않습니다.
      • GOTO 레이블을 실행하여 서브 루틴의 시작 부분에 파일 포인터를 배치하십시오 (: label 다음에 오는 다른 토큰은 무시하십시오) GOTO 작동 방식에 대한 규칙은 7 단계를 참조하십시오.
    • 지정된 배치 스크립트로의 다른 전송 제어.
    • CALLed : label 또는 script의 실행은 EXIT / B 또는 파일 끝에 도달 할 때까지 계속됩니다.이 시점에서 CALL 스택이 팝업되고 저장된 파일 위치에서 실행이 재개됩니다.
      콜드 스크립트 또는 : labels에 대해서는 7 단계가 실행되지 않습니다.
  • 그렇지 않으면 6 단계의 결과는 실행을 위해 7 단계로 넘어갑니다.

단계 7) 실행 : 명령이 실행됩니다

  • 7.1-내부 명령 실행 -명령 토큰이 인용 된 경우이 단계를 건너 뜁니다. 그렇지 않으면 내부 명령을 구문 분석하고 실행하십시오.
    • 인용되지 않은 명령 토큰이 내부 명령을 나타내는 지 확인하기 위해 다음 테스트가 수행됩니다.
      • 명령 토큰이 내부 명령과 정확히 일치하면 실행하십시오.
      • 그렇지 않으면 처음 발생하기 전에 명령 토큰을 끊 + / [ ] <space> <tab> , ;거나 =
        이전 텍스트가 내부 명령 인 경우 해당 명령을 기억하십시오.
        • 명령 행 모드에 있거나 명령이 괄호로 묶인 블록, IF true 또는 false 명령 블록, FOR DO 명령 블록에 있거나 명령 연결과 관련된 경우 내부 명령을 실행하십시오.
        • 그렇지 않으면 (일괄 처리 모드에서 독립 실행 형 명령이어야 함) 현재 폴더와 PATH에서 기본 이름이 원래 명령 토큰과 일치하는 .COM, .EXE, .BAT 또는 .CMD 파일을 검색합니다.
          • 첫 번째로 일치하는 파일이 .BAT 또는 .CMD 인 경우 7.3.exec로 이동하여 해당 스크립트를 실행하십시오.
          • 그렇지 않으면 (일치를 찾을 수 없거나 첫 번째로 일치하는 것이 .EXE 또는 .COM 임) 기억 된 내부 명령을 실행하십시오.
      • 그렇지 않으면 첫 번째 발생 전에 명령 토큰을 끊 . \거나 :
        이전 텍스트가 내부 명령이 아닌 경우 7.2로
        가십시오. 그렇지 않으면 이전 텍스트가 내부 명령 일 수 있습니다. 이 명령을 기억하십시오.
      • 첫 번째 발생 전에 명령 토큰을 중단하십시오. + / [ ] <space> <tab> , ;또는 =
        이전 텍스트가 기존 파일의 경로 인 경우 7.2로
        이동하여 기억 된 내부 명령을 실행하십시오.
    • 더 큰 명령 토큰에서 내부 명령을 구문 분석하면 명령 토큰의 사용되지 않은 부분이 인수 목록에 포함됩니다.
    • 명령 토큰이 내부 명령으로 구문 분석되었다고해서 성공적으로 실행되는 것은 아닙니다. 각 내부 명령에는 인수와 옵션을 구문 분석하는 방법과 허용되는 구문에 대한 자체 규칙이 있습니다.
    • 모든 내부 명령 /?은 감지 된 경우 기능을 수행하는 대신 도움말을 인쇄 합니다. /?인수의 어느 곳에 나 나타나는지 대부분을 인식 합니다. 그러나 ECHO 및 SET와 같은 몇 가지 명령은 첫 번째 인수 토큰이로 시작하는 경우에만 도움말을 인쇄합니다 /?.
    • SET에는 몇 가지 흥미로운 의미가 있습니다.
      • 변수 이름과 확장명을 활성화하기 전에 SET 명령에 따옴표가 있으면
        set "name=content" ignored -> value = content
        첫 번째 등호와 마지막 따옴표 사이의 텍스트가 내용으로 사용됩니다 (첫 번째 등호와 마지막 따옴표는 제외). 마지막 따옴표 뒤의 텍스트는 무시됩니다. 등호 뒤에 따옴표가 없으면 나머지 줄이 내용으로 사용됩니다.
      • SET 명령에 이름
        set name="content" not ignored -> value = 앞에 따옴표가 없으면 "content" not ignored
        등호 다음에 나오는 줄의 나머지 부분이 모든 따옴표를 포함하여 내용으로 사용됩니다.
    • IF 비교가 평가되고 조건이 참인지 거짓인지에 따라 5 단계부터 시작하여 적절한 구문 분석 된 종속 명령 블록이 처리됩니다.
    • FOR 명령의 IN 절이 적절하게 반복됩니다.
      • 이것이 명령 블록의 출력을 반복하는 FOR / F 인 경우 :
        • IN 절은 CMD / C를 통해 새 cmd.exe 프로세스에서 실행됩니다.
        • 명령 블록은 두 번째로 전체 구문 분석 프로세스를 거쳐야하지만 이번에는 명령 행 컨텍스트에서 수행해야합니다.
        • ECHO는 ON으로 시작하고 지연된 확장은 일반적으로 비활성화됩니다 (레지스트리 설정에 따라)
        • 하위 cmd.exe 프로세스가 종료되면 IN 절 명령 블록에 의해 작성된 모든 환경 변경 사항이 유실됩니다.
      • 각 반복에 대해 :
        • FOR 변수 값이 정의됩니다
        • 그런 다음 이미 구문 분석 된 DO 명령 블록이 단계 4부터 시작하여 처리됩니다.
    • GOTO는 다음 로직을 사용하여 : label을 찾습니다.
      • 첫 번째 인수 토큰에서 레이블이 구문 분석됩니다.
      • 다음 번에 나타나는 레이블에 대해 스크립트를 스캔합니다.
        • 스캔은 현재 파일 위치에서 시작합니다
        • 파일의 끝에 도달하면 스캔은 파일의 시작 부분으로 되돌아 가고 원래 시작점으로 계속됩니다.
      • 발견 된 레이블이 처음 발견되면 스캔이 중지되고 파일 포인터는 레이블 바로 다음 줄로 설정됩니다. 이 시점부터 스크립트 실행이 재개됩니다. 성공적인 true GOTO는 FOR 루프를 포함하여 구문 분석 된 코드 블록을 즉시 중단합니다.
      • 레이블을 찾을 수 없거나 레이블 토큰이 없으면 GOTO가 실패하고 오류 메시지가 인쇄되며 호출 스택이 팝업됩니다. 이것은 GOTO를 따르는 현재 명령 블록에서 이미 구문 분석 된 명령이 여전히 실행되지만 CALLer의 컨텍스트 (EXIT / B 뒤에 존재하는 컨텍스트)를 제외하고는 EXIT / B로 효과적으로 작동합니다.
      • 레이블 구문 분석에 사용되는 규칙에 대한 자세한 설명은 https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 을 참조 하십시오 .
    • RENAME 및 COPY는 모두 소스 및 대상 경로에 와일드 카드를 허용합니다. 그러나 Microsoft는 와일드 카드가 작동하는 방식, 특히 대상 경로에 대해 끔찍한 작업을 수행합니다. 유용한 와일드 카드 규칙 세트 는 Windows RENAME 명령이 와일드 카드를 어떻게 해석합니까? 에서 찾을 수 있습니다 .
  • 7.2-볼륨 변경 실행 -그렇지 않으면 명령 토큰이 따옴표로 시작하지 않고 정확히 두 문자 길이이고 두 번째 문자가 콜론 인 경우 볼륨을 변경하십시오.
    • 모든 인수 토큰은 무시됩니다
    • 첫 번째 문자로 지정된 볼륨을 찾을 수 없으면 오류와 함께 중단하십시오
    • ::SUBST를 사용하여 볼륨을 정의하지 않는 한의 명령 토큰 은 항상 오류가 발생합니다. SUBST를 사용하여 볼륨을 정의하는 ::
      경우 ::볼륨이 변경되면 레이블로 처리되지 않습니다.
  • 7.3-외부 명령 실행 -그렇지 않으면 명령을 외부 명령 으로 취급하십시오.
    • 커맨드 라인 모드와 명령에서 인용되지 않고 볼륨 사양, 공백로 시작하지 않으면, ,, ;, =또는 +다음의 첫 번째 항목에서 토큰 명령 중단 <space> , ;또는 =과 (들) 토큰 인수에 나머지를 앞에 추가.
    • 명령 토큰의 두 번째 문자가 콜론 인 경우 첫 번째 문자로 지정된 볼륨을 찾을 수 있는지 확인하십시오.
      볼륨을 찾을 수 없으면 오류와 함께 중단하십시오.
    • 배치 모드에서 명령 토큰이로 시작 하면 :7.4로 이동
      하십시오. 레이블 토큰이로 시작하면 ::SUBST를 사용하여 볼륨을 정의하지 않는 한 이전 단계에서 오류가 발생하여이 단계에 도달하지 못합니다 ::.
    • 실행할 외부 명령을 식별하십시오.
      • 이것은 현재 볼륨, 현재 디렉토리, PATH 변수, PATHEXT 변수 및 / 또는 파일 연관을 포함 할 수있는 복잡한 프로세스입니다.
      • 유효한 외부 명령을 식별 할 수 없으면 오류와 함께 중단하십시오.
    • 커맨드 라인 모드로 시작 토큰 명령의 경우 :, 다음 고토 7.4
      명령 토큰으로 시작하지 않는 한 이전 단계가 오류로 중단 한 것 때문에 거의 도달되지 않도록주의 ::하고, SUBST를위한 볼륨을 정의하는 데 사용 ::하고, 전체 명령 토큰은 외부 명령의 유효한 경로입니다.
    • 7.3.exec- 외부 명령을 실행합니다.
  • 7.4-레이블 무시-명령 토큰이로 시작하면 명령과 모든 인수를 무시하십시오 :.
    7.2 및 7.3의 규칙에 따라 라벨이이 지점에 도달하지 못할 수 있습니다.

커맨드 라인 파서 :

다음을 제외하고 BatchLine-Parser와 같이 작동합니다.

1 단계) 확장 비율 :

  • 아니 %*, %1등 인수 확장
  • var가 정의되지 않은 경우 %var%변경되지 않은 상태로 유지됩니다.
  • 특별한 취급은 없습니다 %%. var = content이면로 %%var%%확장됩니다 %content%.

3 단계) 파싱 된 명령 에코

  • 2 단계 이후에는 수행되지 않으며 FOR DO 명령 블록의 4 단계 이후에만 수행됩니다.

5 단계) 지연 확장 : DelayedExpansion이 활성화 된 경우에만

  • var가 정의되지 않은 경우 !var!변경되지 않은 상태로 유지됩니다.

7 단계) 명령 실행

  • : label을 호출하거나 GOTO하려고하면 오류가 발생합니다.
  • 7 단계에서 이미 설명한 바와 같이, 실행 된 레이블은 다른 시나리오에서 오류가 발생할 수 있습니다.
    • 배치 실행 레이블은 다음으로 시작하는 경우에만 오류를 일으킬 수 있습니다. ::
    • 명령 행에서 실행 된 레이블은 거의 항상 오류를 발생시킵니다

정수 값 파싱

cmd.exe가 문자열의 정수 값을 구문 분석하는 규칙에는 여러 가지가 있으며 규칙이 일치하지 않습니다.

  • SET /A
  • IF
  • %var:~n,m% (가변 부분 문자열 확장)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

이러한 규칙에 대한 자세한 내용은 CMD.EXE가 숫자를 구문 분석하는 방법에 대한 규칙 에서 찾을 수 있습니다.


cmd.exe 구문 분석 규칙을 개선하려는 사람은 DosTips 포럼 에 문제를보고하고 제안 할 수 있는 토론 주제가 있습니다.

희망이 도움이
월 에릭 (젭) - 단계의 원래 저자 및 발견을
데이브 벤함 (Benham) (dbenham) - 많은 추가 콘텐츠 및 편집


4
안녕하세요 jeb, 귀하의 통찰력에 감사드립니다 ... 이해하기 어려울 수 있지만, 그것을 통해 생각하려고 노력할 것입니다! 많은 테스트를 수행 한 것 같습니다! 번역 해 주셔서 감사합니다 ( administrator.de/… )
Benoit

2
배치 단계 5)-%% a는 1 단계에서 이미 % a로 변경되었으므로 for-loop 확장은 실제로 % a를 확장합니다. 또한 아래 답변에 배치 1 단계에 대한 자세한 설명을 추가했습니다 (편집 권한이 없음)
dbenham

3
Jeb-아마도 0 단계를 6 단계로 옮기고 결합시킬 수 있습니까? 그것은 나에게 더 합리적입니까, 아니면 그렇게 분리 된 이유가 있습니까?
dbenham

1
@aschipfl-해당 섹션을 업데이트했습니다. 는 )정말 거의 같은 기능을 수행 REM괄호 카운터가 명령 줄에서이 두 0 시도가있을 때 명령 ) Ignore thisecho OK & ) Ignore this
dbenham

1
@aschipfl 네 맞습니다. 따라서 때때로 'set "var = % expr %"를 보게됩니다! '마지막 느낌표 제거하지만, 힘의 5 단계됩니다

62

명령 창에서 명령을 호출 할 때 명령 행 인수의 토큰 화 cmd.exe( "쉘")가 수행되지 않습니다 . 대부분 토큰 화는 새로 형성된 프로세스의 C / C ++ 런타임에 의해 수행되지만, 반드시 그렇지는 않습니다. 예를 들어, 새 프로세스가 C / C ++로 작성되지 않았거나 새 프로세스가 무시 argv하고 처리 하기로 선택한 경우 자체 원시 명령 줄 (예 : GetCommandLine ()). OS 수준에서 Windows는 토큰 화되지 않은 명령 줄을 단일 문자열로 새 프로세스에 전달합니다. 이것은 대부분의 * nix 쉘과 대조적으로, 쉘은 인수를 새로 형성된 프로세스로 전달하기 전에 일관되고 예측 가능한 방식으로 토큰 화합니다. 이 모든 것은 개별 프로그램이 종종 인수 토큰 화를 자신의 손에 갖기 때문에 Windows의 다른 프로그램에서 매우 다양한 인수 토큰 화 동작을 경험할 수 있음을 의미합니다.

그것이 무정부 상태처럼 들린다면, 그것은 일종입니다. 그러나 많은 Windows 프로그램 Microsoft C / C ++ 런타임 argv을 사용하기 때문에 MSVCRT가 인수를 토큰 화하는 방법 을 이해 하는 것이 일반적으로 유용 할 수 있습니다 . 발췌문은 다음과 같습니다.

  • 인수는 공백 또는 탭인 공백으로 구분됩니다.
  • 큰 따옴표로 묶인 문자열은 포함 된 공백에 관계없이 단일 인수로 해석됩니다. 인용 된 문자열을 인수에 포함 할 수 있습니다. 캐럿 (^)은 이스케이프 문자 또는 구분자로 인식되지 않습니다.
  • 큰 따옴표 (\)가 앞에 오는 큰 따옴표는 리터럴 큰 따옴표 ( ")로 해석됩니다.
  • 백 슬래시는 큰 따옴표가 바로 앞에 오지 않는 한 문자 그대로 해석됩니다.
  • 짝수의 백 슬래시 다음에 큰 따옴표가 있으면, 백 슬래시 쌍 (\)마다 백 슬래시 () 하나가 argv 배열에 배치되고 큰 따옴표 ( ")는 문자열 구분 기호로 해석됩니다.
  • 홀수의 백 슬래시 뒤에 큰 따옴표가 있으면, 백 슬래시 쌍 (\)마다 백 슬래시 () 하나가 argv 배열에 배치되고 큰 따옴표는 나머지 백 슬래시에 의해 이스케이프 시퀀스로 해석됩니다. argv에 배치 할 리터럴 큰 따옴표 ( ")

Microsoft "일괄 처리 언어"( .bat)는이 무질서한 환경에서도 예외가 아니며 토큰 화 및 이스케이프에 대한 고유 한 규칙을 개발했습니다. 또한 cmd.exe의 명령 프롬프트는 인수를 새로 실행중인 프로세스로 전달하기 전에 명령 줄 인수 (대부분 변수 대체 및 이스케이프)의 일부 전처리를 수행하는 것처럼 보입니다. 이 페이지의 jeb 및 dbenham의 탁월한 답변에서 배치 언어 및 cmd 이스케이프에 대한 하위 레벨 세부 정보를 읽을 수 있습니다.


C로 간단한 명령 행 유틸리티를 빌드하고 테스트 케이스에 대해 설명합니다.

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

(참고 : argv [0]은 항상 실행 파일의 이름이며 간결성을 위해 아래에서는 생략합니다. Windows XP SP3에서 테스트되었습니다. 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]

그리고 내 자신의 몇 가지 테스트 :

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

답변 주셔서 감사합니다. TinyPerl이 프로그램이 출력하는 것을 출력하지 않으며, 포스트 프로세싱을 어떻게 [a "b" c]할 수 있는지 이해하기가 어렵습니다 [a "b] [c].
Benoit

이제 그것에 대해 생각하면, 명령 행의 토큰 화는 C 런타임에 의해 수행 될 것입니다. 실행 파일은 C 런타임을 사용하지 않도록 작성 될 수 있습니다.이 경우 명령 줄을 그대로 처리하고 자체 토큰 화 (원하는 경우)를 수행해야한다고 생각합니다. 응용 프로그램이 C 런타임을 사용하는 경우 argc 및 argv를 무시하고 Win32와 같은 원시 명령 줄을 얻을 수 GetCommandLine있습니다. TinyPerl은 argv를 무시하고 단순히 자체 규칙에 따라 원시 명령 행을 토큰 화합니다.
Mike Clark

4
"Win32의 관점에서 볼 때 명령 줄은 새로운 프로세스의 주소 공간에 복사되는 문자열 일뿐입니다. 시작 프로세스와 새 프로세스가이 문자열을 해석하는 방식은 규칙이 아니라 규칙에 따라 결정됩니다." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
정말 좋은 답변 감사합니다. 그것은 내 의견으로는 많은 것을 설명합니다. 그리고 그것은 왜 내가 Windows와 함께 작동하는 것을 정말로 간절히 찾는 이유를 설명합니다…
Benoit

내가 발견 Win32 C ++ 프로그램의 경우 명령 줄에서 argv로 변환하는 동안 백 슬래시와 따옴표와 관련하여 . 백 슬래시 수는 마지막 백 슬래시 다음에 dblquote가 올 때 2로만 나뉘며, 이전에 짝수의 백 슬래시가 있으면 dblquote는 문자열을 종료합니다.
Benoit

47

확장 규칙 백분율

다음은 jeb의 답변 에서 1 단계에 대한 확장 설명입니다 (일괄 처리 모드와 명령 행 모드 모두에 대해 유효 함).

1 단계) 비율 확대 왼쪽에서 시작은, 각 문자를 스캔 %하거나 <LF>. 발견되면

  • 1.05 (에서 줄 잘라 내기 <LF>)
    • 문자의 경우 <LF>다음
      • 선의 나머지 부분을 <LF>위에서부터 삭제 (무시)
      • Goto Phase 1.5 (스트립 <CR>)
    • 그렇지 않으면 캐릭터는이어야 %하므로 1.1로 진행하십시오.
  • 1.1 (탈출 % 명령 행 모드 인 경우 )을 건너 뜁니다.
    • 배치 모드와 다른 다음에 경우 %다음
      교체%% 단일로 %스캔 계속
  • 1.2 (확장 인수) 명령 행 모드 인 경우 건너 뜁니다.
    • 그렇지 않으면 배치 모드 인 경우
      • 뒤에 * 및 명령 확장이 후 사용할 수 있습니다
        바꾸기 %*모든 명령 행 인수의 텍스트 (인수가없는 경우 아무것도 교체) 및 스캔 계속합니다.
      • 그렇지 않으면 그렇지 않으면 <digit>
        교체 %<digit>인수 값 (undefined 인 경우 아무것도 대체) 및 스캔 계속합니다.
      • 그렇지 않으면 그렇지 않으면 ~ 명령 확장이 활성화
        • 필요한 다음 인수 수식의 선택 유효 목록 다음에 경우 <digit>다음
          교체 %~[modifiers]<digit>및 스캔 계속 : 수정 된 인수 값 (수정이 정의되지 않은 정의되지 또는 $ PATH를 지정한 경우 경우 아무것도 교체).
          참고 : 수정자는 대소 문자를 구분하지 않으며 $ PATH를 제외하고 어떤 순서로든 여러 번 나타날 수 있습니다. 수정자는 한 번만 나타날 수 있으며 마지막 수정 자 여야합니다.<digit>
        • 그렇지 않으면 수정되지 않은 인수 구문으로 인해 치명적인 오류발생합니다. 구문 분석 된 모든 명령이 중단되고 일괄 처리 모드 인 경우 일괄 처리가 중단됩니다!
  • 1.3 (확장 변수)
    • 그렇지 않으면 명령 확장이 비활성화 된 경우
      다음 문자열 을 살펴보고% 버퍼 또는 끝을 VAR이라고 부릅니다 (빈 목록 일 수 있음)
      • 다음 문자가 % 다음
        • VAR이 정의되면
          교체 %VAR%VAR의 값으로 스캔 계속
        • 그렇지 않으면 배치 모드 인 경우
          제거 %VAR%및 스캔 계속
        • 다른 고토 1.4
      • 다른 고토 1.4
    • 그렇지 않으면 명령 확장이 활성화 된 경우
      다음 문자열 을 보고 % :버퍼의 앞 또는 끝을 끊고 VAR (빈 목록 일 수 있음)을 호출하십시오. VAR이 이전에 중단 :되고 후속 문자가 %포함 되면: VAR의 마지막 문자 되고 이전에 중단 %됩니다.
      • 다음 문자가 % 다음
        • VAR이 정의되면
          교체 %VAR%VAR의 값으로 스캔 계속
        • 그렇지 않으면 배치 모드 인 경우
          제거%VAR%및 스캔 계속
        • 다른 고토 1.4
      • 다음 캐릭터가 아니면 : 다음
        • VAR이 정의되지 않은 경우
          • 배치 모드 인 경우
            제거%VAR: 하고 스캔을 계속 .
          • 다른 고토 1.4
        • 다음 캐릭터가 아니면 ~ 다음
          • 다음 문자열이 패턴과 일치 [integer][,[integer]]%하면
            바꾸기%VAR:~[integer][,[integer]]% (아마도 빈 문자열 결과) 스캔 계속 VAR 값의 하위 문자열로.
          • 다른 고토 1.4
        • 그 밖에 다음 경우 =또는 *=다음
          잘못된 변수 검색 및 구문 교체가 제기 치명적인 오류 : 모든 명령이 중단되는 구문 분석 및 일괄 처리 중단한다 경우 배치 모드!
        • 문자의 다음 문자열의 패턴과 일치하는 다른 경우 [*]search=[replace]%검색을 제외한 모든 문자 집합을 포함 할 수있다, =그리고 제외한 모든 문자 집합을 포함 할 수있다 대체 %다음을,
          교체 %VAR:[*]search=[replace]%(아마도 빈 문자열의 결과로) 검색을 수행 한 후 VAR의 값과 교체하고 계속을 주사
        • 다른 고토 1.4
  • 1.4 (스트립 %)
    • 그렇지 않으면 배치 모드 인 경우
      제거% 후 다음 문자부터 시작하여 스캔을 계속하십시오.%
    • 그렇지 않으면 선행을 %보존하고 보존 된 선행 후 다음 문자부터 스캔을 계속하십시오.%

위는 왜이 배치를 설명하는 데 도움이됩니다.

@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

다음과 같은 결과를 제공합니다.

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

참고 1 1-1 단계는 REM 문을 인식하기 전에 발생합니다. 이는 인수 확장 구문이 유효하지 않거나 변수 검색 및 대체 구문이 유효하지 않은 경우 발언으로 인해 치명적인 오류가 발생할 수 있음을 의미하므로 매우 중요합니다!

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

주 2- % 구문 분석 규칙의 또 다른 흥미로운 결과 : 이름에 :를 포함하는 변수를 정의 할 수 있지만 명령 확장을 비활성화하지 않으면 확장 할 수 없습니다. 한 가지 예외가 있습니다. 명령 확장을 사용하는 동안 끝에 단일 콜론을 포함하는 변수 이름을 확장 할 수 있습니다. 그러나 콜론으로 끝나는 변수 이름에 대해서는 서브 스트링 또는 검색 및 바꾸기 조작을 수행 할 수 없습니다. 아래 배치 파일 (jeb 제공)에서이 동작을 보여줍니다.

@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%

주 3 -jeb가 자신의 게시물에 제시 한 구문 분석 규칙 순서의 흥미로운 결과 : 찾기 및 바꾸기 지연된 확장으로 찾기 및 바꾸기를 수행 할 때 찾기 및 바꾸기 용어의 특수 문자는 이스케이프되거나 인용되어야합니다. 그러나 확장 률은 상황이 다릅니다. 찾기 용어를 이스케이프해서는 안됩니다 (인용 할 수 있음). 퍼센트 교체 문자열은 의도에 따라 이스케이프 또는 따옴표가 필요하거나 필요하지 않을 수 있습니다.

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

지연된 확장 규칙

다음은 jeb의 답변 에서 5 단계에 대한 확장되고보다 정확한 설명입니다 (배치 모드 및 명령 행 모드 모두에 대해 유효 함).

5 단계) 지연된 확장

다음 조건 중 하나라도 해당되면이 단계를 건너 뜁니다.

  • 지연 확장이 비활성화되었습니다.
  • 명령은 파이프 양쪽의 괄호 안에 있습니다.
  • 들어오는 명령 토큰은 "네이 키드 (naked)"배치 스크립트로 CALL, 괄호로 묶은 블록, 모든 형태의 명령 연결 ( &, &&또는 ||) 또는 파이프 와 관련이 없음을 의미합니다 |.

지연된 확장 프로세스는 토큰에 독립적으로 적용됩니다. 명령에는 여러 개의 토큰이있을 수 있습니다.

  • 명령 토큰. 대부분의 명령에서 명령 이름 자체는 토큰입니다. 그러나 일부 명령에는 5 단계에 대한 토큰으로 간주되는 특수 영역이 있습니다.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN비교 중 하나이고 ==, equ, neq, lss, leq, gtr, 또는geq
  • 인수 토큰
  • 리디렉션 대상 토큰 (리디렉션 당 하나씩)

를 포함하지 않는 토큰은 변경되지 않습니다 !.

적어도 하나를 포함 않는 각 토큰 !에 대한 왼쪽에서 오른쪽으로, 각 문자를 스캔 ^하거나 !, 발견이라면,

  • 5.1 (캐럿 탈출) 에 필요한 !또는^ 리터럴
    • 캐릭터가 캐럿이라면 ^ 이라면
      • 제거 ^
      • 다음 문자를 스캔하여 리터럴로 유지
      • 스캔 계속
  • 5.2 (확장 변수)
    • 캐릭터가 ! 인 경우
      • 명령 확장자가 비활성화 된 경우
        다음 문자열 을 살펴 !보거나<LF> (빈리스트가 될 수 있음), 그 VAR 전화
        • 다음 문자가 ! 다음
          • VAR이 정의 된 경우
            바꾸기!VAR! VAR의 값으로 스캔 계속
          • 그렇지 않으면 배치 모드 인 경우
            제거!VAR! 및 스캔 계속
          • 다른 goto 5.2.1
        • 다른 goto 5.2.1
      • 명령 확장을 사용하는 경우 다른 다음
        , 문자의 다음 문자열을 살펴보기 전에 파괴 !, :또는 <LF>(빈리스트가 될 수 있음), 그 VAR를 호출합니다. VAR이 이전에 중단 :되고 후속 문자가 VAR의 마지막 문자 !로 포함 :되고 이전에 중단 된 경우!
        • 다음 문자 인 경우! 다음
          • VAR이 존재하는 경우,
            교체 !VAR!VAR의 값으로 스캔 계속
          • 그렇지 않으면 배치 모드 인 경우
            제거 !VAR!및 스캔 계속
          • 다른 goto 5.2.1
        • 다음 문자 인 경우 다른: 다음
          • VAR이 정의되지 않은 경우
            • 배치 모드 인 경우
              제거 !VAR:및 스캔 계속
            • 다른 goto 5.2.1
          • 다음 캐릭터가 아니면 ~ 다음
            • 문자의 다음 문자열의 패턴과 일치하면 [integer][,[integer]]!다음 바꾸기를 !VAR:~[integer][,[integer]]!(아마도 빈 문자열 결과) 스캔 계속 VAR 값의 하위 문자열로.
            • 다른 goto 5.2.1
          • 문자의 다음 문자열의 패턴과 일치하는 다른 경우 [*]search=[replace]!검색을 제외한 모든 문자 집합을 포함 할 수있다, =그리고 제외한 모든 문자 집합을 포함 할 수있다 교체 !한 후
            교체 !VAR:[*]search=[replace]!(아마도 빈 문자열의 결과로) 검색을 수행 한 후 VAR의 값과 교체를하고 스캔 계속
          • 다른 goto 5.2.1
        • 다른 goto 5.2.1
      • 5.2.1
        • 배치 모드 인 경우 행간을 제거 !
          유지!
        • 보존 된 행간 다음에 다음 문자부터 스캔을 계속하십시오. !

3
+1, %definedVar:a=b%vs %undefinedVar:a=b%%var:~0x17,-010%형식에 대해 콜론 구문과 규칙 만 누락되었습니다.
jeb

2
좋은 점-변수 확장 섹션을 확장하여 문제를 해결했습니다. 또한 인수 확장 섹션을 확장하여 누락 된 세부 사항을 채 웁니다.
dbenham

2
jeb에서 개인 피드백을 추가로 얻은 후 콜론으로 끝나는 변수 이름에 대한 규칙을 추가하고 메모 2를 추가했습니다. 또한 메모 3이 흥미롭고 중요하다고 생각했기 때문에 추가했습니다.
dbenham

1
@aschipfl-그래, 나는 그것에 대해 더 자세히 고려하는 것을 고려했지만 그 토끼 구멍을 내려 가고 싶지 않았습니다. [정수]라는 용어를 사용할 때 의도적으로 커밋되지 않았습니다. 규칙에 CMD.EXE가 숫자를 구문 분석하는 방법에 대한 자세한 정보 가 있습니다 .
dbenham

1
변수와 같은 이름의 첫 번째 문자에 대한 예약 문자가 없는지처럼 나는 cmd를 컨텍스트에 대한 확장 규칙을 누락 %<digit>, %*또는 %~. 정의되지 않은 변수에 대한 동작이 변경됩니다. 아마도 두 번째 답변을 열어야 할 것입니다.
jeb

7

지적한 바와 같이, 명령은 μSoft 랜드에서 전체 인수 문자열을 전달받으며, 자체 사용을 위해이 인수를 별도의 인수로 구문 분석하는 것은 사용자의 몫입니다. 서로 다른 프로그램 간에는 일관성이 없으므로이 프로세스를 설명하는 규칙 세트가 없습니다. 프로그램이 사용하는 모든 C 라이브러리에 대해 각 코너 케이스를 확인해야합니다.

시스템 .bat파일에 관한 한, 다음은 그 테스트입니다.

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

이제 몇 가지 테스트를 실행할 수 있습니다. μSoft가 무엇을하려고하는지 알아낼 수 있는지 확인하십시오.

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

지금까지는 괜찮습니다. (나는 재미 떠날 것 %cmdcmdline%등을 %0지금부터.)

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

파일 이름 확장이 없습니다.

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

따옴표는 인수 분리를 방지하지만 따옴표 제거는 없습니다.

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

연속적인 큰 따옴표는 그들이 가지고 있었던 특별한 파싱 능력을 잃게합니다. @Beniot의 예 :

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

퀴즈 : 환경 변수의 값을 단일 인수 (예 %1:)로 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!

Sane 구문 분석이 영원히 중단 된 것 같습니다.

시청, 기타 추가 시도 ^, \,' , &이들에 (& C.) 문자.


% t %를 단일 인수로 전달하려면 "% t :"= \ "%"를 사용할 수 있습니다. 즉, 변수 확장에 % VAR : str = replacement % 구문을 사용하십시오. 같은 쉘 메타 문자 | 그리고 변수의 내용에서 다시 노출되지 않는 한 여전히 내용이 노출되어 쉘을
망칠

@Toughy 제 예에서는 t입니다 a "b c. 당신이 그 6 자 얻기위한 조리법이 있나요 ( a2 × 공간, ", b, 및 c)로 표시하는 %1, 안쪽 .cmd? 그래도 난 네 생각이 좋아 args "%t:"=""%":-)
bobbogo

5

위의 몇 가지 훌륭한 답변이 있지만 질문의 한 부분에 답변하십시오.

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

무슨 일이 일어나고 있는지 = 앞에 공백이 있기 때문에 변수가 올바르게 평가 %a<space>% 되면 호출 echo %a %됩니다 b.

b% c%그런 다음 나머지 부분 은 일반 텍스트 + 정의되지 않은 변수로 평가 % c%되며 입력 한대로 에코되어야 echo %a %b% c%합니다.bb% c%

변수 이름에 공백을 포함하는 기능이 계획된 '기능'보다 더 많은 감독이라고 생각합니다.


0

편집 : 허용 된 답변을 참조하십시오. 다음 내용은 잘못되었으며 TinyPerl에 명령 줄을 전달하는 방법 만 설명합니다.


따옴표에 관해서는, 나는 행동이 다음과 같은 느낌이 듭니다.

  • " 발견, 문자열 대체 (globbing)가 시작됩니다
  • 문자열 globbing이 발생할 때 :
    • 아닌 모든 캐릭터 " 는 globbed
    • "가 발견 :
      • 그 뒤에 ""(따라서 트리플" ) 큰 따옴표가 문자열에 추가됩니다
      • 뒤에 오는 경우 "(따라서 이중" ) 큰 따옴표가 문자열에 추가되고 문자열 globbing 끝
      • 다음 문자가 아닌 "경우 문자열 globbing이 종료됩니다.
    • 줄이 끝나면 문자열 글 로빙이 끝납니다.

한마디로 :

"a """ b "" c"""두 개의 문자열로 구성 a " b "하고c"

"a"", "a"""그리고 "a""""모두 같은 문자열이있는 줄 끝의 경우


토크 나이저와 문자열 globbing은 명령에 따라 다릅니다! "집합"은 "전화"또는 "if"와 다르게 작동합니다.
jeb

예, 그러나 외부 명령은 어떻습니까? cmd.exe가 항상 동일한 인수를 전달한다고 생각합니까?
Benoit

1
cmd.exe는 항상 확장 결과를 토큰이 아닌 문자열로 외부 명령에 전달합니다. 그것은 어떻게 탈출을 토큰 화하는 외부 명령에 따라, FINDSTR 용도는 다음 중 하나가 다른 것을 사용할 수 있습니다 백 슬래시

0

Microsoft는 터미널의 소스 코드를 게시했습니다. 구문 분석과 관련하여 명령 줄과 유사하게 작동 할 수 있습니다. 터미널의 구문 분석 규칙에 따라 리버스 엔지니어링 구문 분석 규칙을 테스트하는 데 관심이있는 사람이있을 수 있습니다.

소스 코드에 연결 하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.