สคริปต์แยกวิเคราะห์คำสั่งของ Windows (CMD.EXE) ทำอย่างไร


142

ฉันวิ่งเข้าไปในss64.comซึ่งให้ความช่วยเหลือที่ดีเกี่ยวกับวิธีการเขียนสคริปต์แบทช์ที่ Windows Command Interpreter จะทำงาน

อย่างไรก็ตามฉันไม่สามารถหาคำอธิบายที่ดีเกี่ยวกับไวยากรณ์ของสคริปต์แบบแบตช์ว่าสิ่งต่าง ๆ ขยายหรือไม่ขยายและวิธีการหลบหนีสิ่งต่าง ๆ

นี่คือคำถามตัวอย่างที่ฉันไม่สามารถแก้ไขได้:

  • ระบบใบเสนอราคามีการจัดการอย่างไร? ฉันสร้างสคริปต์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.exeจาก UnxUtils a bที่ผมได้รับ มา%a%ขยายตัวในวิธีที่แตกต่างกันอย่างไร

ขอบคุณสำหรับแสงของคุณ


คำตอบ:


200

เราทำการทดลองเพื่อตรวจสอบไวยากรณ์ของสคริปต์ชุดงาน นอกจากนี้เรายังตรวจสอบความแตกต่างระหว่างแบทช์และโหมดบรรทัดคำสั่ง

Batch Line Parser:

นี่คือภาพรวมโดยย่อของเฟสในตัวแยกวิเคราะห์บรรทัดไฟล์แบทช์:

เฟส 0) อ่านบรรทัด:

ขั้นตอนที่ 1) การขยายเปอร์เซ็นต์:

เฟส 2) ประมวลผลอักขระพิเศษโทเค็นและสร้างบล็อกคำสั่งแคช:พิเศษโทเค็นนี่เป็นกระบวนการที่ซับซ้อนที่ได้รับผลกระทบจากสิ่งต่าง ๆ เช่นอัญประกาศอักขระพิเศษตัวคั่นโทเค็นและเครื่องหมายคาเร็ต

เฟส 3) สะท้อนคำสั่งที่แยกวิเคราะห์เฉพาะเมื่อบล็อกคำสั่งไม่ได้ขึ้นต้นด้วย@และ ECHO เปิดอยู่ที่จุดเริ่มต้นของขั้นตอนก่อนหน้า

เฟส 4) สำหรับ%Xการขยายตัวแปร:เฉพาะเมื่อคำสั่ง FOR แอ็คทีฟและคำสั่งหลังจาก DO กำลังถูกประมวลผล

ขั้นตอนที่ 5) การขยายล่าช้า:เฉพาะเมื่อเปิดใช้งานการขยายล่าช้า

เฟส 5.3) การประมวลผลไปป์:เฉพาะเมื่อคำสั่งอยู่ที่ด้านใดด้านหนึ่งของไปป์

เฟส 5.5) ดำเนินการเปลี่ยนเส้นทาง:

เฟส 6) การประมวลผล CALL / Caret เพิ่มเป็นสองเท่า:เฉพาะเมื่อโทเค็นคำสั่งคือ CALL

เฟส 7) ดำเนินการ:คำสั่งถูกดำเนินการ


นี่คือรายละเอียดสำหรับแต่ละเฟส:

โปรดทราบว่าเฟสที่อธิบายด้านล่างนี้เป็นเพียงรูปแบบการทำงานของตัวแยกวิเคราะห์แบบแบทช์ cmd.exe ที่เกิดขึ้นจริงอาจไม่แสดงถึงขั้นตอนเหล่านี้ แต่รุ่นนี้มีประสิทธิภาพในการทำนายพฤติกรรมของสคริปต์แบบแบทช์

เฟส 0) อ่านบรรทัด:<LF>อ่านบรรทัดของการป้อนข้อมูลผ่านครั้งแรก

  • เมื่ออ่านบรรทัดที่จะวิเคราะห์คำเป็นคำสั่ง<Ctrl-Z>(0x1A) จะอ่านว่าเป็น<LF>(LineFeed 0x0A)
  • เมื่อ GOTO หรือ CALL อ่านบรรทัดขณะสแกนหา: label <Ctrl-Z>จะถือว่าเป็นตัวของมันเองซึ่งจะไม่ถูกแปลงเป็น<LF>

ขั้นตอนที่ 1) การขยายเปอร์เซ็นต์:

  • ดับเบิล%%ถูกแทนที่ด้วยซิงเกิ้ล%
  • การขยายตัวของการขัดแย้ง ( %*, %1,%2ฯลฯ )
  • การขยายตัวของ %var%ถ้า var ไม่มีอยู่แทนที่ด้วยไม่มีอะไร
  • บรรทัดจะถูกปัดเศษในตอนแรก<LF>ไม่ใช่ภายในส่วน%var%ขยาย
  • สำหรับคำอธิบายที่สมบูรณ์อ่านครึ่งแรกของเรื่องนี้จาก dbenham หัวข้อเดียวกัน: Percent Phase

ขั้นตอนที่ 2) ประมวลผลอักขระพิเศษโทเค็นและสร้างบล็อกคำสั่งแคช:นี่เป็นกระบวนการที่ซับซ้อนที่ได้รับผลกระทบจากสิ่งต่าง ๆ เช่นอัญประกาศอักขระพิเศษตัวคั่นโทเค็นและเครื่องหมายคาเร็ต สิ่งที่ตามมาคือการประมาณของกระบวนการนี้

มีแนวคิดที่สำคัญตลอดช่วงนี้

  • โทเค็นเป็นเพียงสตริงของอักขระที่ถือว่าเป็นหน่วย
  • โทเค็นถูกคั่นด้วยตัวคั่นโทเค็น ตัวคั่นโทเค็นมาตรฐานคือ<space> <tab> ; , = <0x0B> <0x0C>และ<0xFF>
    ตัวคั่นโทเค็นต่อเนื่องจะถือว่าเป็นหนึ่ง - ไม่มีโทเค็นว่างระหว่างตัวคั่นโทเค็น
  • ไม่มีตัวคั่นโทเค็นภายในสตริงที่ยกมา สตริงที่ยกมาทั้งหมดจะถือว่าเป็นส่วนหนึ่งของโทเค็นเดียวเสมอ โทเค็นเดียวอาจประกอบด้วยการรวมกันของสตริงที่ยกมาและตัวอักษรที่ไม่ยกมา

