Làm thế nào để các lệnh phiên dịch lệnh Windows (CMD.EXE) phân tích cú pháp?


142

Tôi đã chạy vào ss64.com , nơi cung cấp trợ giúp tốt về cách viết các tập lệnh bó mà Trình thông dịch lệnh Windows sẽ chạy.

Tuy nhiên, tôi đã không thể tìm thấy một lời giải thích tốt về ngữ pháp của các tập lệnh bó, cách mọi thứ mở rộng hoặc không mở rộng và làm thế nào để thoát khỏi mọi thứ.

Dưới đây là những câu hỏi mẫu mà tôi chưa thể giải quyết:

  • Hệ thống báo giá được quản lý như thế nào? Tôi đã tạo một tập lệnh TinyPerl
    ( foreach $i (@ARGV) { print '*' . $i ; }), biên dịch nó và gọi nó theo cách này:
    • my_script.exe "a ""b"" c" → đầu ra là *a "b*c
    • my_script.exe """a b c""" → xuất nó *"a*b*c"
  • Làm thế nào để echolệnh nội bộ hoạt động? Điều gì được mở rộng bên trong lệnh đó?
  • Tại sao tôi phải sử dụng for [...] %%Itrong tập lệnh, nhưng for [...] %Itrong các phiên tương tác?
  • Các nhân vật thoát là gì, và trong bối cảnh nào? Làm thế nào để thoát khỏi một dấu hiệu phần trăm? Ví dụ, làm thế nào tôi có thể lặp lại theo %PROCESSOR_ARCHITECTURE%nghĩa đen? Tôi thấy rằng echo.exe %""PROCESSOR_ARCHITECTURE%hoạt động, có một giải pháp tốt hơn?
  • Làm thế nào để cặp đôi %phù hợp? Thí dụ:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Làm thế nào để tôi đảm bảo một biến chuyển đến một lệnh dưới dạng một đối số nếu bao giờ biến này chứa dấu ngoặc kép?
  • Làm thế nào các biến được lưu trữ khi sử dụng setlệnh? Ví dụ, nếu tôi làm set a=a" bvà sau đó echo.%a%tôi có được a" b. Tuy nhiên, nếu tôi sử dụng echo.exetừ UnxUtils, tôi sẽ nhận được a b. Làm thế nào đến %a%mở rộng theo một cách khác nhau?

Cảm ơn bạn cho đèn của bạn.


Rob van der Woude có một kịch bản Batch tuyệt vời và tham chiếu dấu nhắc Windows Command trên trang web của anh ấy.
JBRWilkinson

Câu trả lời:


200

Chúng tôi đã thực hiện các thí nghiệm để điều tra ngữ pháp của các tập lệnh bó. Chúng tôi cũng đã nghiên cứu sự khác biệt giữa chế độ dòng lệnh và dòng lệnh.

Trình phân tích cú pháp hàng loạt:

Dưới đây là tổng quan ngắn gọn về các giai đoạn trong trình phân tích cú pháp dòng tệp bó:

Giai đoạn 0) Đọc dòng:

Giai đoạn 1) Mở rộng phần trăm:

Giai đoạn 2) Xử lý các ký tự đặc biệt, mã thông báo và xây dựng khối lệnh được lưu trong bộ nhớ cache: Đây là một quy trình phức tạp bị ảnh hưởng bởi những thứ như dấu ngoặc kép, ký tự đặc biệt, dấu phân cách mã thông báo và thoát dấu mũ.

Giai đoạn 3) Báo lại (các) lệnh được phân tích cú pháp Chỉ khi khối lệnh không bắt đầu @và ECHO được BẬT khi bắt đầu bước trước.

Giai đoạn 4) %XMở rộng biến FOR : Chỉ khi lệnh FOR hoạt động và các lệnh sau khi DO đang được xử lý.

Giai đoạn 5) Mở rộng bị trì hoãn: Chỉ khi bật mở rộng bị trì hoãn

Giai đoạn 5.3) Xử lý đường ống: Chỉ khi các lệnh nằm ở hai bên của đường ống

Giai đoạn 5.5) Thực hiện chuyển hướng:

Giai đoạn 6) Xử lý CALL / Nhân đôi Caret: Chỉ khi mã thông báo lệnh là GỌI

Giai đoạn 7) Thực thi: Lệnh được thực thi


Dưới đây là chi tiết cho từng giai đoạn:

Lưu ý rằng các giai đoạn được mô tả dưới đây chỉ là một mô hình về cách hoạt động của trình phân tích cú pháp hàng loạt. Các nội bộ cmd.exe thực tế có thể không phản ánh các giai đoạn này. Nhưng mô hình này có hiệu quả trong việc dự đoán hành vi của các tập lệnh bó.

Giai đoạn 0) Dòng đọc: Đọc dòng đầu vào qua đầu tiên <LF>.

  • Khi đọc một dòng được phân tích cú pháp dưới dạng lệnh, <Ctrl-Z>(0x1A) được đọc là <LF>(LineFeed 0x0A)
  • Khi GOTO hoặc CALL đọc các dòng trong khi quét nhãn : <Ctrl-Z>, được coi là chính nó - nó không được chuyển đổi thành<LF>

Giai đoạn 1) Mở rộng phần trăm:

  • Một đôi %%được thay thế bằng một%
  • Mở rộng đối số ( %*, %1, %2, vv)
  • Mở rộng %var%, nếu var không tồn tại thay thế nó bằng không có gì
  • Dòng đầu tiên bị cắt ngắn <LF>không nằm trong phạm vi %var%mở rộng
  • Để được giải thích đầy đủ, hãy đọc nửa đầu của phần này từ dbenham Cùng một chủ đề: Phần trăm pha

Giai đoạn 2) Xử lý các ký tự đặc biệt, mã thông báo và xây dựng khối lệnh được lưu trong bộ nhớ cache: Đây là một quy trình phức tạp bị ảnh hưởng bởi những thứ như dấu ngoặc kép, ký tự đặc biệt, dấu phân cách mã thông báo và thoát dấu mũ. Những gì sau đây là một xấp xỉ của quá trình này.

Có những khái niệm quan trọng trong suốt giai đoạn này.

  • Mã thông báo đơn giản là một chuỗi các ký tự được coi là một đơn vị.
  • Mã thông báo được phân tách bằng dấu phân cách mã thông báo. Các dấu phân cách mã thông báo tiêu chuẩn là <space> <tab> ; , = <0x0B> <0x0C>và các <0xFF>
    dấu phân cách mã thông báo liên tiếp được coi là một - không có mã thông báo trống giữa các dấu phân cách mã thông báo
  • Không có dấu phân cách mã thông báo trong một chuỗi được trích dẫn. Toàn bộ chuỗi trích dẫn luôn được coi là một phần của một mã thông báo. Một mã thông báo duy nhất có thể bao gồm sự kết hợp của các chuỗi được trích dẫn và các ký tự không được trích dẫn.

Các ký tự sau có thể có ý nghĩa đặc biệt trong giai đoạn này, tùy thuộc vào ngữ cảnh: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