อักขระต่อไปนี้อาจมีความหมายพิเศษในเฟสนี้ขึ้นอยู่กับบริบท: <CR> ^ ( @ & | < > <LF> <space> <tab> ; , = <0x0B> <0x0C> <0xFF>

ดูตัวละครแต่ละตัวจากซ้ายไปขวา:

  • หาก<CR>ลบออกราวกับว่ามันไม่เคยมี (ยกเว้นพฤติกรรมการเปลี่ยนเส้นทางแปลก)
  • หากเครื่องหมายรูปหมวก ( ^) ตัวละครถัดไปจะถูกหลบหนีและเครื่องหมายรูปหมวกจะถูกลบออก อักขระที่หลีกเลี่ยงสูญเสียความหมายพิเศษทั้งหมด (ยกเว้น<LF>)
  • ถ้า quote ( ") ให้สลับค่าสถานะ quote หากเครื่องหมายคำพูดเปิดใช้งานอยู่เฉพาะ"และ<LF>พิเศษเท่านั้น ตัวละครอื่น ๆ ทั้งหมดจะสูญเสียความหมายพิเศษของพวกเขาจนกว่าการอ้างอิงครั้งต่อไปจะสลับการปิดการอ้างอิง มันเป็นไปไม่ได้ที่จะหนีจากคำพูดปิด อักขระที่ยกมาทั้งหมดอยู่ในโทเค็นเดียวกันเสมอ
  • <LF>ปิดการใช้งานเครื่องหมายคำพูดเสมอ พฤติกรรมอื่น ๆ แตกต่างกันไปขึ้นอยู่กับบริบท <LF>แต่คำพูดที่ไม่เคยเปลี่ยนแปลงพฤติกรรมของ
    • หนี <LF>
      • <LF> ถูกปล้น
      • ตัวละครต่อไปคือการหลบหนี หากในตอนท้ายของบัฟเฟอร์บรรทัดแล้วบรรทัดถัดไปจะถูกอ่านและประมวลผลโดยเฟส 1 และ 1.5 และผนวกเข้ากับหนึ่งในปัจจุบันก่อนที่จะหลบหนีตัวอักษรต่อไป หากตัวละครถัดไปคือ<LF>มันจะถือว่าเป็นตัวอักษรหมายถึงกระบวนการนี้จะไม่เกิดซ้ำ
    • <LF>ไม่ใช้ค่าEscape ไม่อยู่ในวงเล็บ
      • <LF> ถูกปล้นและการแยกวิเคราะห์บรรทัดปัจจุบันถูกยกเลิก
      • อักขระที่เหลืออยู่ในบัฟเฟอร์บรรทัดจะถูกละเว้น
    • ไม่ใช้ค่า Escape <LF>ภายในบล็อกที่ใส่เครื่องหมายวงเล็บใน
      • <LF> ถูกแปลงเป็น <space>
      • หากท้ายบรรทัดบัฟเฟอร์แล้วบรรทัดถัดไปจะถูกอ่านและผนวกเข้ากับบัฟเฟอร์ปัจจุบัน
    • unescaped <LF>ภายในบล็อกคำสั่งวงเล็บ
      • <LF>จะถูกแปลงเป็น<LF><space>และ<space>จะถือว่าเป็นส่วนหนึ่งของบรรทัดถัดไปของบล็อกคำสั่ง
      • หากท้ายบัฟเฟอร์บรรทัดบรรทัดถัดไปจะถูกอ่านและผนวกเข้ากับพื้นที่
  • หากหนึ่งในตัวละครพิเศษ& | <หรือ>แยกบรรทัดที่จุดนี้เพื่อจัดการท่อ, การต่อคำสั่งและการเปลี่ยนเส้นทาง
    • ในกรณีของไพพ์ ( |) แต่ละข้างเป็นคำสั่งแยกต่างหาก (หรือบล็อกคำสั่ง) ที่ได้รับการจัดการพิเศษในเฟส 5.3
    • ในกรณีของ&, &&หรือการ||ต่อคำสั่ง, แต่ละด้านของการต่อข้อมูลจะถือว่าเป็นคำสั่งแยกกัน
    • ในกรณีของ<, <<, >หรือ>>การเปลี่ยนเส้นทางข้อเปลี่ยนเส้นทางจะแยกออกชั่วคราวแล้วต่อท้ายคำสั่งปัจจุบัน ส่วนคำสั่งเปลี่ยนเส้นทางประกอบด้วยตัวเลขตัวจัดการไฟล์ทางเลือกตัวดำเนินการเปลี่ยนเส้นทางและโทเค็นปลายทางการเปลี่ยนเส้นทาง
      • หากโทเค็นที่นำหน้าโอเปอเรเตอร์โอเปอเรเตอร์เป็นตัวเลขที่ไม่มีค่า Escape เดียวหลักจะระบุว่าหมายเลขอ้างอิงไฟล์จะถูกเปลี่ยนเส้นทาง หากไม่พบโทเค็นการจัดการดังนั้นค่าเริ่มต้นการเปลี่ยนเส้นทางการส่งออกเป็น 1 (stdout) และค่าเริ่มต้นการเปลี่ยนเส้นทางการป้อนข้อมูลเป็น 0 (stdin)
  • หากโทเค็นแรกสำหรับคำสั่งนี้ (ก่อนที่จะย้ายการเปลี่ยนเส้นทางไปยังจุดสิ้นสุด) เริ่มต้นด้วย@แสดงว่า@มีความหมายพิเศษ ( @ไม่พิเศษในบริบทอื่น ๆ )
    • พิเศษ@จะถูกลบออก
    • หาก ECHO เปิดอยู่คำสั่งนี้พร้อมกับคำสั่งต่อไปนี้ในบรรทัดนี้จะถูกแยกออกจากเฟส 3 echo ถ้า@เป็นก่อนการเปิด(บล็อกทั้งหมดที่อยู่ในวงเล็บจะถูกแยกออกจาก echo เฟส 3
  • วงเล็บกระบวนการ (จัดทำขึ้นสำหรับคำสั่งผสมข้ามหลายบรรทัด):
    • หาก parser ไม่ได้ค้นหาโทเค็นคำสั่งแสดงว่า(ไม่พิเศษ
    • หาก parser กำลังมองหาโทเค็นคำสั่งและค้นหา(จากนั้นเริ่มคำสั่งผสมใหม่และเพิ่มตัวนับวงเล็บ
    • หากตัวนับวงเล็บเป็น> 0 ให้)ยกเลิกคำสั่งผสมและลดจำนวนตัวนับวงเล็บ
    • หากถึงจุดสิ้นสุดของบรรทัดและตัวนับวงเล็บคือ> 0 ดังนั้นบรรทัดถัดไปจะถูกผนวกเข้ากับคำสั่งผสม (เริ่มอีกครั้งด้วยเฟส 0)
    • หากตัวนับวงเล็บเป็น 0 และเครื่องมือแยกวิเคราะห์กำลังมองหาคำสั่ง )ฟังก์ชันจะคล้ายกับREMคำสั่งตราบใดที่มันถูกตามด้วยตัวคั่นโทเค็นอักขระพิเศษบรรทัดใหม่หรือจุดสิ้นสุดของไฟล์ทันที
      • อักขระพิเศษทั้งหมดสูญเสียความหมายยกเว้น ^ (สามารถต่อการต่อบรรทัดได้)
      • เมื่อถึงจุดสิ้นสุดของบรรทัดตรรกะแล้วคำสั่ง "ทั้งหมด" จะถูกยกเลิก
  • แต่ละคำสั่งจะถูกแยกวิเคราะห์เป็นชุดโทเค็น โทเค็นแรกจะถือว่าเป็นโทเค็นคำสั่งเสมอ (หลังจากพิเศษ)@ถูกปล้นและการเปลี่ยนเส้นทางถูกย้ายไปยังจุดสิ้นสุด)
    • ตัวคั่นโทเค็นนำหน้าก่อนโทเค็นคำสั่งจะถูกปล้น
    • เมื่อแยกโทเค็นคำสั่ง (ทำหน้าที่เป็นตัวคั่นคำสั่งโทเค็นนอกเหนือจากตัวคั่นโทเค็นมาตรฐาน
    • การจัดการโทเค็นที่ตามมาขึ้นอยู่กับคำสั่ง
  • คำสั่งส่วนใหญ่จะทำการเชื่อมอาร์กิวเมนต์ทั้งหมดหลังจากโทเค็นคำสั่งเป็นโทเค็นอาร์กิวเมนต์เดียว ตัวคั่นโทเค็นอาร์กิวเมนต์ทั้งหมดจะถูกเก็บรักษาไว้ ตัวเลือกอาร์กิวเมนต์มักจะไม่แยกวิเคราะห์จนกว่าจะถึงระยะที่ 7
  • สามคำสั่งได้รับการจัดการพิเศษ - IF, FOR และ REM
    • IF ถูกแบ่งออกเป็นสองหรือสามส่วนที่แตกต่างกันซึ่งดำเนินการอย่างอิสระ ข้อผิดพลาดทางไวยากรณ์ในโครงสร้าง IF จะส่งผลให้เกิดข้อผิดพลาดทางไวยากรณ์ที่ร้ายแรง
      • การดำเนินการเปรียบเทียบเป็นคำสั่งจริงที่ไหลไปตลอดจนถึงระยะที่ 7
        • ตัวเลือก IF ทั้งหมดจะถูกวิเคราะห์คำอย่างสมบูรณ์ในเฟส 2
        • ตัวคั่นโทเค็นติดต่อกันยุบลงในช่องว่างเดียว
        • ขึ้นอยู่กับตัวดำเนินการเปรียบเทียบจะมีโทเค็นค่าหนึ่งหรือสองค่าที่ระบุ
      • บล็อกคำสั่ง True คือชุดคำสั่งหลังจากเงื่อนไขและแยกวิเคราะห์เหมือนกับบล็อกคำสั่งอื่น ๆ ถ้าจะใช้ ELSE บล็อก True จะต้องถูกวงเล็บ
      • บล็อกคำสั่ง False ที่เป็นทางเลือกคือชุดคำสั่งหลังจาก ELSE อีกครั้งบล็อกคำสั่งนี้จะถูกแยกวิเคราะห์ตามปกติ
      • บล็อกคำสั่งจริงและเท็จจะไม่ไหลเข้าสู่ระยะต่อมาโดยอัตโนมัติ การประมวลผลที่ตามมาของพวกเขาถูกควบคุมโดยเฟส 7
    • FOR ถูกแบ่งออกเป็นสองส่วนหลังจากการใช้ DO ข้อผิดพลาดทางไวยากรณ์ในการก่อสร้าง FOR จะส่งผลให้เกิดข้อผิดพลาดทางไวยากรณ์ที่ร้ายแรง
      • ส่วนที่ผ่าน DO เป็นคำสั่ง FOR ซ้ำที่เกิดขึ้นจริงซึ่งไหลไปตลอดทางจนถึงเฟส 7
        • ตัวเลือก FOR ทั้งหมดจะถูกวิเคราะห์คำอย่างสมบูรณ์ในเฟส 2
        • ในวงเล็บถือว่าข้อเป็น<LF> <space>หลังจากแยกประโยค IN แล้วโทเค็นทั้งหมดจะถูกต่อกันเข้าด้วยกันเพื่อสร้างโทเค็นเดี่ยว
        • ตัวคั่นโทเค็นที่ไม่ใช้ค่า Escape / unquoted ติดต่อกันจะยุบลงในช่องว่างเดียวตลอดทั้งคำสั่ง FOR ถึง DO
      • ส่วนหลังจาก DO คือบล็อกคำสั่งที่แยกวิเคราะห์ตามปกติ การประมวลผลที่ตามมาของบล็อกคำสั่ง DO ถูกควบคุมโดยการวนซ้ำในเฟส 7
    • การตรวจพบ REM ในระยะที่ 2 นั้นถือว่าแตกต่างอย่างมากจากคำสั่งอื่นทั้งหมด
      • มีการแยกวิเคราะห์โทเค็นอาร์กิวเมนต์เดียวเท่านั้น - ตัวแยกวิเคราะห์จะละเว้นอักขระหลังจากโทเค็นอาร์กิวเมนต์แรก
      • คำสั่ง REM อาจปรากฏในเอาต์พุตเฟส 3 แต่คำสั่งไม่เคยถูกเรียกใช้งานและข้อความอาร์กิวเมนต์ดั้งเดิมจะถูก echoed - carets ที่กำลังหลบหนีจะไม่ถูกลบยกเว้น ...
        • หากมีเพียงโทเค็นการโต้แย้งเพียงตัวเดียวที่จบลงด้วยการไม่ใช้ Escape ^ที่สิ้นสุดบรรทัดโทเค็นการโต้แย้งจะถูกโยนออกไปและบรรทัดต่อมาจะถูกแยกวิเคราะห์และผนวกเข้ากับ REM ^ซ้ำนี้จนกว่าจะมีมากกว่าหนึ่งโทเค็นหรือตัวอักษรตัวสุดท้ายไม่ได้เป็น
  • หากโทเค็นคำสั่งเริ่มต้นด้วย:และนี่คือรอบแรกของเฟส 2 (ไม่ใช่การรีสตาร์ทเนื่องจาก CALL ในเฟส 6)
    • โดยปกติแล้วโทเค็นจะถือว่าเป็นป้ายกำกับที่ยังไม่ได้ดำเนินการ
      • ส่วนที่เหลือของเส้นจะแยกกันอย่างไร), <, >, &และ|ไม่ได้มีความหมายพิเศษ ส่วนที่เหลือทั้งหมดของบรรทัดนั้นถือว่าเป็นส่วนหนึ่งของป้ายกำกับ "คำสั่ง"
      • ^ยังคงเป็นพิเศษหมายความว่าสายต่อเนื่องสามารถใช้ในการผนวกบรรทัดต่อมาให้กับต้นสังกัด
      • ป้าย unexecutedภายในบล็อกวงเล็บจะมีผลในไวยากรณ์ผิดพลาดร้ายแรงจนกว่าจะมีการทันทีตามคำสั่งหรือดำเนินการป้ายในบรรทัดถัดไป
        • (ไม่มีความหมายพิเศษสำหรับคำสั่งแรกที่ตามหลังป้ายกำกับที่ยังไม่ได้ดำเนินการ
      • คำสั่งถูกยกเลิกหลังจากการแยกวิเคราะห์ฉลากเสร็จสมบูรณ์ ขั้นตอนต่อมาจะไม่เกิดขึ้นกับฉลาก
    • มีข้อยกเว้นสามข้อที่สามารถทำให้ป้ายกำกับที่พบในระยะที่ 2 ถูกถือว่าเป็นป้ายกำกับที่ดำเนินการซึ่งจะดำเนินการแยกวิเคราะห์ผ่านขั้นตอนที่ 7 ต่อไป
      • มีการเปลี่ยนเส้นทางคือว่าแจ๋วฉลากโทเค็นและมี|ท่อหรือ&, &&หรือ||เรียงต่อกันคำสั่งในบรรทัด
      • มีการเปลี่ยนเส้นทางที่นำหน้าโทเค็นป้ายกำกับและคำสั่งอยู่ในบล็อกที่มีเครื่องหมายวงเล็บ
      • โทเค็นเลเบลคือคำสั่งแรกสุดของบรรทัดภายในบล็อกที่อยู่ในวงเล็บและบรรทัดด้านบนจบลงด้วยป้ายกำกับที่ไม่ได้ดำเนินการ
    • สิ่งต่อไปนี้เกิดขึ้นเมื่อพบป้ายกำกับที่ดำเนินการในเฟส 2
      • เลเบลอาร์กิวเมนต์และการเปลี่ยนทิศทางทั้งหมดถูกแยกออกจากเอาต์พุต echo ใด ๆ ในเฟส 3
      • คำสั่งที่ต่อกันใด ๆ บนบรรทัดจะถูกวิเคราะห์และดำเนินการอย่างสมบูรณ์
    • สำหรับข้อมูลเพิ่มเติมเกี่ยวกับป้ายดำเนินการกับป้าย unexecutedดูhttps://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

เฟส 3) สะท้อนคำสั่งที่แยกวิเคราะห์เฉพาะเมื่อบล็อกคำสั่งไม่ได้ขึ้นต้นด้วย@และ ECHO เปิดอยู่ที่จุดเริ่มต้นของขั้นตอนก่อนหน้า

เฟส 4) สำหรับ%Xการขยายตัวแปร:เฉพาะเมื่อคำสั่ง FOR แอ็คทีฟและคำสั่งหลังจาก DO กำลังถูกประมวลผล

  • ณ จุดนี้เฟส 1 ของการประมวลผลชุดจะมีแปลงแล้วสำหรับตัวแปรเช่นเข้า%%X %Xบรรทัดคำสั่งมีกฎการขยายตัวร้อยละที่แตกต่างกันสำหรับเฟส 1 นี้คือเหตุผลที่บรรทัดคำสั่งใช้%Xแต่ไฟล์ชุดใช้%%Xสำหรับสำหรับตัวแปร
  • สำหรับชื่อตัวแปรคำนึงถึงขนาดตัวพิมพ์ แต่~modifiersไม่คำนึงถึงขนาดตัวพิมพ์
  • ~modifiersมีความสำคัญมากกว่าชื่อตัวแปร หากตัวอักษรต่อไปนี้~เป็นทั้งตัวดัดแปลงและชื่อตัวแปรที่ถูกต้องสำหรับและมีตัวละครที่ตามมาที่เป็นชื่อตัวแปรสำหรับการใช้งานแล้วตัวละครจะถูกตีความว่าเป็นตัวดัดแปลง
  • สำหรับชื่อตัวแปรนั้นเป็นชื่อโกลบอล แต่จะอยู่ในบริบทของประโยคคำสั่ง DO เท่านั้น หากรูทีนถูกเรียกจากภายในประโยค FOR DO ตัวแปร FOR จะไม่ถูกขยายภายในรูทีน CALLed แต่ถ้ารูทีนมีคำสั่ง FOR ของตัวเองตัวแปรที่กำหนดไว้ในปัจจุบันทั้งหมดจะสามารถเข้าถึงคำสั่ง DO ภายในได้
  • สำหรับชื่อตัวแปรสามารถนำกลับมาใช้ใหม่ได้ภายใน FORs ที่ซ้อนกัน ค่า Inner สำหรับมีความสำคัญกว่าเดิม แต่เมื่อ INNER FOR ปิดลงค่าด้านนอก FOR จะถูกกู้คืน
  • หาก ECHO เปิดอยู่ที่จุดเริ่มต้นของขั้นตอนนี้ระยะที่ 3) จะถูกทำซ้ำเพื่อแสดงคำสั่ง DO แยกวิเคราะห์หลังจากตัวแปร FOR ถูกขยาย

---- จากจุดนี้เป็นต้นไปแต่ละคำสั่งที่ระบุในเฟส 2 จะถูกประมวลผลแยกกัน
---- ขั้นตอนที่ 5 ถึง 7 จะเสร็จสมบูรณ์สำหรับคำสั่งเดียวก่อนที่จะย้ายไปยังถัดไป

ขั้นตอนที่ 5) การขยายล่าช้า:เฉพาะเมื่อการขยายล่าช้าเปิดอยู่คำสั่งไม่ได้อยู่ในบล็อกที่อยู่ในวงเล็บที่ด้านใดด้านหนึ่งของไปป์และคำสั่งไม่ได้เป็นชุดสคริปต์ "เปล่า" (ชื่อสคริปต์ที่ไม่มีวงเล็บ, CALL, การต่อประสานคำสั่ง หรือท่อ)

  • แต่ละโทเค็นสำหรับคำสั่งจะถูกวิเคราะห์คำสำหรับการขยายที่ล่าช้าอย่างอิสระ
    • คำสั่งส่วนใหญ่แยกโทเค็นอย่างน้อยสองโทเค็น - โทเค็นคำสั่งโทเค็นอาร์กิวเมนต์และโทเค็นปลายทางการเปลี่ยนเส้นทางแต่ละรายการ
    • คำสั่ง FOR แยกวิเคราะห์โทเค็นส่วนคำสั่ง IN เท่านั้น
    • คำสั่ง IF แยกวิเคราะห์ค่าการเปรียบเทียบเท่านั้น - หนึ่งหรือสองขึ้นอยู่กับผู้ประกอบการเปรียบเทียบ
  • สำหรับโทเค็นการวิเคราะห์คำแต่ละรายการก่อนอื่นให้ตรวจสอบว่ามีโทเค็นใด ๆ หรือ!ไม่ มิฉะนั้นโทเค็นจะไม่ถูกวิเคราะห์ - สำคัญสำหรับ^อักขระ หากโทเค็นประกอบด้วย!ให้สแกนอักขระแต่ละตัวจากซ้ายไปขวา:
    • ถ้าเป็น caret (^ ) อักขระถัดไปไม่มีความหมายพิเศษคาเร็ตจะถูกลบออก
    • หากเป็นเครื่องหมายอัศเจรีย์ให้ค้นหาเครื่องหมายอัศเจรีย์ถัดไป (ไม่พบเครื่องหมายคาเร็ตอีกต่อไป) ขยายเป็นค่าของตัวแปร
      • เปิดอย่างต่อเนื่อง !จะถูกยุบเป็นหนึ่งเดียว!
      • ส่วนที่เหลือที่ไม่ได้รับการชำระ!จะถูกลบออก
    • การขยาย vars ในขั้นนี้คือ "ปลอดภัย" เนื่องจากไม่พบอักขระพิเศษอีกต่อไป (แม้<CR>หรือ<LF>)
    • สำหรับคำอธิบายที่สมบูรณ์ยิ่งขึ้นอ่านครึ่งหลังของหัวข้อนี้จากหัวข้อเดียวกัน dbenham - Exclamation Point Phase