Nhìn vào từng nhân vật từ trái sang phải:

  • Nếu <CR>sau đó loại bỏ nó, như thể nó không bao giờ ở đó (ngoại trừ hành vi chuyển hướng kỳ lạ )
  • Nếu một dấu mũ ( ^), ký tự tiếp theo được thoát và dấu mũ thoát được loại bỏ. Nhân vật trốn thoát mất hết ý nghĩa đặc biệt (trừ <LF>).
  • Nếu một trích dẫn ( "), hãy chuyển cờ báo giá. Nếu cờ trích dẫn đang hoạt động, thì chỉ "<LF>là đặc biệt. Tất cả các nhân vật khác mất ý nghĩa đặc biệt của họ cho đến khi trích dẫn tiếp theo tắt cờ trích dẫn. Không thể thoát khỏi trích dẫn kết thúc. Tất cả các ký tự được trích dẫn luôn nằm trong cùng một mã thông báo.
  • <LF>luôn luôn tắt cờ báo giá. Các hành vi khác thay đổi tùy theo ngữ cảnh, nhưng trích dẫn không bao giờ thay đổi hành vi của <LF>.
    • Đã trốn thoát <LF>
      • <LF> bị tước
      • Nhân vật tiếp theo được trốn thoát. Nếu ở cuối bộ đệm dòng, thì dòng tiếp theo được đọc và xử lý bởi các pha 1 và 1.5 và được thêm vào dòng hiện tại trước khi thoát ký tự tiếp theo. Nếu ký tự tiếp theo là <LF>, thì nó được coi là một nghĩa đen, có nghĩa là quá trình này không được đệ quy.
    • Unescaped <LF>không nằm trong ngoặc đơn
      • <LF> bị tước và phân tích cú pháp của dòng hiện tại bị chấm dứt.
      • Bất kỳ ký tự còn lại trong bộ đệm dòng chỉ đơn giản là bỏ qua.
    • Không được giải thoát <LF>trong một khối ngoặc đơn FOR IN
      • <LF> được chuyển đổi thành một <space>
      • Nếu ở cuối bộ đệm dòng, thì dòng tiếp theo được đọc và gắn vào dòng hiện tại.
    • Không thoát <LF>trong khối lệnh được ngoặc đơn
      • <LF>được chuyển đổi thành <LF><space><space>được coi là một phần của dòng tiếp theo của khối lệnh.
      • Nếu ở cuối bộ đệm dòng, thì dòng tiếp theo được đọc và gắn vào khoảng trắng.
  • Nếu một trong các ký tự đặc biệt & | <hoặc >, chia dòng tại thời điểm này để xử lý các đường ống, nối lệnh và chuyển hướng.
    • Trong trường hợp đường ống ( |), mỗi bên là một lệnh (hoặc khối lệnh) riêng biệt được xử lý đặc biệt trong giai đoạn 5.3
    • Trong trường hợp &, &&hoặc ||nối lệnh, mỗi bên của nối được coi là một lệnh riêng biệt.
    • Trong trường hợp <, <<, >, hoặc >>chuyển hướng, mệnh đề chuyển hướng được phân tách, tạm thời loại bỏ, và sau đó nối vào cuối của lệnh hiện hành. Mệnh đề chuyển hướng bao gồm một chữ số xử lý tệp tùy chọn, toán tử chuyển hướng và mã thông báo đích chuyển hướng.
      • Nếu mã thông báo đi trước toán tử chuyển hướng là một chữ số không thoát, thì chữ số chỉ định xử lý tệp sẽ được chuyển hướng. Nếu không tìm thấy mã thông báo xử lý, thì đầu ra chuyển hướng mặc định thành 1 (stdout) và chuyển hướng đầu vào mặc định thành 0 (stdin).
  • Nếu mã thông báo đầu tiên cho lệnh này (trước khi chuyển hướng chuyển đến cuối) bắt đầu bằng @, thì nó @có ý nghĩa đặc biệt. ( @không đặc biệt trong bất kỳ bối cảnh nào khác)
    • Sự đặc biệt @được loại bỏ.
    • Nếu ECHO đang BẬT, thì lệnh này, cùng với bất kỳ lệnh được nối nào sau đây trên dòng này, được loại trừ khỏi tiếng vang giai đoạn 3. Nếu @là trước khi mở (, thì toàn bộ khối ngoặc được loại trừ khỏi tiếng vang giai đoạn 3.
  • Quá trình ngoặc đơn (cung cấp cho các câu lệnh ghép trên nhiều dòng):
    • Nếu trình phân tích cú pháp không tìm kiếm mã thông báo lệnh, thì (nó không đặc biệt.
    • Nếu trình phân tích cú pháp đang tìm kiếm mã thông báo lệnh và tìm thấy (, thì hãy bắt đầu một câu lệnh ghép mới và tăng bộ đếm dấu ngoặc đơn
    • Nếu bộ đếm dấu ngoặc đơn> 0 thì )chấm dứt câu lệnh ghép và giảm bộ đếm dấu ngoặc đơn.
    • Nếu kết thúc dòng đạt được và bộ đếm dấu ngoặc đơn> 0 thì dòng tiếp theo sẽ được thêm vào câu lệnh ghép (bắt đầu lại với pha 0)
    • Nếu bộ đếm dấu ngoặc đơn là 0 và trình phân tích cú pháp đang tìm kiếm một lệnh, thì các )chức năng tương tự như một REMcâu lệnh miễn là nó được theo sau bởi một dấu phân cách mã thông báo, ký tự đặc biệt, dòng mới hoặc cuối tệp
      • Tất cả các ký tự đặc biệt mất ý nghĩa của chúng ngoại trừ ^(nối dòng là có thể)
      • Khi kết thúc dòng logic, toàn bộ "lệnh" sẽ bị loại bỏ.
  • Mỗi lệnh được phân tích thành một loạt các mã thông báo. Mã thông báo đầu tiên luôn được coi là mã thông báo lệnh (sau khi mã đặc biệt @đã bị tước và chuyển hướng đến cuối).
    • Các dấu phân cách mã thông báo hàng đầu trước mã thông báo lệnh bị tước
    • Khi phân tích mã thông báo lệnh, (hoạt động như một dấu phân cách mã thông báo lệnh, ngoài các dấu phân cách mã thông báo tiêu chuẩn
    • Việc xử lý các mã thông báo tiếp theo phụ thuộc vào lệnh.
  • Hầu hết các lệnh chỉ đơn giản là nối tất cả các đối số sau khi mã thông báo lệnh thành một mã thông báo đối số duy nhất. Tất cả các dấu phân cách mã thông báo đối số được bảo tồn. Các tùy chọn đối số thường không được phân tích cú pháp cho đến giai đoạn 7.
  • Ba lệnh được xử lý đặc biệt - IF, FOR và REM
    • IF được chia thành hai hoặc ba phần riêng biệt được xử lý độc lập. Một lỗi cú pháp trong cấu trúc IF sẽ dẫn đến lỗi cú pháp nghiêm trọng.
      • Hoạt động so sánh là lệnh thực tế chảy suốt đến giai đoạn 7
        • Tất cả các tùy chọn IF được phân tích cú pháp đầy đủ trong giai đoạn 2.
        • Các dấu phân cách mã thông báo liên tiếp sụp đổ vào một không gian duy nhất.
        • Tùy thuộc vào toán tử so sánh, sẽ có một hoặc hai mã thông báo giá trị được xác định.
      • Khối lệnh True là tập hợp các lệnh sau điều kiện và được phân tích cú pháp như bất kỳ khối lệnh nào khác. Nếu ELSE được sử dụng, thì khối True phải được ngoặc đơn.
      • Khối lệnh Sai tùy chọn là tập hợp các lệnh sau ELSE. Một lần nữa, khối lệnh này được phân tích cú pháp bình thường.
      • Các khối lệnh Đúng và Sai không tự động chảy vào các giai đoạn tiếp theo. Quá trình xử lý tiếp theo của họ được kiểm soát bởi giai đoạn 7.
    • FOR được chia làm hai sau DO. Một lỗi cú pháp trong cấu trúc FOR sẽ dẫn đến lỗi cú pháp nghiêm trọng.
      • Phần qua DO là lệnh lặp FOR thực tế chảy suốt giai đoạn 7
        • Tất cả các tùy chọn FOR được phân tích cú pháp đầy đủ trong giai đoạn 2.
        • Mệnh đề IN được coi <LF><space>. Sau khi mệnh đề IN được phân tích cú pháp, tất cả các mã thông báo được nối với nhau để tạo thành một mã thông báo duy nhất.
        • Liên tiếp các dấu phân cách mã thông báo không được trích dẫn / không trích dẫn sụp đổ vào một khoảng trống trong suốt lệnh FOR thông qua DO.
      • Phần sau DO là một khối lệnh được phân tích cú pháp bình thường. Việc xử lý tiếp theo của khối lệnh DO được điều khiển bởi phép lặp trong giai đoạn 7.
    • REM được phát hiện trong giai đoạn 2 được xử lý khác biệt đáng kể so với tất cả các lệnh khác.
      • Chỉ có một mã thông báo đối số được phân tích cú pháp - trình phân tích cú pháp bỏ qua các ký tự sau mã thông báo đối số đầu tiên.
      • Lệnh REM có thể xuất hiện ở đầu ra giai đoạn 3, nhưng lệnh không bao giờ được thực thi và văn bản đối số ban đầu được lặp lại - các dấu thoát không bị xóa, ngoại trừ ...
        • Nếu chỉ có một mã thông báo đối số kết thúc bằng một mã thông báo không kết ^thúc dòng, thì mã thông báo đối số sẽ bị loại bỏ và dòng tiếp theo được phân tích cú pháp và gắn vào REM. Điều này lặp lại cho đến khi có nhiều hơn một mã thông báo, hoặc ký tự cuối cùng thì không ^.
  • Nếu mã thông báo lệnh bắt đầu bằng :và đây là vòng đầu tiên của giai đoạn 2 (không phải là khởi động lại do CALL trong giai đoạn 6) thì
    • Mã thông báo thường được coi là Nhãn chưa được thực hiện .
      • Phần còn lại của dòng được phân tách, tuy nhiên ), <, >, &|không còn có ý nghĩa đặc biệt. Toàn bộ phần còn lại của dòng được coi là một phần của "lệnh" nhãn.
      • Các ^tiếp tục là đặc biệt, có nghĩa là tiếp tục dòng có thể được sử dụng để thêm các dòng sau vào nhãn.
      • Một nhãn chưa được thực hiện trong một khối được ngoặc đơn sẽ dẫn đến một lỗi cú pháp nghiêm trọng trừ khi nó được theo sau bởi một lệnh hoặc Nhãn đã thực hiện trên dòng tiếp theo.
        • (không còn có ý nghĩa đặc biệt đối với lệnh đầu tiên tuân theo Nhãn chưa được thực hiện .
      • Lệnh bị hủy bỏ sau khi phân tích nhãn xong. Các giai đoạn tiếp theo không diễn ra cho nhãn
    • Có ba trường hợp ngoại lệ có thể khiến nhãn được tìm thấy trong giai đoạn 2 được coi là Nhãn thực thi tiếp tục phân tích cú pháp qua giai đoạn 7.
      • Có chuyển hướng mà đến trước nhãn Token, và có một |đường ống hoặc &, &&hoặc ||lệnh nối trên đường dây.
      • Có sự chuyển hướng đi trước mã thông báo nhãn và lệnh nằm trong khối được ngoặc đơn.
      • Mã thông báo nhãn là lệnh đầu tiên trên một dòng trong khối được ngoặc đơn và dòng trên kết thúc bằng một Nhãn chưa được thực hiện .
    • Điều sau đây xảy ra khi Nhãn thực thi được phát hiện trong giai đoạn 2
      • Nhãn, đối số và chuyển hướng của nó đều được loại trừ khỏi mọi đầu ra tiếng vang trong giai đoạn 3
      • Bất kỳ lệnh nối tiếp theo nào trên dòng đều được phân tích cú pháp và thực thi đầy đủ.
    • Để biết thêm thông tin về Nhãn đã thi vs Nhãn chưa thi hành , xem https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

Giai đoạn 3) Báo lại (các) lệnh được phân tích cú pháp Chỉ khi khối lệnh không bắt đầu @và ECHO được BẬT khi bắt đầu bước trước.

Giai đoạn 4) %XMở rộng biến FOR : Chỉ khi lệnh FOR hoạt động và các lệnh sau khi DO đang được xử lý.

  • Tại thời điểm này, giai đoạn 1 của xử lý hàng loạt sẽ chuyển đổi một biến FOR như %%Xthành %X. Dòng lệnh có các quy tắc mở rộng phần trăm khác nhau cho giai đoạn 1. Đây là lý do mà các dòng lệnh sử dụng %Xnhưng các tệp bó sử dụng %%Xcho các biến FOR.
  • Tên biến FOR là trường hợp nhạy cảm, nhưng ~modifierskhông phân biệt chữ hoa chữ thường.
  • ~modifiersđược ưu tiên hơn tên biến. Nếu một ký tự theo sau ~vừa là tên biến đổi vừa là tên biến FOR hợp lệ và tồn tại một ký tự tiếp theo là tên biến FOR hoạt động, thì ký tự đó được hiểu là biến tố.
  • Tên biến FOR là toàn cục, nhưng chỉ trong ngữ cảnh của mệnh đề DO. Nếu một thường trình được GỌI từ trong mệnh đề FOR ​​DO, thì các biến FOR không được mở rộng trong thói quen GỌI. Nhưng nếu thường trình có lệnh FOR riêng, thì tất cả các biến FOR được xác định hiện tại đều có thể truy cập được vào các lệnh DO bên trong.
  • Tên biến có thể được sử dụng lại trong các FOR lồng nhau. Giá trị FOR bên trong được ưu tiên, nhưng một khi INNER FOR đóng, thì giá trị FOR bên ngoài được khôi phục.
  • Nếu ECHO được BẬT khi bắt đầu giai đoạn này, thì giai đoạn 3) được lặp lại để hiển thị các lệnh DO được phân tích cú pháp sau khi các biến FOR được mở rộng.