เฟส 5.3) การประมวลผลไปป์:เฉพาะเมื่อคำสั่งอยู่ที่ด้านใดด้านหนึ่งของไปป์
แต่ละด้านของไพพ์จะถูกประมวลผลอย่างอิสระและแบบอะซิงโครนัส

  • หากคำสั่งอยู่ภายใน cmd.exe หรือเป็นไฟล์แบตช์หรือหากเป็นบล็อกคำสั่งที่ใช้วงเล็บแล้วจะถูกดำเนินการในเธรด cmd.exe ใหม่ผ่านทาง%comspec% /S /D /c" commandBlock"ดังนั้นบล็อกคำสั่งจะได้รับการรีสตาร์ทเฟส แต่คราวนี้ ในโหมดบรรทัดคำสั่ง
    • หากบล็อกคำสั่งวงเล็บแล้วทั้งหมดที่มีคำสั่งก่อนและหลังจะถูกแปลงเป็น<LF> <space>&อื่น ๆ<LF>ถูกปล้น
  • นี่คือจุดสิ้นสุดของการประมวลผลสำหรับคำสั่งไพพ์
  • ดูที่เหตุใดการขยายที่ล่าช้าจึงล้มเหลวเมื่ออยู่ในบล็อกของ piped code สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการแยกวิเคราะห์ท่อและการประมวลผล

ขั้นตอน 5.5) ดำเนินการเปลี่ยนเส้นทาง: การเปลี่ยนเส้นทางใด ๆ ที่ค้นพบในระยะที่ 2 จะถูกดำเนินการในขณะนี้

เฟส 6) การประมวลผล CALL / Caret เพิ่มเป็นสองเท่า:เฉพาะในกรณีที่คำสั่งโทเค็นเป็น CALL หรือหากข้อความก่อนที่ตัวคั่นโทเค็นมาตรฐานที่เกิดขึ้นครั้งแรกคือ CALL หาก CALL ถูกแยกวิเคราะห์จากโทเค็นคำสั่งที่มีขนาดใหญ่ส่วนที่ไม่ได้ใช้จะถูกนำไปรวมกับโทเค็นอาร์กิวเมนต์ก่อนดำเนินการต่อ

  • สแกนโทเค็นอาร์กิวเมนต์เพื่อหาค่าที่/?ไม่ได้อ้างอิง หากพบที่ใดก็ได้ภายในโทเค็นให้ยกเลิกขั้นตอนที่ 6 และไปที่ขั้นตอนที่ 7 ซึ่งจะมีการพิมพ์ HELP สำหรับ CALL
  • ลบออกก่อน CALLดังนั้นจึงสามารถซ้อน CALL หลายรายการได้
  • carets สองเท่าทั้งหมด
  • รีสตาร์ทเฟส 1, 1.5 และ 2 แต่อย่าดำเนินการต่อในเฟส 3
    • เครื่องหมายคาเร็ตที่เป็นสองเท่าใด ๆ จะถูกลดกลับเป็นคาเร็ตหนึ่งอันตราบใดที่มันไม่ได้ยกมา แต่น่าเสียดายที่ carets ที่ยกมายังคงเป็นสองเท่า
    • ขั้นตอนที่ 1 การเปลี่ยนแปลงเล็กน้อย
      • ข้อผิดพลาดการขยายในขั้นตอนที่ 1.2 หรือ 1.3 ยกเลิก CALL แต่ข้อผิดพลาดนั้นไม่ร้ายแรง - การประมวลผลแบบต่อเนื่องจะดำเนินต่อไป
    • งานเฟส 2 จะเปลี่ยนไปเล็กน้อย
      • ตรวจพบการเปลี่ยนทิศทางที่ไม่มีการอ้างอิงใหม่ซึ่งไม่ได้กล่าวถึงซึ่งไม่ถูกตรวจพบในรอบแรกของเฟส 2 แต่จะถูกลบออก (รวมถึงชื่อไฟล์) โดยไม่ทำการเปลี่ยนเส้นทางจริงๆ
      • เครื่องหมายคาเร็ตที่ยังไม่ได้ใส่เครื่องหมายใหม่ใด ๆ ที่ปรากฏในตอนท้ายของบรรทัดจะถูกลบออกโดยไม่ดำเนินการต่อเนื่องของบรรทัด
      • CALL ถูกยกเลิกโดยไม่มีข้อผิดพลาดหากตรวจพบสิ่งต่อไปนี้
        • ที่เพิ่งปรากฏขึ้นโดยไม่ระบุชื่อ unescaped &หรือ|
        • โทเค็นคำสั่ง resultant เริ่มต้นด้วย unquoted, unescaped (
        • โทเค็นแรก ๆ หลังจาก CALL ที่ถูกลบเริ่มต้นด้วย @
      • หากคำสั่งผลลัพธ์เป็น IF หรือ FOR ที่ถูกต้องดูเหมือนว่าการดำเนินการจะล้มเหลวในภายหลังโดยมีข้อผิดพลาดที่ระบุว่าIFหรือFORไม่ได้รับการยอมรับว่าเป็นคำสั่งภายในหรือภายนอก
      • แน่นอนโทรไม่ได้ยกเลิกในรอบที่ 2 ของขั้นตอนที่ 2 :ถ้าโทเค็นคำสั่งผลเป็นจุดเริ่มต้นที่มีฉลาก
  • หากโทเค็นคำสั่งผลลัพธ์เป็น CALL ให้รีสตาร์ทเฟส 6 (ทำซ้ำจนกว่าจะไม่มี CALL อีกต่อไป)
  • หากโทเค็นคำสั่ง resultant เป็นแบตช์สคริปต์หรือฉลาก: การดำเนินการของ CALL จะได้รับการจัดการอย่างเต็มที่โดยส่วนที่เหลือของเฟส 6
    • พุชตำแหน่งไฟล์แบตช์สคริปต์ปัจจุบันบน call stack เพื่อให้การดำเนินการสามารถดำเนินต่อจากตำแหน่งที่ถูกต้องเมื่อ CALL เสร็จสมบูรณ์
    • ตั้งค่าอาร์กิวเมนต์โทเค็น% 0,% 1,% 2, ... % N และ% * สำหรับการโทรโดยใช้โทเค็นผลลัพธ์ทั้งหมด
    • หากโทเค็นคำสั่งเป็นป้ายกำกับที่ขึ้นต้นด้วย :แล้ว
      • รีสตาร์ทเฟส 5 สิ่งนี้อาจส่งผลต่อสิ่งที่: label ถูกเรียก แต่เนื่องจากโทเค็น% 0 และอื่น ๆ ได้รับการตั้งค่าแล้วมันจะไม่เปลี่ยนอาร์กิวเมนต์ที่ส่งผ่านไปยังรูทีน CALLed
      • ดำเนินการป้ายกำกับ GOTO เพื่อวางตำแหน่งตัวชี้ไฟล์ที่จุดเริ่มต้นของรูทีนย่อย (ละเว้นโทเค็นอื่น ๆ ที่อาจเป็นไปตาม: label) ดูขั้นตอนที่ 7 สำหรับกฎเกี่ยวกับวิธีการทำงานของ GOTO
    • การควบคุมการถ่ายโอนอื่นไปยังสคริปต์ชุดงานที่ระบุ
    • การดำเนินการของ CALLed: เลเบลหรือสคริปต์ดำเนินต่อไปจนกระทั่ง EXIT / B หรือถึงจุดสิ้นสุดไฟล์สิ้นสุด ณ จุดที่สแต็ก CALL ถูกเปิดและการดำเนินการต่อจากตำแหน่งไฟล์ที่บันทึกไว้
      เฟส 7 ไม่ถูกเรียกใช้งานสำหรับสคริปต์ที่เรียกว่าหรือ: ป้ายกำกับ
  • มิฉะนั้นผลลัพธ์ของเฟส 6 จะเข้าสู่เฟส 7 เพื่อดำเนินการ

เฟส 7) ดำเนินการ:คำสั่งถูกดำเนินการ

  • 7.1 - ดำเนินการคำสั่งภายใน - หากมีการยกโทเค็นคำสั่งให้ข้ามขั้นตอนนี้ มิฉะนั้นพยายามแยกคำสั่งภายในและดำเนินการ
    • การทดสอบต่อไปนี้ทำขึ้นเพื่อพิจารณาว่าโทเค็นคำสั่งที่ไม่ได้อ้างถึงแสดงถึงคำสั่งภายในหรือไม่:
      • หากโทเค็นคำสั่งตรงกับคำสั่งภายในทั้งหมดให้รันคำสั่งนั้น
      • อื่นทำลายโทเค็นคำสั่งก่อนที่จะเกิดขึ้นครั้งแรก+ / [ ] <space> <tab> , ;หรือ=
        ถ้าข้อความก่อนหน้านี้เป็นคำสั่งภายในแล้วจำคำสั่งนั้น
        • ถ้าอยู่ในโหมดบรรทัดคำสั่งหรือถ้าคำสั่งมาจากบล็อกวงเล็บ, IF บล็อกคำสั่งจริงหรือเท็จ, บล็อกคำสั่ง FOR DO, หรือเกี่ยวข้องกับการต่อคำสั่งแล้วรันคำสั่งภายใน
        • อื่น (ต้องเป็นคำสั่งแบบสแตนด์อะโลนในโหมดแบตช์) สแกนโฟลเดอร์ปัจจุบันและไฟล์ PATH สำหรับไฟล์. COM, .EXE, .BAT หรือ. CMD ที่มีชื่อฐานตรงกับโทเค็นคำสั่งดั้งเดิม
          • หากไฟล์แรกที่ตรงกันคือ .BAT หรือ. CMD ให้ไปที่ 7.3.exec แล้วจึงเรียกใช้งานสคริปต์ดังกล่าว
          • อื่น (ไม่พบคู่ที่ตรงกันหรือคู่แรกคือ. EXE หรือ. COM) เรียกใช้งานคำสั่งภายในที่จำได้
      • อื่นทำลายโทเค็นคำสั่งก่อนที่จะเกิดขึ้นครั้งแรก. \หรือ:
        ถ้าข้อความก่อนหน้านี้ไม่ได้เป็นคำสั่งภายในแล้วข้ามไป 7.2
        ข้อความอื่น ๆ อาจเป็นคำสั่งภายใน จำคำสั่งนี้
      • ทำลายโทเค็นคำสั่งก่อนที่จะเกิดขึ้นครั้งแรกของ+ / [ ] <space> <tab> , ;หรือ=
        ถ้าข้อความก่อนหน้านี้เป็นเส้นทางไปยังไฟล์ที่มีอยู่แล้ว goto 7.2
        อื่นดำเนินการคำสั่งภายในที่จำได้
    • หากคำสั่งภายในถูกแยกวิเคราะห์จากโทเค็นคำสั่งขนาดใหญ่ส่วนโทเค็นคำสั่งที่ไม่ได้ใช้จะรวมอยู่ในรายการอาร์กิวเมนต์
    • เพียงเพราะโทเค็นคำสั่งถูกแยกวิเคราะห์เป็นคำสั่งภายในไม่ได้หมายความว่ามันจะดำเนินการได้สำเร็จ แต่ละคำสั่งภายในมีกฎของตัวเองสำหรับวิธีการแยกวิเคราะห์อาร์กิวเมนต์และตัวเลือกและไวยากรณ์ที่ได้รับอนุญาต
    • คำสั่งภายในทั้งหมดจะพิมพ์ความช่วยเหลือแทนการปฏิบัติหน้าที่หาก/?ตรวจพบ ส่วนใหญ่รู้จัก/?ถ้ามันปรากฏที่ใดก็ได้ในการโต้แย้ง แต่ไม่กี่คำสั่งเช่น ECHO /?และตลาดหลักทรัพย์พิมพ์เฉพาะความช่วยเหลือหากโทเค็นอาร์กิวเมนต์แรกเริ่มต้นด้วย
    • ตลาดหลักทรัพย์มีความหมายที่น่าสนใจ:
      • หากคำสั่ง SET มีอัญประกาศก่อนที่ชื่อตัวแปรและส่วนขยายจะเปิดใช้งาน
        set "name=content" ignored -> value = content
        ดังนั้นข้อความระหว่างเครื่องหมายเท่ากับแรกและเครื่องหมายคำพูดสุดท้ายจะถูกใช้เป็นเนื้อหา ข้อความหลังจากเครื่องหมายคำพูดสุดท้ายจะถูกละเว้น หากไม่มีเครื่องหมายคำพูดหลังเครื่องหมายเท่ากับจะใช้ส่วนที่เหลือเป็นเนื้อหา
      • หากคำสั่ง SET ไม่มีเครื่องหมายอัญประกาศหน้าชื่อ
        set name="content" not ignored -> value = "content" not ignored
        หมายถึงส่วนที่เหลือทั้งหมดของบรรทัดหลังจากใช้เครื่องหมายเท่ากับเป็นเนื้อหารวมถึงอัญประกาศใด ๆ และทั้งหมดที่อาจมีอยู่
    • การเปรียบเทียบ IF จะได้รับการประเมินและขึ้นอยู่กับว่าเงื่อนไขเป็นจริงหรือเท็จบล็อกคำสั่งที่ขึ้นต่อกันของการแยกวิเคราะห์ที่เหมาะสมแล้วจะถูกประมวลผลเริ่มต้นด้วยเฟส 5
    • คำสั่งย่อย IN ของคำสั่ง FOR ถูกวนซ้ำอย่างเหมาะสม
      • หากนี่เป็น FOR / F ที่วนซ้ำเอาต์พุตของบล็อกคำสั่งดังนั้น:
        • ส่วนคำสั่ง IN ถูกดำเนินการในกระบวนการ cmd.exe ใหม่ผ่านทาง CMD / C
        • บล็อกคำสั่งต้องผ่านกระบวนการแยกวิเคราะห์ทั้งหมดในครั้งที่สอง แต่ครั้งนี้ในบริบทบรรทัดคำสั่ง
        • ECHO จะเริ่มทำงานและการขยายที่ล่าช้ามักจะเริ่มต้นที่ปิดใช้งาน (ขึ้นอยู่กับการตั้งค่ารีจิสทรี)
        • การเปลี่ยนแปลงสภาพแวดล้อมทั้งหมดที่ทำโดยบล็อกคำสั่ง IN clause จะหายไปเมื่อกระบวนการ child cmd.exe สิ้นสุดลง
      • สำหรับการทำซ้ำแต่ละครั้ง:
        • มีการกำหนดค่าตัวแปร FOR
        • บล็อกคำสั่ง DO ที่แจงแล้วจะถูกประมวลผลแล้วเริ่มต้นด้วยเฟส 4
    • GOTO ใช้ตรรกะต่อไปนี้เพื่อค้นหา: label
      • ป้ายกำกับจะถูกแยกวิเคราะห์จากโทเค็นอาร์กิวเมนต์แรก
      • สแกนสคริปต์สำหรับการเกิดขึ้นของฉลากถัดไป
        • การสแกนเริ่มต้นจากตำแหน่งไฟล์ปัจจุบัน
        • หากถึงจุดสิ้นสุดของไฟล์การสแกนจะวนกลับไปที่จุดเริ่มต้นของไฟล์และไปยังจุดเริ่มต้นเดิม
      • การสแกนจะหยุดลงเมื่อพบป้ายกำกับครั้งแรกและตัวชี้ไฟล์ถูกตั้งค่าเป็นบรรทัดตามป้ายกำกับทันที การดำเนินการของสคริปต์ดำเนินการต่อจากจุดนั้น โปรดทราบว่า GOTO จริงที่ประสบความสำเร็จจะยกเลิกบล็อกโค้ดใด ๆ ที่ถูกแยกวิเคราะห์ทันทีรวมถึง FOR ลูป
      • หากไม่พบฉลากหรือโทเค็นฉลากหายไป GOTO จะล้มเหลวข้อความแสดงข้อผิดพลาดจะถูกพิมพ์และสแตกการโทรจะปรากฏขึ้น สิ่งนี้ทำหน้าที่เป็น EXIT / B ได้อย่างมีประสิทธิภาพยกเว้นคำสั่งแยกวิเคราะห์ใด ๆ ในบล็อกคำสั่งปัจจุบันที่ติดตาม GOTO จะยังคงทำงาน แต่ในบริบทของ CALLer (บริบทที่มีอยู่หลังจาก EXIT / B)
      • ดูhttps://www.dostips.com/forum/viewtopic.php?f=3&t=3803สำหรับคำอธิบายที่แม่นยำยิ่งขึ้นของกฎที่ใช้สำหรับการแยกวิเคราะห์ป้ายกำกับ
    • RENAME และ COPY ยอมรับทั้ง wildcard สำหรับเส้นทางต้นทางและปลายทาง แต่ไมโครซอฟท์ทำงานที่ยอดเยี่ยมในการบันทึกสัญลักษณ์การทำงานของสัญลักษณ์แทนโดยเฉพาะอย่างยิ่งสำหรับเส้นทางเป้าหมาย สามารถพบชุดกฎกฎที่เป็นประโยชน์ได้ที่คำสั่ง Windows RENAME ตีความสัญลักษณ์แทนได้อย่างไร
  • 7.2 - เรียกใช้การเปลี่ยนแปลงโวลุ่ม - อื่น ๆ ถ้าโทเค็นคำสั่งไม่ได้เริ่มต้นด้วยอัญประกาศมีความยาวสองตัวอักษรอย่างแน่นอนและตัวที่สองคือโคลอนจากนั้นเปลี่ยนโวลุ่ม
    • โทเค็นการโต้แย้งทั้งหมดจะถูกละเว้น
    • หากไม่พบปริมาณที่ระบุโดยอักขระตัวแรกให้ยกเลิกด้วยข้อผิดพลาด
    • โทเค็นคำสั่งของ::จะส่งผลให้เกิดข้อผิดพลาดยกเว้นว่าใช้ SUBST เพื่อกำหนดระดับเสียงสำหรับ::
      หากใช้ SUBST เพื่อกำหนดระดับเสียงสำหรับ::ปริมาณจะถูกเปลี่ยนมันจะไม่ถูกใช้เป็นป้ายกำกับ
  • 7.3 - ดำเนินการคำสั่งภายนอก - ลองพยายามใช้คำสั่งเป็นคำสั่งภายนอก
    • หากอยู่ในโหมดบรรทัดคำสั่งและคำสั่งที่ไม่ได้ยกมาและไม่ได้เริ่มต้นด้วยข้อกำหนดปริมาณพื้นที่สีขาว, ,, ;, =หรือ+แล้วทำลายคำสั่ง token ที่เกิดขึ้นครั้งแรกของ<space> , ;หรือ=และย่อหน้าที่เหลือที่จะโต้แย้งโทเค็น (s)
    • หากอักขระตัวที่สองของโทเค็นคำสั่งเป็นเครื่องหมายโคลอนให้ตรวจสอบปริมาณที่ระบุโดยอักขระตัวที่หนึ่งที่สามารถพบได้
      หากไม่พบปริมาณให้ยกเลิกด้วยข้อผิดพลาด
    • หากอยู่ในโหมดชุดและคำสั่งโทเค็นเริ่มต้นด้วย:แล้วข้ามไป 7.4
      หมายเหตุว่าถ้าโทเค็นฉลากเริ่มต้นด้วย::แล้วนี้จะไม่สามารถเข้าถึงได้เพราะขั้นตอนก่อนหน้าจะมีการยกเลิกด้วยข้อผิดพลาดเว้นแต่ SUBST ::ถูกนำมาใช้ในการกำหนดปริมาณสำหรับ
    • ระบุคำสั่งภายนอกเพื่อดำเนินการ
      • นี่เป็นกระบวนการที่ซับซ้อนที่อาจเกี่ยวข้องกับไดรฟ์ข้อมูลปัจจุบันไดเรกทอรีปัจจุบันตัวแปร PATH ตัวแปร PATHEXT และหรือการเชื่อมโยงไฟล์
      • หากไม่สามารถระบุคำสั่งภายนอกที่ถูกต้องให้ยกเลิกด้วยข้อผิดพลาด
    • หากอยู่ในโหมดบรรทัดคำสั่งและคำสั่งโทเค็นเริ่มต้นด้วย:แล้วข้ามไป 7.4
      ทราบว่านี้เป็นถึงไม่ค่อยเพราะขั้นตอนก่อนหน้าจะมีการยกเลิกด้วยข้อผิดพลาดเว้นแต่โทเค็นคำสั่งเริ่มต้นด้วย::และ SUBST ถูกนำมาใช้ในการกำหนดปริมาณการหา::และ โทเค็นคำสั่งทั้งหมดเป็นพา ธ ที่ถูกต้องไปยังคำสั่งภายนอก
    • 7.3.exec - ดำเนินการคำสั่งภายนอก
  • 7.4 - ละเว้นฉลาก - :ไม่สนใจคำสั่งและข้อโต้แย้งทั้งหมดถ้าโทเค็นคำสั่งเริ่มต้นด้วย
    กฎในข้อ 7.2 และ 7.3 อาจป้องกันไม่ให้ฉลากถึงจุดนี้