---- Từ thời điểm này trở đi, mỗi lệnh được xác định trong giai đoạn 2 được xử lý riêng.
---- Các giai đoạn 5 đến 7 được hoàn thành cho một lệnh trước khi chuyển sang lệnh tiếp theo.

Giai đoạn 5) Mở rộng bị trì hoãn: Chỉ khi mở rộng bị trì hoãn, lệnh không nằm trong khối được ngoặc đơn ở hai bên của ống và lệnh không phải là tập lệnh bó "trần trụi" (tên tập lệnh không có dấu ngoặc đơn, CALL, nối lệnh, hoặc đường ống).

  • Mỗi mã thông báo cho một lệnh được phân tích cú pháp để mở rộng chậm trễ một cách độc lập.
    • Hầu hết các lệnh phân tích hai hoặc nhiều mã thông báo - mã thông báo lệnh, mã thông báo đối số và mỗi mã thông báo đích chuyển hướng.
    • Lệnh FOR chỉ phân tích mã thông báo mệnh đề IN.
    • Lệnh IF chỉ phân tích các giá trị so sánh - một hoặc hai, tùy thuộc vào toán tử so sánh.
  • Đối với mỗi mã thông báo được phân tích cú pháp, trước tiên hãy kiểm tra xem nó có chứa bất kỳ !. Nếu không, mã thông báo không được phân tích cú pháp - quan trọng đối với các ^ký tự. Nếu mã thông báo có chứa !, sau đó quét từng ký tự từ trái sang phải:
    • Nếu đó là dấu mũ ( ^) ký tự tiếp theo không có ý nghĩa đặc biệt, thì dấu mũ sẽ bị xóa
    • Nếu đó là dấu chấm than, hãy tìm kiếm dấu chấm than tiếp theo (dấu mũ không được quan sát nữa), mở rộng thành giá trị của biến.
      • Mở liên tiếp ! được thu gọn thành một!
      • Bất kỳ không ghép đôi còn lại !được loại bỏ
    • Mở rộng các vars ở giai đoạn này là "an toàn", vì các ký tự đặc biệt không được phát hiện nữa (thậm chí <CR>hoặc <LF>)
    • Để được giải thích đầy đủ hơn, hãy đọc nửa sau của điều này từ cùng một chủ đề - Giai đoạn dấu chấm than

Giai đoạn 5.3) Xử lý đường ống: Chỉ khi các lệnh nằm ở hai bên của đường ống
Mỗi bên của đường ống được xử lý độc lập và không đồng bộ.

  • Nếu lệnh là nội bộ của cmd.exe, hoặc nó là một tệp bó hoặc nếu đó là một khối lệnh được ngoặc đơn, thì nó được thực thi trong một luồng cmd.exe mới thông qua %comspec% /S /D /c" commandBlock" , vì vậy khối lệnh sẽ khởi động lại pha, nhưng lần này trong chế độ dòng lệnh.
    • Nếu một khối lệnh được ngoặc đơn, thì tất cả <LF>với một lệnh trước và sau được chuyển đổi thành <space>&. Khác <LF>bị tước.
  • Đây là kết thúc xử lý cho các lệnh ống.
  • Xem tại sao việc mở rộng bị trì hoãn thất bại khi bên trong một khối mã được xử lý? để biết thêm về phân tích và xử lý đường ống

Giai đoạn 5.5) Thực hiện chuyển hướng: Bất kỳ chuyển hướng nào được phát hiện trong giai đoạn 2 đều được thực hiện.