ตัวแยกวิเคราะห์บรรทัดคำสั่ง:

ทำงานเหมือน BatchLine-Parser ยกเว้น:

ขั้นตอนที่ 1) การขยายเปอร์เซ็นต์:

  • ไม่มี%*, %1ฯลฯ การขยายตัวของการโต้แย้ง
  • หาก var ไม่ได้ถูกกำหนดไว้%var%จะไม่มีการเปลี่ยนแปลง
  • %%ไม่มีการจัดการพิเศษของ หาก var = เนื้อหาแล้วขยายไปยัง%%var%%%content%

เฟส 3) สะท้อนคำสั่งที่แยกวิเคราะห์

  • สิ่งนี้ไม่ถูกดำเนินการหลังจากเฟส 2 ซึ่งจะดำเนินการหลังจากเฟส 4 สำหรับบล็อกคำสั่ง FOR DO เท่านั้น

ขั้นตอนที่ 5) การขยายล่าช้า:เฉพาะเมื่อเปิดใช้งานการหน่วงเวลาล่าช้าเท่านั้น

  • หาก var ไม่ได้ถูกกำหนดไว้!var!จะไม่มีการเปลี่ยนแปลง

ขั้นตอนที่ 7) ดำเนินการคำสั่ง

  • ความพยายามในการโทรหรือ GOTO a: ฉลากทำให้เกิดข้อผิดพลาด
  • ดังที่มีการบันทึกไว้แล้วในเฟส 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ที่สามารถรายงานปัญหาและข้อเสนอแนะได้

หวังว่าจะช่วย
Jan Erik (jeb) - ผู้แต่งดั้งเดิมและผู้ค้นพบ phases
Dave Benham (dbenham) - เนื้อหาและการแก้ไขเพิ่มเติมมากมาย


4
สวัสดี jeb ขอบคุณสำหรับความเข้าใจของคุณ ... มันอาจจะยากที่จะเข้าใจ แต่ฉันจะพยายามคิดให้ดี! คุณดูเหมือนจะทำการทดสอบมากมาย! ขอบคุณสำหรับการแปล ( administrator.de/… )
เบอนัวต์

2
แบทช์เฟส 5) - %% a จะถูกเปลี่ยนเป็น% a ในเฟส 1 แล้วดังนั้นการขยายสำหรับลูปจะขยายเป็น% a นอกจากนี้ฉันได้เพิ่มคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับแบทช์เฟส 1 ในคำตอบด้านล่าง (ฉันไม่มีสิทธิ์แก้ไข)
dbenham

3
Jeb - อาจจะสามารถย้ายเฟส 0 และรวมกับเฟส 6 ได้ มันสมเหตุสมผลมากกว่าสำหรับฉันหรือมีเหตุผลว่าทำไมพวกเขาถึงถูกแยกจากกันอย่างนั้น?
dbenham

1
@aschipfl - ฉันอัพเดทส่วนนั้นแล้ว )จริงๆไม่ฟังก์ชั่นเกือบจะเหมือนREMคำสั่งเมื่อเคาน์เตอร์วงเล็บคือ 0 ลองทั้งสองแบบนี้จากบรรทัดคำสั่ง: ) Ignore thisและecho OK & ) Ignore this
dbenham

1
@aschipfl ใช่ถูกต้องแล้วคุณจะเห็นบางครั้ง 'ตั้งค่า "var =% expr%"! เครื่องหมายอัศเจรีย์สุดท้ายจะถูกลบออก แต่บังคับให้เฟส 5
jeb

62

เมื่อเรียกใช้คำสั่งจากหน้าต่างคำสั่งการทำโทเค็นของอาร์กิวเมนต์บรรทัดคำสั่งจะไม่กระทำโดยcmd.exe(aka "the shell") บ่อยครั้งที่โทเค็นจะทำโดยรันไทม์ C / C ++ ของโพรเซสที่เพิ่งสร้างขึ้นใหม่ แต่ไม่จำเป็นต้องทำเช่นนั้นตัวอย่างเช่นถ้าโพรเซสใหม่ไม่ได้เขียนใน C / C ++ หรือถ้ากระบวนการใหม่เลือกที่จะไม่สนใจargvและประมวลผล commandline ดิบสำหรับตัวเอง (เช่นกับGetCommandLine ()) ในระดับระบบปฏิบัติการ Windows จะส่งบรรทัดคำสั่งที่ไม่ได้ทำการแปลงเป็นสตริงเดี่ยวไปยังกระบวนการใหม่ สิ่งนี้ตรงกันข้ามกับเชลล์ * nix ส่วนใหญ่ที่เชลล์ tokenizes ข้อโต้แย้งในวิธีที่สอดคล้องและคาดการณ์ได้ก่อนที่จะส่งพวกเขาไปยังกระบวนการที่เกิดขึ้นใหม่ ทั้งหมดนี้หมายความว่าคุณอาจพบว่ามีการใช้โทเค็นการโต้แย้งอาร์ไคฟ์ที่แตกต่างกันอย่างรุนแรงในโปรแกรมต่างๆบน Windows เนื่องจากแต่ละโปรแกรมมักจะใช้โทเค็นการโต้แย้งในมือของตนเอง