Giai đoạn 6) Xử lý CALL / Nhân đôi Caret: Chỉ khi mã thông báo lệnh là GỌI hoặc nếu văn bản trước dấu phân cách mã thông báo tiêu chuẩn xuất hiện đầu tiên là GỌI. Nếu CALL được phân tích cú pháp từ mã thông báo lệnh lớn hơn, thì phần không được sử dụng sẽ được thêm vào mã thông báo đối số trước khi tiếp tục.

  • Quét mã thông báo đối số cho một không trích dẫn /?. Nếu tìm thấy bất cứ nơi nào trong mã thông báo, sau đó hủy bỏ giai đoạn 6 và chuyển sang Giai đoạn 7, trong đó GIÚP cho CALL sẽ được in.
  • Xóa cái đầu tiên CALL, để nhiều CALL có thể được xếp chồng lên nhau
  • Nhân đôi tất cả
  • Khởi động lại các giai đoạn 1, 1.5 và 2, nhưng không tiếp tục giai đoạn 3
    • Bất kỳ dấu mũ đôi nào cũng được giảm trở lại thành một dấu mũ miễn là chúng không được trích dẫn. Nhưng thật không may, dấu ngoặc kép vẫn tăng gấp đôi.
    • Giai đoạn 1 thay đổi một chút
      • Lỗi mở rộng ở bước 1.2 hoặc 1.3 đã hủy bỏ GỌI, nhưng lỗi không nghiêm trọng - quá trình xử lý hàng loạt vẫn tiếp tục.
    • Nhiệm vụ giai đoạn 2 được thay đổi một chút
      • Bất kỳ chuyển hướng mới nào không được trích dẫn, không được giải mã mà không được phát hiện trong vòng đầu tiên của giai đoạn 2 đều được phát hiện, nhưng nó đã bị xóa (bao gồm cả tên tệp) mà không thực sự thực hiện chuyển hướng
      • Bất kỳ dấu mũ mới xuất hiện không được trích dẫn, không thoát ra ở cuối dòng được loại bỏ mà không thực hiện tiếp tục dòng
      • CALL bị hủy bỏ mà không có lỗi nếu phát hiện bất kỳ điều nào sau đây
        • Mới xuất hiện không được trích dẫn, không được giải thoát &hoặc|
        • Mã thông báo lệnh kết quả bắt đầu bằng không trích dẫn, không thoát (
        • Mã thông báo đầu tiên sau khi CALL bị xóa bắt đầu bằng @
      • Nếu lệnh kết quả là IF hoặc FOR có vẻ hợp lệ, thì việc thực thi sau đó sẽ thất bại với lỗi cho biết IFhoặcFOR không được công nhận là lệnh nội bộ hoặc bên ngoài.
      • Tất nhiên, CALL không bị hủy bỏ trong vòng 2 của giai đoạn 2 này nếu mã thông báo lệnh kết quả là nhãn bắt đầu bằng :.
  • Nếu mã thông báo lệnh kết quả là CALL, sau đó khởi động lại Giai đoạn 6 (lặp lại cho đến khi không còn GỌI nữa)
  • Nếu mã thông báo lệnh kết quả là tập lệnh bó hoặc nhãn: thì việc thực thi CALL được xử lý hoàn toàn bởi phần còn lại của Giai đoạn 6.
    • Nhấn vị trí tệp tập lệnh hiện tại trên ngăn xếp cuộc gọi để việc thực thi có thể tiếp tục từ vị trí chính xác khi CALL hoàn tất.
    • Thiết lập mã thông báo đối số% 0,% 1,% 2, ...% N và% * cho CALL, sử dụng tất cả các mã thông báo kết quả
    • Nếu mã thông báo lệnh là nhãn bắt đầu bằng : , thì
      • Khởi động lại Giai đoạn 5. Điều này có thể ảnh hưởng đến những gì: nhãn được GỌI. Nhưng vì mã thông báo% 0, v.v. đã được thiết lập, nên nó sẽ không thay đổi các đối số được truyền cho thói quen GỌI.
      • Thực thi nhãn GOTO để định vị con trỏ tệp ở đầu chương trình con (bỏ qua mọi mã thông báo khác có thể theo nhãn:) Xem Giai đoạn 7 để biết các quy tắc về cách GOTO hoạt động.
    • Khác kiểm soát chuyển giao cho tập lệnh bó được chỉ định.
    • Việc thực thi nhãn CALLed: script hoặc script tiếp tục cho đến khi đạt được EXIT / B hoặc end-of-file, tại đó, ngăn xếp CALL được bật lên và thực thi lại từ vị trí tệp đã lưu.
      Giai đoạn 7 không được thực thi cho các tập lệnh GỌI hoặc: nhãn.
  • Khác kết quả của giai đoạn 6 rơi vào giai đoạn 7 để thực hiện.

Giai đoạn 7) Thực thi: Lệnh được thực thi

  • 7.1 - Thực thi lệnh nội bộ - Nếu mã thông báo lệnh được trích dẫn, sau đó bỏ qua bước này. Nếu không, cố gắng phân tích một lệnh nội bộ và thực thi.
    • Các thử nghiệm sau đây được thực hiện để xác định xem mã thông báo lệnh không trích dẫn có đại diện cho lệnh nội bộ hay không:
      • Nếu mã thông báo lệnh khớp chính xác với một lệnh nội bộ, thì thực hiện nó.
      • Khác phá vỡ mã thông báo lệnh trước khi xuất hiện lần đầu tiên + / [ ] <space> <tab> , ;hoặc =
        Nếu văn bản trước đó là một lệnh nội bộ, thì hãy nhớ lệnh đó
        • Nếu ở chế độ dòng lệnh hoặc nếu lệnh từ khối được ngoặc đơn, thì khối lệnh IF đúng hoặc sai, khối lệnh FOR DO hoặc liên quan đến nối lệnh, sau đó thực hiện lệnh bên trong
        • Khác (phải là một lệnh độc lập trong chế độ hàng loạt) quét thư mục hiện tại và PATH để tìm tệp .COM, .EXE, .BAT hoặc .CMD có tên cơ sở khớp với mã thông báo lệnh gốc
          • Nếu tệp phù hợp đầu tiên là .BAT hoặc .CMD, thì hãy goto 7.3.exec và thực thi tập lệnh đó
          • Khác (không tìm thấy kết quả khớp hoặc khớp đầu tiên là .EXE hoặc .COM) thực thi lệnh nội bộ đã nhớ
      • Khác phá vỡ mã thông báo lệnh trước lần xuất hiện đầu tiên . \hoặc :
        Nếu văn bản trước không phải là lệnh nội bộ, thì goto 7.2
        Khác văn bản trước có thể là lệnh nội bộ. Ghi nhớ lệnh này.
      • Phá mã thông báo lệnh trước khi xuất hiện lần đầu tiên + / [ ] <space> <tab> , ;hoặc =
        Nếu văn bản trước đó là đường dẫn đến tệp hiện có, thì goto 7.2
        Else thực thi lệnh nội bộ đã nhớ.
    • Nếu một lệnh nội bộ được phân tích cú pháp từ mã thông báo lệnh lớn hơn, thì phần không được sử dụng của mã thông báo lệnh được bao gồm trong danh sách đối số
    • Chỉ vì mã thông báo lệnh được phân tích cú pháp như một lệnh nội bộ không có nghĩa là nó sẽ thực thi thành công. Mỗi lệnh nội bộ có các quy tắc riêng về cách các đối số và tùy chọn được phân tích cú pháp và cú pháp nào được cho phép.
    • Tất cả các lệnh nội bộ sẽ in trợ giúp thay vì thực hiện chức năng của chúng nếu /?được phát hiện. Hầu hết nhận ra /?nếu nó xuất hiện bất cứ nơi nào trong các đối số. Nhưng một vài lệnh như ECHO và SET chỉ in trợ giúp nếu mã thông báo đối số đầu tiên bắt đầu bằng /?.
    • SET có một số ngữ nghĩa thú vị:
      • Nếu lệnh SET có dấu ngoặc kép trước khi tên biến và phần mở rộng được bật
        set "name=content" ignored -> value = content
        thì văn bản giữa dấu bằng đầu tiên và trích dẫn cuối cùng được sử dụng làm nội dung (loại trừ trích dẫn đầu tiên và cuối cùng). Văn bản sau khi trích dẫn cuối cùng được bỏ qua. Nếu không có trích dẫn sau dấu bằng, thì phần còn lại của dòng được sử dụng làm nội dung.
      • Nếu lệnh SET không có dấu ngoặc kép trước tên
        set name="content" not ignored -> value = "content" not ignored
        thì toàn bộ phần còn lại của dòng sau bằng được sử dụng làm nội dung, bao gồm bất kỳ và tất cả các trích dẫn có thể có mặt.
    • Một so sánh IF được đánh giá và tùy thuộc vào điều kiện là đúng hay sai, khối lệnh phụ thuộc đã được phân tích cú pháp thích hợp được xử lý, bắt đầu với giai đoạn 5.
    • Mệnh đề IN của lệnh FOR được lặp lại một cách thích hợp.
      • Nếu đây là FOR / F lặp lại đầu ra của khối lệnh, thì:
        • Mệnh đề IN được thực thi trong một quy trình cmd.exe mới thông qua CMD / C.
        • Khối lệnh phải trải qua toàn bộ quá trình phân tích cú pháp lần thứ hai, nhưng lần này trong bối cảnh dòng lệnh
        • ECHO sẽ bắt đầu BẬT và việc mở rộng bị trì hoãn thường sẽ bị vô hiệu hóa (phụ thuộc vào cài đặt đăng ký)
        • Tất cả các thay đổi môi trường được thực hiện bởi khối lệnh mệnh đề IN sẽ bị mất sau khi quá trình cmd.exe con kết thúc
      • Đối với mỗi lần lặp:
        • Các giá trị biến FOR được xác định
        • Khối lệnh DO đã được phân tích cú pháp sau đó được xử lý, bắt đầu với giai đoạn 4.
    • GOTO sử dụng logic sau để định vị nhãn:
      • Nhãn được phân tích cú pháp từ mã thông báo đối số đầu tiên
      • Kịch bản được quét cho lần xuất hiện tiếp theo của nhãn
        • Quá trình quét bắt đầu từ vị trí tệp hiện tại
        • Nếu kết thúc tập tin, thì vòng quét trở lại điểm bắt đầu của tập tin và tiếp tục đến điểm bắt đầu ban đầu.
      • Quá trình quét dừng lại ở lần xuất hiện đầu tiên của nhãn mà nó tìm thấy và con trỏ tệp được đặt thành dòng ngay sau nhãn. Thi hành kịch bản tiếp tục từ thời điểm đó. Lưu ý rằng một GOTO thực sự thành công sẽ ngay lập tức hủy bỏ bất kỳ khối mã được phân tích cú pháp nào, bao gồm các vòng lặp FOR.
      • Nếu không tìm thấy nhãn hoặc mã thông báo nhãn bị thiếu, thì GOTO không thành công, thông báo lỗi được in và ngăn xếp cuộc gọi được bật lên. Điều này hoạt động hiệu quả như một EXIT / B, ngoại trừ mọi lệnh đã được phân tích cú pháp trong khối lệnh hiện tại theo GOTO vẫn được thực thi, nhưng trong ngữ cảnh của CALLer (bối cảnh tồn tại sau EXIT / B)
      • Xem https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 để biết mô tả chính xác hơn về các quy tắc được sử dụng để phân tích nhãn.
    • RENAME và COPY đều chấp nhận ký tự đại diện cho đường dẫn nguồn và đích. Nhưng Microsoft thực hiện một công việc khủng khiếp là tài liệu về cách các ký tự đại diện hoạt động, đặc biệt là cho đường dẫn đích. Có thể tìm thấy một bộ quy tắc ký tự đại diện hữu ích tại Làm thế nào để lệnh Windows RENAME diễn giải các ký tự đại diện?
  • 7.2 - Thực hiện thay đổi âm lượng - Khác nếu mã thông báo lệnh không bắt đầu bằng trích dẫn, dài chính xác hai ký tự và ký tự thứ 2 là dấu hai chấm, sau đó thay đổi âm lượng
    • Tất cả các mã thông báo đối số được bỏ qua
    • Nếu không thể tìm thấy âm lượng được chỉ định bởi ký tự đầu tiên, thì hủy bỏ với lỗi
    • Mã thông báo lệnh ::sẽ luôn dẫn đến lỗi trừ khi SUBST được sử dụng để xác định âm lượng cho ::
      Nếu SUBST được sử dụng để xác định âm lượng ::, thì âm lượng sẽ bị thay đổi, nó sẽ không được coi là nhãn.
  • 7.3 - Thực thi lệnh bên ngoài - Khác cố gắng coi lệnh như một lệnh bên ngoài.
    • Nếu trong chế độ dòng lệnh và lệnh không được trích dẫn và không bắt đầu bằng một đặc tả khối lượng, trắng-không gian, ,, ;, =hoặc +sau đó phá vỡ các lệnh mã thông báo tại sự xuất hiện đầu tiên của <space> , ;hay =và thêm vào trước thời gian còn lại để lập luận token (s).
    • Nếu ký tự thứ 2 của mã thông báo lệnh là dấu hai chấm, thì có thể xác minh âm lượng được chỉ định bởi ký tự thứ 1 có thể được tìm thấy.
      Nếu âm lượng không thể được tìm thấy, sau đó hủy bỏ với một lỗi.
    • Nếu ở chế độ hàng loạt và mã thông báo lệnh bắt đầu bằng :, thì goto 7.4
      Lưu ý rằng nếu mã thông báo nhãn bắt đầu bằng ::thì điều này sẽ không đạt được vì bước trước đó sẽ bị hủy bỏ trừ khi sử dụng SUBST để xác định âm lượng ::.
    • Xác định lệnh bên ngoài để thực thi.
      • Đây là một quá trình phức tạp có thể liên quan đến khối lượng hiện tại, thư mục hiện tại, biến PATH, biến PATHEXT và các liên kết tệp.
      • Nếu một lệnh bên ngoài hợp lệ không thể được xác định, sau đó hủy bỏ với một lỗi.
    • Nếu ở chế độ dòng lệnh và mã thông báo lệnh bắt đầu :, thì goto 7.4
      Lưu ý rằng điều này hiếm khi đạt được vì bước trước đó sẽ bị hủy bỏ với lỗi trừ khi mã thông báo lệnh bắt đầu ::và SUBST được sử dụng để xác định âm lượng cho ::và toàn bộ mã thông báo lệnh là một đường dẫn hợp lệ đến một lệnh bên ngoài.
    • 7.3.exec - Thực hiện lệnh bên ngoài.
  • 7.4 - Bỏ qua nhãn - Bỏ qua lệnh và tất cả các đối số của nó nếu mã thông báo lệnh bắt đầu bằng :.
    Các quy tắc trong 7.2 và 7.3 có thể ngăn nhãn đạt đến điểm này.