ถ้ามันฟังดูเหมือนเป็นอนาธิปไตย แต่เนื่องจากจำนวนมากของ Windows โปรแกรมทำใช้ Microsoft C / C ++ รันไทม์ของargvมันอาจจะมีประโยชน์โดยทั่วไปจะเข้าใจวิธีการ MSVCRT tokenizesข้อโต้แย้ง นี่คือข้อความที่ตัดตอนมา:

  • อาร์กิวเมนต์ถูกคั่นด้วยช่องว่างสีขาวซึ่งเป็นช่องว่างหรือแท็บ
  • สตริงที่ล้อมรอบด้วยเครื่องหมายอัญประกาศคู่ถูกตีความว่าเป็นอาร์กิวเมนต์เดียวโดยไม่คำนึงถึงพื้นที่สีขาวที่อยู่ภายใน สตริงที่ยกมาสามารถฝังในการโต้แย้ง โปรดทราบว่าเครื่องหมายรูปหมวก (^) ไม่ได้รับการยอมรับว่าเป็นตัวหนีหรือตัวคั่น
  • เครื่องหมายอัญประกาศคู่นำหน้าด้วยแบ็กสแลช \ "ถูกตีความเป็นเครื่องหมายอัญประกาศคู่ตามตัวอักษร (")
  • แบ็กสแลชจะถูกตีความอย่างแท้จริงเว้นแต่ว่าพวกเขาจะนำหน้าเครื่องหมายคำพูดคู่ทันที
  • หากมีเครื่องหมายแบ็กสแลชจำนวนคู่ตามด้วยเครื่องหมายคำพูดคู่หนึ่งเครื่องหมายแบ็กสแลช () จะอยู่ในอาร์เรย์ argv สำหรับทุกคู่ของแบ็กสแลช (\) และเครื่องหมายคำพูดคู่ (") ถูกตีความว่าเป็นตัวคั่นสตริง
  • หากมีเครื่องหมายแบคสแลชจำนวนคี่ตามด้วยเครื่องหมายคำพูดคู่หนึ่งเครื่องหมายแบ็กสแลช () จะอยู่ในอาร์เรย์ argv สำหรับทุกคู่ของแบ็กสแลช (\) และเครื่องหมายอัญประกาศคู่ถูกตีความว่าเป็นลำดับการหลบหนีโดยแบ็กสแลชที่เหลือ เครื่องหมายอัญประกาศคู่ตัวอักษร (") ที่จะอยู่ใน argv

Microsoft "ภาษาชุด" ( .bat) ไม่มีข้อยกเว้นสำหรับสภาพแวดล้อมแบบอนาธิปไตยนี้และได้พัฒนากฎเฉพาะของตนเองสำหรับการทำโทเค็นและการหลบหนี นอกจากนี้ยังดูเหมือนว่าพรอมต์คำสั่งของ cmd.exe จะทำการประมวลผลอาร์กิวเมนต์บรรทัดคำสั่งล่วงหน้า (ส่วนใหญ่เป็นการทดแทนตัวแปรและการหลบหนี) ก่อนที่จะส่งอาร์กิวเมนต์ไปยังกระบวนการที่เพิ่งดำเนินการ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับรายละเอียดในระดับต่ำของภาษาแบทช์และคำสั่ง cmd หนีออกมาในคำตอบที่ยอดเยี่ยมโดย jeb และ dbenham ในหน้านี้


มาสร้างอรรถประโยชน์บรรทัดคำสั่งง่ายๆใน 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]ทำหลังการประมวลผล
เบอนัวต์

ตอนนี้ฉันคิดเกี่ยวกับมันโทเค็นบรรทัดคำสั่งนี้อาจทำโดยรันไทม์ C ทั้งหมด สามารถเขียนโปรแกรมที่รันได้โดยที่มันไม่ได้ใช้ C runtime ซึ่งในกรณีนี้ฉันคิดว่ามันจะต้องจัดการกับ command line verbatim และรับผิดชอบในการทำ tokenization ของตัวเอง (ถ้ามันต้องการ) ถ้าแอพลิเคชันของคุณไม่ใช้รันไทม์ C คุณสามารถเลือกที่จะไม่สนใจ argc และ argv และเพิ่งได้รับบรรทัดคำสั่งดิบผ่านเช่น GetCommandLineWin32 บางที TinyPerl อาจเพิกเฉย argv และเพียงแค่ tokenizing บรรทัดคำสั่ง raw ตามกฎของมันเอง
Mike Clark

4
"โปรดจำไว้ว่าจากมุมมองของ Win32 บรรทัดคำสั่งเป็นเพียงสตริงที่คัดลอกลงในพื้นที่ที่อยู่ของกระบวนการใหม่กระบวนการเริ่มต้นและกระบวนการใหม่ตีความสตริงนี้ไม่ได้อยู่ภายใต้กฎ แต่โดยการประชุม" -Raymond Chen blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Mike Clark

2
ขอบคุณสำหรับคำตอบที่ดีอย่างแท้จริง ที่อธิบายมากในความคิดของฉัน และยังอธิบายว่าทำไมบางครั้งผมพบว่าแท้จริงเส็งเคร็งที่จะทำงานร่วมกับ Windows ...
เบอนัวต์

ฉันพบสิ่งนี้เกี่ยวกับแบ็กสแลชและเครื่องหมายคำพูดในระหว่างการแปลงจาก commandline เป็น argv's สำหรับโปรแกรม Win32 C ++ จำนวนแบ็กสแลชจะถูกหารด้วยสองเท่านั้นเมื่อแบ็กสแลชสุดท้ายตามด้วย dblquote และ dblquote จะยุติสตริงเมื่อมีแบ็กสแลชเป็นจำนวนเท่ากันมาก่อน
เบอนัวต์

47

กฎการขยายเปอร์เซ็นต์

นี่คือคำอธิบายเพิ่มเติมของเฟส 1 ในคำตอบของ jeb (ใช้ได้ทั้งโหมดแบทช์และโหมดบรรทัดคำสั่ง)

ขั้นตอนที่ 1) การขยายตัวร้อยละ เริ่มต้นจากซ้ายสแกนตัวละครแต่ละตัวสำหรับหรือ% <LF>หากพบแล้ว

  • 1.05 (ตัดบรรทัดที่<LF>)
    • หากเป็นตัวละคร<LF>แล้ว
      • ปล่อย (ละเว้น) ส่วนที่เหลือของบรรทัดจาก<LF>เป็นต้นไป
      • ไปที่เฟส 1.5 (แถบ<CR>)
    • อักขระอื่นต้องเป็น%ดังนั้นให้ดำเนินการต่อที่ 1.1
  • 1.1 (escape %) ข้ามหากโหมดบรรทัดคำสั่ง
    • หากโหมดแบทช์แล้วตามด้วยอีกอัน %แล้ว
      แทนที่%%ด้วยเดียว%และยังคงการสแกน
  • 1.2 (ขยายอาร์กิวเมนต์) ข้ามหากโหมดบรรทัดคำสั่ง
    • ถ้าโหมดแบตช์แล้ว
      • หากตามมาด้วย *เปิดใช้งานและส่วนขยายคำสั่งให้
        แทนที่%*ด้วยข้อความของอาร์กิวเมนต์บรรทัดคำสั่งทั้งหมด (แทนที่ด้วยไม่มีอะไรหากไม่มีข้อโต้แย้ง) และสแกนต่อไป
      • ถ้าอย่างนั้นตามมาด้วย <digit>แล้ว
        แทนที่%<digit>ด้วยค่าอาร์กิวเมนต์ (แทนที่ด้วยอะไรถ้าไม่ได้กำหนด) และยังคงการสแกน
      • มิฉะนั้นถ้า~มีการเปิดใช้งานส่วนขยายคำสั่งแล้วตามด้วย
        • หากตามด้วยรายการตัวดัดแปลงอาร์กิวเมนต์ที่ถูกต้องที่เป็นทางเลือก<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 เกิดขึ้นก่อนการจดจำคำสั่ง 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!"

กฎการขยายที่ล่าช้า

นี่คือคำอธิบายที่ขยายเพิ่มขึ้นและแม่นยำยิ่งขึ้นของเฟส 5 ในคำตอบของ jeb (ใช้ได้ทั้งโหมดแบทช์และโหมดบรรทัดคำสั่ง)

ขั้นตอนที่ 5) การขยายตัวล่าช้า

ขั้นตอนนี้จะถูกข้ามไปหากมีเงื่อนไขใด ๆ ต่อไปนี้:

  • การขยายที่ล่าช้าถูกปิดใช้งาน
  • คำสั่งอยู่ในบล็อกที่อยู่ในวงเล็บที่ด้านใดด้านหนึ่งของไปป์
  • โทเค็นคำสั่งเข้ามาเป็น "เปล่า" สคริปต์ชุดซึ่งหมายความว่ามันจะไม่เกี่ยวข้องกับ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"""]

คำถาม: คุณส่งค่าของ var สิ่งแวดล้อมใด ๆ เป็นอาร์กิวเมนต์เดียว (เช่น as %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!

การแยกวิเคราะห์มีเหตุผลดูเหมือนจะขาดหายไปตลอดกาล

ความบันเทิงสำหรับคุณลองเพิ่มเบ็ดเตล็ด^, \, ', &(& c.) ตัวอักษรตัวอย่างเหล่านี้


ในการผ่าน% t% เป็นอาร์กิวเมนต์เดี่ยวคุณสามารถใช้ "% t:" = \ "%" นั่นคือใช้ไวยากรณ์% VAR: str = replace% สำหรับการขยายตัวแปร เชลล์ metacharacters ชอบ | และ & ในเนื้อหาตัวแปรยังคงสามารถเปิดเผยและทำให้เปลือกเสียหายได้เว้นแต่ว่าคุณจะหลบหนีพวกเขาอีกครั้ง ....
Toughy

@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


เกี่ยวกับคำพูดฉันมีความรู้สึกว่าพฤติกรรมดังต่อไปนี้:

  • เมื่อ"พบa สตริงจะเริ่มขึ้น
  • เมื่อสตริง globbing เกิดขึ้น:
    • ตัวละครทุกตัวที่ไม่ได้"เป็นก้อนกลม
    • เมื่อ"พบ:
      • ถ้ามันตามมาด้วย""(เช่นสาม") แล้วเพิ่มคำพูดสองครั้งจะถูกเพิ่มไปยังสตริง
      • ถ้าตามด้วย"(จึงเป็น double ") ดังนั้นการเพิ่ม double quote ใน string และ string ทำให้เกิดการวนซ้ำ
      • หากอักขระตัวถัดไปไม่ใช่"สตริงจะสิ้นสุดลง
    • เมื่อสิ้นสุดบรรทัดสตริงจะสิ้นสุดลง

ในระยะสั้น:

"a """ b "" c"""ประกอบด้วยสองสาย: a " b "และc"

"a"", "a"""และ"a""""มีทุกสายเดียวกันถ้าในตอนท้ายของบรรทัด


tokenizer และสตริง globbing ขึ้นอยู่กับคำสั่ง! A "ชุด" ผลงานที่แตกต่างกันแล้ว "เรียกว่า" หรือแม้กระทั่ง "ถ้า"
Jeb

ใช่ แต่สิ่งที่เกี่ยวกับคำสั่งภายนอก? ฉันเดา cmd.exe จะส่งผ่านข้อโต้แย้งเดียวกันกับพวกเขาเสมอ
เบอนัวต์

1
cmd.exe ส่งผ่านผลลัพธ์การขยายเป็นสตริงที่ไม่ใช่โทเค็นไปยังคำสั่งภายนอกเสมอ มันขึ้นอยู่กับคำสั่งภายนอกว่าจะหลบหนีและ tokenize มันใช้ findstr ทับขวาหนึ่งต่อไปสามารถใช้อย่างอื่น
Jeb

0

โปรดทราบว่า Microsoft ได้เผยแพร่ซอร์สโค้ดของเทอร์มินัลแล้ว มันอาจทำงานคล้ายกับบรรทัดคำสั่งเกี่ยวกับการแยกวิเคราะห์ไวยากรณ์ บางทีมีคนสนใจทดสอบกฎการแยกวิเคราะห์ย้อนกลับตามกฎการแยกวิเคราะห์ของเทอร์มินัล

ลิงก์ไปยังซอร์สโค้ด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.