Trình phân tích dòng lệnh:

Hoạt động như BatchLine-Parser, ngoại trừ:

Giai đoạn 1) Mở rộng phần trăm:

  • Không %*, %1v.v.
  • Nếu var không được xác định, thì %var%không thay đổi.
  • Không xử lý đặc biệt %%. Nếu var = nội dung, sau đó %%var%%mở rộng thành %content%.

Giai đoạn 3) Báo lại (các) lệnh được phân tích cú pháp

  • Điều này không được thực hiện sau giai đoạn 2. Nó chỉ được thực hiện sau giai đoạn 4 cho khối lệnh FOR DO.

Giai đoạn 5) Mở rộng bị trì hoãn: chỉ khi DelayedExpansion được bật

  • Nếu var không được xác định, thì !var!không thay đổi.

Giai đoạn 7) Thực thi lệnh

  • Nỗ lực GỌI hoặc GOTO a: nhãn dẫn đến lỗi.
  • Như đã được ghi lại trong giai đoạn 7, một nhãn thực thi có thể dẫn đến lỗi trong các tình huống khác nhau.
    • Nhãn thực thi hàng loạt chỉ có thể gây ra lỗi nếu chúng bắt đầu bằng ::
    • Các dòng lệnh thực thi hầu như luôn luôn dẫn đến một lỗi

Phân tích cú pháp các giá trị nguyên

Có nhiều bối cảnh khác nhau trong đó cmd.exe phân tích các giá trị nguyên từ các chuỗi và các quy tắc không nhất quán:

  • SET /A
  • IF
  • %var:~n,m% (mở rộng chuỗi con thay đổi)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Chi tiết cho các quy tắc này có thể được tìm thấy tại Quy tắc về cách CMD.EXE phân tích số


Đối với bất kỳ ai muốn cải thiện các quy tắc phân tích cú pháp cmd.exe, có một chủ đề thảo luận trên diễn đàn DosTips nơi các vấn đề có thể được báo cáo và đề xuất.

Hy vọng nó sẽ giúp
Jan Erik (jeb) - Tác giả và người phát hiện ra các giai đoạn
Dave Benham (dbenham) - Nhiều nội dung và chỉnh sửa bổ sung


4
Xin chào jeb, cảm ơn bạn vì cái nhìn sâu sắc của bạn Có thể khó hiểu, nhưng tôi sẽ cố gắng suy nghĩ kỹ! Bạn dường như đã thực hiện nhiều bài kiểm tra! Cảm ơn bạn đã dịch ( Administrator.de/
Kẻ

2
Batch giai đoạn 5) - %% a sẽ được thay đổi thành% a trong Giai đoạn 1, vì vậy việc mở rộng vòng lặp thực sự mở rộng% a. Ngoài ra, tôi đã thêm một lời giải thích chi tiết hơn về Batch giai đoạn 1 trong câu trả lời bên dưới (Tôi không có đặc quyền chỉnh sửa)
dbenham

3
Jeb - có lẽ giai đoạn 0 có thể được di chuyển và kết hợp với giai đoạn 6? Điều đó có ý nghĩa hơn với tôi, hoặc có một lý do tại sao họ bị tách ra như vậy?
dbenham

1
@aschipfl - Tôi đã cập nhật phần đó. Hàm )thực sự hoạt động gần giống như một REMlệnh khi bộ đếm dấu ngoặc đơn là 0. Hãy thử cả hai từ dòng lệnh: ) Ignore thisecho OK & ) Ignore this
dbenham

1
@aschipfl đúng vậy, đôi khi bạn thấy "đặt" var =% expr% "! 'Dấu chấm than cuối cùng sẽ được gỡ bỏ, nhưng lực lượng giai đoạn 5
Jeb

62

Khi gọi một lệnh từ cửa sổ lệnh, mã thông báo của các đối số dòng lệnh không được thực hiện bởi cmd.exe(còn gọi là "shell"). Thông thường, mã thông báo được thực hiện bởi thời gian chạy C / C ++ của các tiến trình mới được tạo, nhưng điều này không nhất thiết phải như vậy - ví dụ, nếu quy trình mới không được viết bằng C / C ++ hoặc nếu quy trình mới chọn bỏ qua argvvà xử lý dòng lệnh thô cho chính nó (ví dụ với GetCommandLine ()). Ở cấp độ HĐH, Windows chuyển các dòng lệnh không được nói thành một chuỗi cho các quy trình mới. Điều này trái ngược với hầu hết các shell * nix, trong đó shell này token hóa các đối số theo một cách nhất quán, có thể dự đoán trước khi chuyển chúng sang quy trình mới được hình thành. Tất cả điều này có nghĩa là bạn có thể gặp phải hành vi mã thông báo đối số khác nhau giữa các chương trình khác nhau trên Windows, vì các chương trình riêng lẻ thường đưa mã thông báo đối số vào tay họ.

Nếu nó có vẻ như vô chính phủ, nó là loại. Tuy nhiên, vì một số lượng lớn các chương trình Windows làm sử dụng Microsoft C / C ++ runtime của argv, nó có thể là thường hữu ích để hiểu như thế nào tokenizes MSVCRT đối số. Đây là một đoạn trích:

  • Các đối số được phân định bởi khoảng trắng, là khoảng trắng hoặc tab.
  • Một chuỗi được bao quanh bởi dấu ngoặc kép được hiểu là một đối số duy nhất, bất kể khoảng trắng chứa trong đó. Một chuỗi trích dẫn có thể được nhúng trong một đối số. Lưu ý rằng dấu mũ (^) không được nhận dạng là ký tự thoát hoặc dấu phân cách.
  • Dấu ngoặc kép đứng trước dấu gạch chéo ngược, \ ", được hiểu là dấu ngoặc kép nghĩa đen (").
  • Dấu gạch chéo ngược được hiểu theo nghĩa đen, trừ khi chúng ngay trước dấu ngoặc kép.
  • Nếu một số dấu gạch chéo chẵn được theo sau bởi dấu ngoặc kép, thì một dấu gạch chéo ngược () được đặt trong mảng argv cho mỗi cặp dấu gạch chéo ngược (\) và dấu ngoặc kép (") được hiểu là dấu phân cách chuỗi.
  • Nếu một số dấu gạch chéo ngược được theo sau bởi dấu ngoặc kép, thì một dấu gạch chéo ngược () được đặt trong mảng argv cho mỗi cặp dấu gạch chéo ngược (\) và dấu ngoặc kép được hiểu là một chuỗi thoát bởi dấu gạch chéo ngược còn lại, gây ra một dấu ngoặc kép bằng chữ (") được đặt trong argv.

"Ngôn ngữ bó" của Microsoft ( .bat) cũng không ngoại lệ đối với môi trường vô chính phủ này và nó đã phát triển các quy tắc duy nhất của riêng mình cho mã thông báo và thoát. Dường như dấu nhắc lệnh của cmd.exe thực hiện một số tiền xử lý đối số dòng lệnh (chủ yếu để thay thế và thoát biến) trước khi chuyển đối số sang quy trình thực thi mới. Bạn có thể đọc thêm về các chi tiết cấp thấp của ngôn ngữ lô và cmd thoát trong các câu trả lời xuất sắc của jeb và dbenham trên trang này.


Hãy xây dựng một tiện ích dòng lệnh đơn giản trong C và xem nó nói gì về các trường hợp thử nghiệm của bạn:

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

(Ghi chú: argv [0] luôn là tên của tệp thực thi và được bỏ qua bên dưới để đơn giản. Đã thử nghiệm trên Windows XP SP3. Được biên dịch với 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]

Và một vài thử nghiệm của riêng tôi:

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

Cảm ơn bạn vì câu trả lời. Nó thậm chí còn đánh đố tôi nhiều hơn khi thấy TinyPerl sẽ không xuất ra những gì chương trình của bạn xuất ra và tôi gặp khó khăn để hiểu làm thế nào [a "b" c]có thể [a "b] [c]thực hiện xử lý hậu kỳ.
Benoit

Bây giờ tôi nghĩ về nó, mã thông báo của dòng lệnh này có thể được thực hiện hoàn toàn bởi thời gian chạy C. Một tệp thực thi có thể được viết sao cho nó thậm chí không sử dụng thời gian chạy C, trong trường hợp đó tôi nghĩ rằng nó sẽ phải xử lý nguyên văn dòng lệnh và chịu trách nhiệm thực hiện mã thông báo của riêng mình (nếu muốn.) Hoặc thậm chí nếu ứng dụng của bạn sử dụng thời gian chạy C, bạn có thể chọn bỏ qua argc và argv và chỉ cần lấy dòng lệnh thô thông qua ví dụ Win32 GetCommandLine. Có lẽ TinyPerl đang bỏ qua argv và chỉ đơn giản là token hóa dòng lệnh thô bằng các quy tắc riêng của nó.
Mike Clark

4
"Hãy nhớ rằng theo quan điểm của Win32, dòng lệnh chỉ là một chuỗi được sao chép vào không gian địa chỉ của quy trình mới. Quá trình khởi chạy và quy trình mới diễn giải chuỗi này không bị chi phối bởi quy tắc mà theo quy ước." -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
Cảm ơn bạn cho câu trả lời thực sự tốt đẹp. Điều đó giải thích rất nhiều trong quan điểm của tôi. Và điều đó cũng giải thích lý do tại sao đôi khi tôi thấy điều đó thật sự nhảm nhí khi làm việc với Windows Sự
Benoit

Tôi đã tìm thấy điều này liên quan đến dấu gạch chéo ngược và dấu ngoặc kép trong quá trình chuyển đổi từ dòng lệnh sang argv, cho các chương trình Win32 C ++. Số lượng dấu gạch chéo ngược chỉ được chia cho hai khi dấu gạch chéo ngược cuối cùng được theo sau bởi một dblquote và dblquote chấm dứt một chuỗi khi có một số dấu gạch chéo chẵn trước đó.
Benoit

47

Quy tắc mở rộng phần trăm

Dưới đây là phần giải thích mở rộng về Giai đoạn 1 trong câu trả lời của jeb (Hợp lệ cho cả chế độ hàng loạt và chế độ dòng lệnh).

Giai đoạn 1) Mở rộng phần trăm Bắt đầu từ bên trái, quét từng ký tự cho %hoặc <LF>. Nếu tìm thấy thì

  • 1,05 (cắt ngắn tại <LF>)
    • Nếu nhân vật là <LF> thì
      • Thả (bỏ qua) phần còn lại của dòng từ <LF>trở đi
      • Goto Giai đoạn 1.5 (Dải <CR>)
    • Khác nhân vật phải được %, vì vậy tiến hành 1.1
  • 1.1 (thoát %) bị bỏ qua nếu chế độ dòng lệnh
    • Nếu chế độ hàng loạt và tiếp theo là chế độ khác %thì
      Thay thế %%bằng đơn %và tiếp tục quét
  • 1.2 (mở rộng đối số) bị bỏ qua nếu chế độ dòng lệnh
    • Khác nếu chế độ hàng loạt thì
      • Nếu theo sau *và tiện ích mở rộng lệnh được bật thì
        Thay thế%* bằng văn bản của tất cả các đối số dòng lệnh (Thay thế bằng không có gì nếu không có đối số) và tiếp tục quét.
      • Khác nếu theo <digit>sau thì
        Thay thế%<digit> bằng giá trị đối số (thay thế bằng không có gì nếu không xác định) và tiếp tục quét.
      • Khác nếu theo sau ~và phần mở rộng lệnh được kích hoạt sau đó
        • Nếu được theo sau bởi danh sách các công cụ sửa đổi đối số hợp lệ tùy chọn theo sau là bắt buộc <digit>thì
          Thay thế %~[modifiers]<digit>bằng giá trị đối số đã sửa đổi (thay thế bằng không có gì nếu không được xác định hoặc nếu được chỉ định $ PATH: công cụ sửa đổi không được xác định) và tiếp tục quét.
          Lưu ý: công cụ sửa đổi không phân biệt chữ hoa chữ thường và có thể xuất hiện nhiều lần theo bất kỳ thứ tự nào, ngoại trừ $ PATH: công cụ sửa đổi chỉ có thể xuất hiện một lần và phải là công cụ sửa đổi cuối cùng trước<digit>
        • Khác cú pháp đối số được sửa đổi không hợp lệ làm tăng lỗi nghiêm trọng: Tất cả các lệnh được phân tích cú pháp bị hủy bỏ và xử lý hàng loạt hủy bỏ nếu ở chế độ hàng loạt!
  • 1.3 (biến mở rộng)
    • Khác nếu các phần mở rộng lệnh bị vô hiệu hóa thì
      hãy xem chuỗi ký tự tiếp theo, ngắt trước %hoặc kết thúc bộ đệm và gọi chúng là VAR (có thể là một danh sách trống)
      • Nếu ký tự tiếp theo là %sau đó
        • Nếu VAR được xác định thì
          Thay thế%VAR% bằng giá trị của VAR và tiếp tục quét
        • Khác nếu chế độ hàng loạt thì loại
          bỏ%VAR%và tiếp tục quét
        • Khác goto 1.4
      • Khác goto 1.4
    • Khác nếu tiện ích mở rộng lệnh được bật thì
      hãy xem chuỗi ký tự tiếp theo, ngắt trước % :hoặc kết thúc bộ đệm và gọi chúng là VAR (có thể là danh sách trống). Nếu VAR ngắt trước :và ký tự tiếp theo sẽ được %đưa :vào làm ký tự cuối cùng trong VAR và ngắt trước% .
      • Nếu nhân vật tiếp theo là %sau đó
        • Nếu VAR được xác định thì
          Thay thế%VAR% bằng giá trị của VAR và tiếp tục quét
        • Khác nếu chế độ hàng loạt thì loại
          bỏ %VAR%và tiếp tục quét
        • Khác goto 1.4
      • Khác nếu nhân vật tiếp theo là :sau đó
        • Nếu VAR không được xác định thì
          • Nếu chế độ hàng loạt thì loại
            bỏ %VAR:và tiếp tục quét.
          • Khác goto 1.4
        • Khác nếu nhân vật tiếp theo là ~sau đó
          • Nếu chuỗi tiếp theo của nhân vật phù hợp với mô hình của [integer][,[integer]]%sau đó
            Replace %VAR:~[integer][,[integer]]%với substring giá trị của VAR (có thể dẫn đến chuỗi rỗng) và tiếp tục quét.
          • Khác goto 1.4
        • Khác nếu theo sau =hoặc *=sau đó
          Tìm kiếm biến không hợp lệ và cú pháp thay thế sẽ gây ra lỗi nghiêm trọng: Tất cả các lệnh được phân tích cú pháp bị hủy bỏ và xử lý hàng loạt hủy bỏ nếu ở chế độ hàng loạt!
        • Khác nếu chuỗi ký tự tiếp theo khớp với mẫu của [*]search=[replace]%, trong đó tìm kiếm có thể bao gồm bất kỳ bộ ký tự nào ngoại trừ =và thay thế có thể bao gồm bất kỳ bộ ký tự nào ngoại trừ %, sau đó
          Thay thế%VAR:[*]search=[replace]% bằng giá trị của VAR sau khi thực hiện tìm kiếm và thay thế (có thể dẫn đến chuỗi trống) và tiếp tục quét
        • Khác goto 1.4
  • 1,4 (dải%)
    • Khác Nếu chế độ hàng loạt thì
      Xóa %và tiếp tục quét bắt đầu bằng ký tự tiếp theo sau%
    • Khác giữ nguyên đầu %và tiếp tục quét bắt đầu với ký tự tiếp theo sau đầu được bảo toàn%

Những điều trên giúp giải thích tại sao đợt này

@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

Cung cấp các kết quả sau:

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

Lưu ý 1 - Giai đoạn 1 xảy ra trước khi nhận ra các câu lệnh REM. Điều này rất quan trọng vì nó có nghĩa là ngay cả một nhận xét cũng có thể tạo ra lỗi nghiêm trọng nếu nó có cú pháp mở rộng đối số không hợp lệ hoặc tìm kiếm biến không hợp lệ và cú pháp thay thế!

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

Lưu ý 2 - Một hậu quả thú vị khác của quy tắc phân tích%: Các biến có chứa: trong tên có thể được xác định, nhưng chúng không thể được mở rộng trừ khi tiện ích mở rộng lệnh bị vô hiệu hóa. Có một ngoại lệ - một tên biến chứa một dấu hai chấm ở cuối có thể được mở rộng trong khi tiện ích mở rộng lệnh được bật. Tuy nhiên, bạn không thể thực hiện chuỗi con hoặc tìm kiếm và thay thế các hoạt động trên các tên biến kết thúc bằng dấu hai chấm. Tệp bó bên dưới (lịch sự của jeb) thể hiện hành vi này

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

Lưu ý 3 - Một kết quả thú vị của thứ tự các quy tắc phân tích cú pháp mà jeb đưa ra trong bài đăng của mình: Khi thực hiện tìm và thay thế bằng mở rộng bị trì hoãn, các ký tự đặc biệt trong cả hai thuật ngữ tìm và thay thế phải được thoát hoặc trích dẫn. Nhưng tình hình là khác nhau đối với việc mở rộng phần trăm - thuật ngữ tìm kiếm không được thoát (mặc dù nó có thể được trích dẫn). Chuỗi phần trăm thay thế có thể hoặc không yêu cầu thoát hoặc trích dẫn, tùy thuộc vào ý định của bạn.

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

Quy tắc mở rộng bị trì hoãn

Dưới đây là phần giải thích mở rộng và chính xác hơn về giai đoạn 5 trong câu trả lời của jeb (Hợp lệ cho cả chế độ hàng loạt và chế độ dòng lệnh)

Giai đoạn 5) Mở rộng bị trì hoãn

Giai đoạn này được bỏ qua nếu áp dụng bất kỳ điều kiện nào sau đây:

  • Mở rộng bị trì hoãn bị vô hiệu hóa.
  • Lệnh nằm trong khối được ngoặc đơn ở hai bên của đường ống.
  • Mã thông báo lệnh đến là một tập lệnh bó "trần trụi", có nghĩa là nó không được liên kết với CALL, khối ngoặc đơn, bất kỳ hình thức nối lệnh nào ( &, &&hoặc ||) hoặc đường ống |.

Quá trình mở rộng bị trì hoãn được áp dụng cho các mã thông báo một cách độc lập. Một lệnh có thể có nhiều mã thông báo:

  • Mã thông báo lệnh. Đối với hầu hết các lệnh, tên lệnh chính nó là một mã thông báo. Nhưng một vài lệnh có các vùng chuyên biệt được coi là TOKEN cho giai đoạn 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, Nơi so sánh là một trong những ==, equ, neq, lss, leq, gtr, hoặcgeq
  • Các mã thông báo đối số
  • Mã thông báo đích của chuyển hướng (mỗi mã chuyển hướng)

Không có thay đổi nào được thực hiện đối với các mã thông báo không chứa !.

Đối với mỗi mã thông báo chứa ít nhất một mã thông báo !, hãy quét từng ký tự từ trái sang phải để tìm ^hoặc !, nếu được tìm thấy, sau đó

  • 5.1 (thoát caret) Cần cho !hoặc^ chữ
    • Nếu nhân vật là một dấu mũ ^ thì
      • Gỡ bỏ ^
      • Quét ký tự tiếp theo và giữ nguyên ký tự
      • Tiếp tục quét
  • 5.2 (biến mở rộng)
    • Nếu nhân vật là ! thì
      • Nếu tiện ích mở rộng lệnh bị vô hiệu hóa, hãy
        xem chuỗi ký tự tiếp theo, ngắt trước !hoặc<LF> gọi chúng là VAR (có thể là danh sách trống)
        • Nếu nhân vật tiếp theo là ! sau đó
          • Nếu VAR được xác định, thì
            Thay thế!VAR! bằng giá trị của VAR và tiếp tục quét
          • Khác nếu chế độ hàng loạt thì loại
            bỏ!VAR! và tiếp tục quét
          • Khác goto 5.2.1
        • Khác goto 5.2.1
      • Else if mở rộng lệnh được kích hoạt sau đó
        Nhìn vào chuỗi tiếp theo của nhân vật, phá vỡ trước !, :hoặc <LF>, và gọi cho họ VAR (có thể là một danh sách trống). Nếu VAR ngắt trước :và ký tự tiếp theo sẽ được !đưa :vào làm ký tự cuối cùng trong VAR và ngắt trước!
        • Nếu ký tự tiếp theo là! sau đó
          • Nếu VAR tồn tại, sau đó
            Thay thế !VAR!bằng giá trị của VAR và tiếp tục quét
          • Khác nếu chế độ hàng loạt thì loại
            bỏ !VAR!và tiếp tục quét
          • Khác goto 5.2.1
        • Khác nếu nhân vật tiếp theo là :sau đó
          • Nếu VAR không được xác định thì
            • Nếu chế độ hàng loạt thì
              Xóa !VAR:và tiếp tục quét
            • Khác goto 5.2.1
          • Khác nếu nhân vật tiếp theo là ~sau đó
            • Nếu chuỗi ký tự tiếp theo khớp với mẫu của [integer][,[integer]]!Thay thế !VAR:~[integer][,[integer]]!bằng chuỗi con có giá trị của VAR (có thể dẫn đến chuỗi trống) và tiếp tục quét.
            • Khác goto 5.2.1
          • Khác nếu chuỗi ký tự tiếp theo khớp với mẫu của [*]search=[replace]!, trong đó tìm kiếm có thể bao gồm bất kỳ bộ ký tự nào ngoại trừ =và thay thế có thể bao gồm bất kỳ bộ ký tự nào ngoại trừ! , sau đó
            Thay thế !VAR:[*]search=[replace]!bằng giá trị của VAR sau khi thực hiện tìm kiếm và thay thế (có thể dẫn đến một chuỗi trống) và tiếp tục quét
          • Khác goto 5.2.1
        • Khác goto 5.2.1
      • 5.2.1
        • Nếu chế độ hàng loạt thì loại bỏ hàng đầu !
          Else giữ nguyên hàng đầu!
        • Tiếp tục quét bắt đầu với ký tự tiếp theo sau hàng đầu được bảo toàn !

3
+1, Chỉ thiếu cú ​​pháp dấu hai chấm và quy tắc ở đây cho %definedVar:a=b%vs %undefinedVar:a=b%và các %var:~0x17,-010%biểu mẫu
jeb

2
Điểm hay - Tôi đã mở rộng phần mở rộng biến để giải quyết mối quan tâm của bạn. Tôi cũng mở rộng phần mở rộng đối số để điền vào một số chi tiết còn thiếu.
dbenham

2
Sau khi nhận được một số phản hồi riêng tư từ jeb, tôi đã thêm một quy tắc cho các tên biến kết thúc bằng dấu hai chấm và thêm ghi chú 2. Tôi cũng đã thêm ghi chú 3 đơn giản vì tôi nghĩ nó thú vị và quan trọng.
dbenham

1
@aschipfl - Vâng, tôi đã cân nhắc việc đi sâu vào chi tiết hơn về điều đó, nhưng tôi không muốn đi xuống cái hố thỏ đó. Tôi đã cố tình không ủy quyền khi tôi sử dụng thuật ngữ [số nguyên]. Có nhiều thông tin hơn tại Quy tắc về cách thức mà CMD.EXE phân tích số .
dbenham

1
Tôi đang thiếu các quy tắc mở rộng cho bối cảnh cmd, như thế không có ký tự dành riêng cho ký tự đầu tiên của tên biến như %<digit>, %*hoặc %~. Và hành vi thay đổi cho các biến không xác định. Có lẽ bạn cần phải mở một câu trả lời thứ hai
Jeb

7

Như đã chỉ ra, các lệnh được truyền toàn bộ chuỗi đối số trong vùng đất mềm, và tùy thuộc vào chúng để phân tích điều này thành các đối số riêng biệt để sử dụng riêng. Không có sự nhất quán trong điều này giữa các chương trình khác nhau, và do đó không có một bộ quy tắc nào để mô tả quá trình này. Bạn thực sự cần phải kiểm tra từng trường hợp góc cho bất kỳ thư viện C nào mà chương trình của bạn sử dụng.

Theo như các .battập tin hệ thống , đây là bài kiểm tra:

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

Bây giờ chúng tôi có thể chạy một số thử nghiệm. Xem nếu bạn có thể tìm ra những gì μSoft đang cố gắng làm:

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

Tốt cho đến nay. (Tôi sẽ bỏ qua việc không quan tâm %cmdcmdline%%0kể từ bây giờ.)

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

Không mở rộng tên tệp.

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

Không trích dẫn trích dẫn, mặc dù trích dẫn không ngăn chia tách đối số.

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

Các dấu ngoặc kép liên tiếp làm cho chúng mất bất kỳ khả năng phân tích đặc biệt nào mà chúng có thể có. @ Ví dụ của Beniot:

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

Trắc nghiệm: Làm thế nào để bạn chuyển giá trị của bất kỳ var môi trường nào dưới dạng một đối số duy nhất (nghĩa là %1) dưới dạng tệp dơi?

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 phân tích dường như mãi mãi bị phá vỡ.

Để tiện giải trí, thử thêm linh tinh ^, \, ', &(& c.) Ký tự để những ví dụ.


Để vượt qua% t% dưới dạng một đối số, bạn có thể sử dụng "% t:" = \ "%" Nghĩa là sử dụng cú pháp% VAR: str = thay thế% để mở rộng biến. Shell metachar character như | và & trong nội dung biến vẫn có thể bị lộ và làm
hỏng

@Toughy Vì vậy, trong ví dụ của tôi, ta "b c. Bạn có một công thức để nhận những 6 ký tự ( a2 × không gian, ", b, và c) xuất hiện như %1bên trong một .cmd? Tôi thích suy nghĩ của bạn mặc dù. args "%t:"=""%"khá gần :-)
bobbogo

5

Bạn đã có một số câu trả lời tuyệt vời ở trên, nhưng để trả lời một phần câu hỏi của bạn:

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

Điều đang xảy ra là bởi vì bạn có một khoảng trắng trước dấu =, một biến được tạo ra được gọi %a<space>% như vậy khi bạn echo %a %được đánh giá chính xác là b.

Phần còn lại b% c%sau đó được đánh giá là văn bản thuần túy + ​​một biến không xác định % c%, sẽ được lặp lại dưới dạng gõ, đối với tôi echo %a %b% c%trả vềbb% c%

Tôi nghi ngờ rằng khả năng bao gồm các khoảng trắng trong các tên biến là một sự giám sát nhiều hơn là một 'tính năng' được lên kế hoạch


0

chỉnh sửa: xem câu trả lời được chấp nhận, những gì sau đây là sai và chỉ giải thích cách chuyển một dòng lệnh cho TinyPerl.


Về trích dẫn, tôi có cảm giác rằng hành vi là như sau:

  • Khi "tìm thấy a, chuỗi chuỗi bắt đầu
  • khi xảy ra hiện tượng chuỗi chuỗi:
    • mỗi nhân vật không phải là một hình "cầu
    • khi "tìm thấy a:
      • nếu nó được theo sau bởi ""(như một bộ ba ") thì một trích dẫn kép được thêm vào chuỗi
      • nếu nó được theo sau bởi "(do đó là một đôi ") thì một trích dẫn kép được thêm vào chuỗi kết thúc chuỗi và chuỗi
      • nếu ký tự tiếp theo không có ", chuỗi kết thúc chuỗi
    • khi dòng kết thúc, chuỗi kết thúc chuỗi.

Nói ngắn gọn:

"a """ b "" c"""bao gồm hai chuỗi: a " b "c"

"a"", "a""""a""""là tất cả các chuỗi tương tự nếu ở phần cuối của một dòng


mã thông báo và chuỗi toàn cầu phụ thuộc vào lệnh! Một "bộ" hoạt động khác nhau sau đó là "cuộc gọi" hoặc thậm chí là "nếu"
jeb

có, nhưng những lệnh bên ngoài thì sao? Tôi đoán cmd.exe luôn chuyển các đối số tương tự cho họ?
Benoit

1
cmd.exe luôn chuyển kết quả mở rộng dưới dạng một chuỗi chứ không phải mã thông báo cho lệnh bên ngoài. Nó phụ thuộc vào lệnh bên ngoài làm thế nào để thoát và mã hóa nó, findstr sử dụng dấu gạch chéo ngược để người tiếp theo có thể sử dụng một cái gì đó khác
jeb

0

Lưu ý rằng Microsoft đã xuất bản mã nguồn của Terminal. Nó có thể hoạt động tương tự như dòng lệnh đối với phân tích cú pháp. Có thể ai đó quan tâm đến việc kiểm tra các quy tắc phân tích cú pháp được thiết kế ngược theo quy tắc phân tích cú pháp của thiết bị đầu cuối.

Liên kết đến mã nguồn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.