การหลีกเลี่ยงเครื่องหมายคำพูดคู่ใน Batch Script


92

ฉันจะเปลี่ยนเครื่องหมายคำพูดคู่ทั้งหมดในพารามิเตอร์ของไฟล์แบตช์ด้วยเครื่องหมายคำพูดคู่ที่ใช้ Escape ได้อย่างไร นี่คือไฟล์แบตช์ปัจจุบันของฉันซึ่งขยายพารามิเตอร์บรรทัดคำสั่งทั้งหมดภายในสตริง:

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

จากนั้นใช้สตริงนั้นเพื่อโทรไปยัง bash ของ Cygwin โดยเรียกใช้งานคอมไพเลอร์ข้าม Linux น่าเสียดายที่ฉันได้รับพารามิเตอร์เช่นนี้ส่งไปยังไฟล์แบตช์ของฉัน:

"launch-linux-g++.bat" -ftemplate-depth-128 -O3 -finline-functions 
-Wno-inline -Wall  -DNDEBUG   -c 
-o "C:\Users\Me\Documents\Testing\SparseLib\bin\Win32\LinuxRelease\hello.o" 
"c:\Users\Me\Documents\Testing\SparseLib\SparseLib\hello.cpp"

โดยที่เครื่องหมายคำพูดแรกรอบเส้นทางแรกที่ส่งเข้ามาคือการสิ้นสุดสตริงที่ถูกส่งไปยัง GCC ก่อนเวลาอันควรและส่งผ่านพารามิเตอร์ที่เหลือไปยัง bash โดยตรง (ซึ่งล้มเหลวอย่างไม่น่าเชื่อ)

ฉันคิดว่าถ้าฉันสามารถต่อพารามิเตอร์เป็นสตริงเดียวได้หรือไม่จากนั้นให้หลีกเลี่ยงเครื่องหมายคำพูดมันก็น่าจะใช้ได้ดี แต่ฉันมีปัญหาในการพิจารณาว่าจะทำอย่างไร มีใครรู้บ้าง?

คำตอบ:


104

^ตัวหนีในสคริปต์ชุดคือ แต่สำหรับสตริงที่ยกมาสองครั้งให้เพิ่มเครื่องหมายคำพูด:

"string with an embedded "" character"

5
การเพิ่มคำพูดเป็นสองเท่าไม่ได้ผลสำหรับฉัน แต่ ^ ทำงานเหมือนแชมป์
davenpcj

26
^เป็นตัวละครที่หลบหนีออกมาเฉพาะในunquotedสตริง; ในสตริงที่ยกมาสองครั้งจะถือว่าเป็นตัวอักษร ซึ่งแตกต่างจากเชลล์ Unix (เหมือน POSIX) cmd.exeไม่มีการรักษาเชลล์มาตรฐานของเครื่องหมายคำพูดคู่ภายในสตริงที่ยกมาสองครั้งและการตีความจะเหลืออยู่ในโปรแกรมที่เรียกใช้ (ต่อในความคิดเห็นถัดไป)
mklement0

9
(ต่อจากความคิดเห็นก่อนหน้า) ในทางปฏิบัติตัวแปลภาษา / สคริปต์ส่วนใหญ่จะใช้หลักการ C ของ"ตัวอักษรที่คาดหวัง ที่จะหลีกเลี่ยง\"ภายในสตริงที่ยกมาสองครั้ง (ใช้อย่างน้อยกับ: C / C ++, Python, Perl, Ruby) โดยคมชัด""ได้รับการยอมรับเฉพาะในชนกลุ่มน้อยของกรณี: ในพารามิเตอร์ส่งผ่านไปยังไฟล์ชุด"" จะได้รับการยอมรับในฐานะที่เป็นที่ฝังตัวราคาคู่ แต่ถูกเก็บไว้เป็นในที่สอดคล้องกัน%<n>พารามิเตอร์แม้หลังจากการลบล้อมรอบ%~<n>ราคาคู่กับ Python ยังตระหนัก""ดีว่าเป็นอีกทางเลือกหนึ่งของ\".
mklement0

90

คำตอบของ eplawlessสามารถแก้ปัญหาเฉพาะของเขาได้อย่างง่ายดายและมีประสิทธิภาพ: มันจะแทนที่"อินสแตนซ์ทั้งหมดในรายการอาร์กิวเมนต์ทั้งหมดด้วย\"ซึ่งเป็นวิธีที่ Bash ต้องการเครื่องหมายคำพูดคู่ภายในสตริงที่ยกมาสองครั้งเพื่อแสดง

โดยทั่วไปจะตอบคำถามเกี่ยวกับวิธีหลีกเลี่ยงเครื่องหมายอัญประกาศคู่ภายในสตริงที่ยกมาสองครั้งโดยใช้cmd.exeตัวแปลบรรทัดคำสั่งของ Windows (ไม่ว่าจะเป็นในบรรทัดคำสั่ง - มักจะเรียกผิดว่า "พรอมต์ DOS" หรือในไฟล์แบตช์): ดูด้านล่างสำหรับการดูที่PowerShell

tl; dr :

  • คุณต้องใช้""เมื่อส่งสตริงไปยังไฟล์แบตช์ (nother)และคุณสามารถใช้""กับแอปพลิเคชันที่สร้างด้วยคอมไพเลอร์ C / C ++ / NET ของ Microsoft (ซึ่งยอมรับเช่นกัน\" ) ซึ่งใน Windows รวมถึง Python และ Node.js :

    • ตัวอย่าง: foo.bat "We had 3"" of rain."

    • ข้อมูลต่อไปนี้ใช้กับไฟล์แบตช์เท่านั้น:

      • ""เป็นวิธีเดียวในการรับ command interpreter ( cmd.exe) เพื่อให้ถือว่าสตริงที่ยกมาคู่ทั้งหมดเป็นอาร์กิวเมนต์เดียว

      • อย่างไรก็ตามน่าเศร้าที่ไม่เพียง แต่เครื่องหมายอัญประกาศแบบปิดล้อมจะยังคงอยู่ (ตามปกติ) เท่านั้น แต่ยังมีค่า Escape เป็นสองเท่าดังนั้นการได้รับสตริงที่ต้องการจึงเป็นกระบวนการสองขั้นตอน เช่นสมมติว่าสตริงที่ยกมาสองครั้งถูกส่งผ่านเป็นอาร์กิวเมนต์ที่ 1 %1:

      • set "str=%~1"ลบเครื่องหมายคำพูดคู่ที่แนบมา set "str=%str:""="%"จากนั้นแปลงเครื่องหมายคำพูดคู่ที่เพิ่มเป็นสองเท่าเป็นค่าเดียว
        อย่าลืมใช้เครื่องหมายคำพูดคู่ล้อมรอบส่วนที่กำหนดเพื่อป้องกันการตีความค่าที่ไม่ต้องการ

  • \"ถูกต้อง - เป็นตัวเลือกเพียง - โดยโปรแกรมอื่น ๆ อีกมากมาย (! เช่นทับทิม, Perl, และแม้กระทั่งไมโครซอฟท์เอง Windows PowerShell ()) แต่การใช้งานเป็นไม่ปลอดภัย :

    • \"เป็นสิ่งที่โปรแกรมเรียกทำงานและตัวแปลจำนวนมากต้องการ - รวมถึง Windows PowerShell - เมื่อส่งผ่านสตริงจากภายนอก - หรือในกรณีของคอมไพเลอร์ของ Microsoft จะสนับสนุนเป็นทางเลือกอื่นใน ""ท้ายที่สุดแม้ว่าโปรแกรมเป้าหมายจะแยกวิเคราะห์รายการอาร์กิวเมนต์ .
      • ตัวอย่าง: foo.exe "We had 3\" of rain."
    • อย่างไรก็ตามการใช้\"สามารถส่งผลในการดำเนินการตามคำสั่งของอนุญาโตตุลาการโดยไม่ต้องการและ / หรือการเปลี่ยนทิศทางอินพุต / เอาต์พุต :
      • อักขระต่อไปนี้แสดงความเสี่ยงนี้: & | < >
      • ตัวอย่างเช่นผลลัพธ์ต่อไปนี้ในการดำเนินการverคำสั่งโดยไม่ได้ตั้งใจ ดูเพิ่มเติมด้านล่างสำหรับคำอธิบายและสัญลักษณ์แสดงหัวข้อย่อยถัดไปสำหรับวิธีแก้ปัญหา:
        • foo.exe "3\" of snow" "& ver."
    • สำหรับWindows PowerShell , \""และ"^""มีประสิทธิภาพ แต่ทางเลือก จำกัด (ดูส่วน "เรียก CLI PowerShell ของ ..." ด้านล่าง)
  • หากคุณต้องใช้\"มีเพียง 3 วิธีที่ปลอดภัยซึ่งเป็นวิธีที่ค่อนข้างยุ่งยาก : เคล็ดลับของหมวกสำหรับTSเพื่อขอความช่วยเหลือ

    • การใช้ (อาจเลือก ) ล่าช้าการขยายตัวของตัวแปรในแฟ้มชุดของคุณคุณสามารถเก็บอักษร\"ในตัวแปรและการอ้างอิงตัวแปรภายใน"..."สตริงโดยใช้!var!ไวยากรณ์ - ดูคำตอบที่เป็นประโยชน์ TS ของ

      • วิธีการข้างต้นแม้จะยุ่งยาก แต่ก็มีข้อได้เปรียบที่คุณสามารถนำไปใช้ได้อย่างมีระบบและใช้งานได้ดีกับอินพุตใด ๆ
    • เฉพาะกับสตริงแบบ LITERAL เท่านั้นซึ่งไม่เกี่ยวข้องกับตัวแปร - คุณจะได้รับแนวทางที่เป็นระเบียบในทำนองเดียวกันหรือไม่: จัดหมวดหมู่^-escape ตัวละครทั้งหมด cmd.exe : " & | < >และ - หากคุณต้องการระงับการขยายตัวแปรด้วย - %:
      foo.exe ^"3\^" of snow^" ^"^& ver.^"

    • มิฉะนั้นคุณต้องกำหนดสตริงของคุณโดยพิจารณาจากการรับรู้ว่าส่วนใดของสตริงที่cmd.exeพิจารณาว่าไม่ได้ใส่\"เครื่องหมายคำพูดเนื่องจากตีความผิดว่าเป็นตัวคั่นปิด:

      • ในส่วนที่เป็นตัวอักษรที่มีตัวอักษรเชลล์: ^-escape พวกเขา; จากตัวอย่างข้างต้นจะ&ต้องเป็น^-escaped:
        foo.exe "3\" of snow" "^& ver."

      • ในส่วนที่มี%...%การอ้างอิงตัวแปรสไตล์ : ให้แน่ใจว่าcmd.exeจะพิจารณาเป็นส่วนหนึ่งของพวกเขา"..."สตริงและว่าค่าตัวแปรไม่ได้ว่าตัวเองมีการฝังตัว, คำพูดที่ไม่สมดุล - ซึ่งไม่ได้เป็นไปได้เสมอ

สำหรับข้อมูลพื้นฐานโปรดอ่านต่อ


พื้นหลัง

หมายเหตุ: นี่มาจากการทดลองของฉันเอง โปรดแจ้งให้ฉันทราบหากฉันผิด

เชลล์ที่เหมือน POSIX เช่น Bash บนระบบที่เหมือน Unix จะทำให้รายการอาร์กิวเมนต์ (สตริง) เป็นโทเค็นก่อนที่จะส่งอาร์กิวเมนต์ทีละรายการไปยังโปรแกรมเป้าหมาย: ในการขยายอื่น ๆ พวกเขาแยกรายการอาร์กิวเมนต์ออกเป็นแต่ละคำ (การแบ่งคำ) และลบอักขระการอ้างอิงออกจาก คำที่เป็นผลลัพธ์ (การลบใบเสนอราคา) โปรแกรมเป้าหมายที่ถูกส่งอาร์เรย์ของข้อโต้แย้งของแต่ละบุคคลด้วยประโยคคำพูดที่เอาออก

ในทางตรงกันข้ามตัวแปลคำสั่งของ Windows ดูเหมือนจะไม่โทเค็นรายการอาร์กิวเมนต์และเพียงแค่ส่งผ่านสตริงเดียวที่ประกอบด้วยอาร์กิวเมนต์ทั้งหมด - รวมถึงการอ้างอิงอักขระ - ไปยังโปรแกรมเป้าหมาย
อย่างไรก็ตามการประมวลผลล่วงหน้าบางอย่างเกิดขึ้นก่อนที่สตริงเดี่ยวจะถูกส่งผ่านไปยังโปรแกรมเป้าหมาย: ^Escape chars ภายนอกของสตริงที่ยกมาสองครั้งจะถูกลบออก (ซึ่งจะหลีกเลี่ยงอักขระต่อไปนี้) และการอ้างอิงตัวแปร (เช่น%USERNAME%) จะถูกแก้ไขก่อน

ดังนั้นไม่เหมือนใน Unix คือความรับผิดชอบของโปรแกรมเป้าหมายในการแยกวิเคราะห์เพื่อแยกวิเคราะห์สตริงอาร์กิวเมนต์และแยกย่อยออกเป็นอาร์กิวเมนต์แต่ละรายการโดยลบเครื่องหมายคำพูดออก ดังนั้นโปรแกรมที่แตกต่างกันอาจต้องการวิธีการหลบหนีที่แตกต่างกันโดยสมมุติฐานและไม่มีกลไกการหลบหนีเดียวที่รับประกันว่าจะทำงานกับทุกโปรแกรมได้ - https://stackoverflow.com/a/4094897/45375มีพื้นหลังที่ยอดเยี่ยมเกี่ยวกับอนาธิปไตยซึ่งเป็นบรรทัดคำสั่งของ Windows การแยกวิเคราะห์

ในทางปฏิบัติ\"เป็นเรื่องปกติมาก แต่ไม่ปลอดภัยดังที่กล่าวไว้ข้างต้น:

เนื่องจากcmd.exeตัวเองไม่ได้รับรู้\"เป็นหนีอ้างดับเบิลก็สามารถเข้าใจผิดในภายหลังสัญญาณในบรรทัดคำสั่งเป็นunquotedและอาจตีความว่าเป็นคำสั่งและ / หรืออินพุต / เอาต์พุตเปลี่ยนเส้นทาง
โดยสรุป: ปัญหาจะปรากฏขึ้นหากอักขระใด ๆ ต่อไปนี้เป็นไปตามการเปิดหรือไม่สมดุล \" :& | < > ; ตัวอย่างเช่น:

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

cmd.exeเห็นโทเค็นต่อไปนี้ซึ่งเป็นผลมาจากการตีความผิด\"เป็นเครื่องหมายคำพูดคู่ปกติ:

  • "3\"
  • of
  • snow" "
  • พักผ่อน: & ver.

เนื่องจากcmd.exeคิดว่า& ver.ไม่มีเครื่องหมายอัญประกาศจึงตีความเป็น&(ตัวดำเนินการลำดับคำสั่ง) ตามด้วยชื่อของคำสั่งเพื่อดำเนินการ ( ver.- .ถูกละเว้นข้อมูลเวอร์ชันของverรายงานcmd.exe)
ผลกระทบโดยรวมคือ:

  • ขั้นแรกfoo.exeถูกเรียกใช้ด้วยโทเค็น3 ตัวแรกเท่านั้น
  • จากนั้นคำสั่งverจะถูกดำเนินการ

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

คอมไพเลอร์ / ล่ามจำนวนมากรู้จักเฉพาะ\" - เช่นคอมไพเลอร์ GNU C / C ++, Python, Perl, Ruby แม้กระทั่ง Windows PowerShell ของ Microsoft เองเมื่อเรียกใช้จากcmd.exe- และยกเว้น (มีข้อ จำกัด ) สำหรับ Windows PowerShell ด้วย\""สำหรับพวกเขาไม่มีวิธีง่ายๆ กับปัญหานี้
โดยพื้นฐานแล้วคุณจะต้องรู้ล่วงหน้าว่าส่วนใดของบรรทัดคำสั่งของคุณถูกตีความผิดว่าไม่มีการ^อ้างถึงและเลือก - คัดลอกอินสแตนซ์ทั้งหมด& | < >ในส่วนเหล่านั้น

ในทางตรงกันข้ามการใช้งาน""คือ SAFEแต่น่าเสียดายที่ได้รับการสนับสนุนโดยไฟล์ปฏิบัติการและไฟล์แบตช์ที่ใช้คอมไพเลอร์ของ Microsoft เท่านั้น (ในกรณีของไฟล์แบตช์ที่มีลักษณะไม่ชอบมาพากลที่กล่าวถึงข้างต้น) ซึ่งโดดเด่นไม่รวมPowerShell - ดูหัวข้อถัดไป


การเรียก CLI ของ PowerShell จากcmd.exeหรือเชลล์แบบ POSIX:

หมายเหตุ: ดูส่วนด้านล่างสำหรับวิธีจัดการใบเสนอราคาภายใน PowerShell

เมื่อเรียกใช้จากภายนอก - เช่นจากcmd.exeไม่ว่าจะจากบรรทัดคำสั่งหรือแบตช์ไฟล์:

  • PowerShell [หลัก] v6 +ตอนนี้ต้องตระหนัก"" (นอกเหนือ\") ซึ่งเป็นทั้งความปลอดภัยในการใช้งานและช่องว่างการรักษา

    • pwsh -c " ""a & c"".length " ไม่แตกและให้ผลผลิตอย่างถูกต้อง 6
  • Windows PowerShell (รุ่นดั้งเดิมที่มีเวอร์ชันล่าสุดคือ 5.1) จะ รับรู้เท่านั้น \"และใน Windows ด้วย"""และยิ่งมีประสิทธิภาพมากขึ้น\""/"^"" (แม้ว่าPowerShellภายในจะใช้`เป็นอักขระหลีกในสตริงที่ยกมาสองครั้งและยังยอมรับ""- ดูส่วนด้านล่าง):

การเรียกWindows PowerShell จากcmd.exe / ไฟล์แบตช์:

  • "" หยุดพักเนื่องจากไม่ได้รับการสนับสนุนโดยพื้นฐาน:

    • powershell -c " ""ab c"".length " -> ข้อผิดพลาด "สตริงไม่มีตัวยุติ"
  • \"และ""" ทำงานตามหลักการแต่ไม่ปลอดภัย :

    • powershell -c " \"ab c\".length "ทำงานตามที่ตั้งใจไว้: เอาต์พุต5(สังเกต2ช่องว่าง)
    • แต่มันไม่ปลอดภัยเพราะcmd.exemetacharacters ทำลายคำสั่งเว้นแต่จะ Escape:
      powershell -c " \"a& c\".length " หยุดพักเนื่องจากสิ่ง&ที่จะต้องหนีในฐานะ^&
  • \""เป็นที่ปลอดภัยแต่การตกแต่งภายในปกติช่องว่างซึ่งสามารถที่ไม่พึงประสงค์:

    • powershell -c " \""a& c\"".length "เอาต์พุต4(!) เนื่องจากช่องว่าง 2 ช่องถูกทำให้เป็นมาตรฐานเป็น 1
  • "^""เป็นตัวเลือกที่ดีที่สุดสำหรับการWindows PowerShellโดยเฉพาะซึ่งจะมีทั้งความปลอดภัยและช่องว่างการรักษา แต่กับ PowerShell หลัก (ใน Windows) มันเป็นเช่นเดียวกับ\""คือด้วยช่องว่างnormalizing เครดิตไปที่Venryxสำหรับการค้นพบแนวทางนี้

    • powershell -c " "^""a& c"^"".length " ผลงาน : ไม่แตก - แม้จะ&- และผลลัพธ์5คือช่องว่างที่เก็บรักษาไว้อย่างถูกต้อง

    • PowerShell หลัก : pwsh -c " "^""a& c"^"".length " ทำงานแต่ผล4คือNormalizes ช่องว่างเป็น\""ไม่

บนแพลตฟอร์มที่เหมือน Unix (Linux, macOS) เมื่อเรียกใช้CLI ของPowerShell [Core]pwshจากเชลล์ที่คล้าย POSIX เช่นbash:

คุณต้องใช้\"อย่างไรก็ตามทั้งปลอดภัยและรักษาช่องว่าง :

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

ข้อมูลที่เกี่ยวข้อง

  • ^เท่านั้นที่สามารถนำมาใช้เป็นตัวหนีในunquotedสตริง - สตริงภายในสองครั้งที่ยกมา^เป็นพิเศษและไม่ถือว่าเป็นตัวอักษร

    • CAVEAT : การใช้^ในพารามิเตอร์ที่ส่งผ่านไปยังcallคำสั่งนั้นใช้ไม่ได้ (ใช้กับทั้งการใช้call: การเรียกใช้ไฟล์แบตช์หรือไบนารีอื่นและการเรียกรูทีนย่อยในไฟล์แบตช์เดียวกัน):
      • ^กรณียกมาสองครั้งค่าเท่าตัวลึกลับเปลี่ยนแปลงมูลค่าที่ถูกส่ง: เช่นถ้าตัวแปร%v%มีค่าที่แท้จริงa^b, call :foo "%v%"กำหนด"a^^b"ที่จะ (!) %1(พารามิเตอร์แรก) :fooในย่อย
      • unquotedใช้^กับcallจะเสียโดยสิ้นเชิงในการที่^จะไม่สามารถใช้ในการหลบหนีตัวอักษรพิเศษ : เช่นcall foo.cmd a^&bเงียบ ๆ แบ่ง (แทนการส่งผ่านตัวอักษรa&bมากเกินไปfoo.cmdเช่นจะเป็นกรณีที่โดยไม่ต้องcall) -foo.cmdไม่เคยเรียกแม้อย่างน้อยบน Windows (!) 7.
  • การหลีกเลี่ยงลิเทอรัล%เป็นกรณีพิเศษน่าเสียดายที่ต้องใช้ไวยากรณ์ที่แตกต่างกันขึ้นอยู่กับว่าสตริงถูกระบุในบรรทัดคำสั่งเทียบกับภายในไฟล์แบตช์หรือไม่ ดูhttps://stackoverflow.com/a/31420292/45375

    • สั้น ๆ : ภายในไฟล์แบตช์ให้ใช้%%. ในบรรทัดคำสั่งที่%ไม่สามารถหนีออกมา แต่ถ้าคุณวาง^ที่เริ่มต้นสิ้นสุดหรือภายในชื่อตัวแปรในunquotedสตริง (เช่นecho %^foo%) คุณสามารถป้องกันไม่ให้เกิดการขยายตัวของตัวแปร (แก้ไข); %อินสแตนซ์บนบรรทัดคำสั่งที่ไม่ได้เป็นส่วนหนึ่งของการอ้างอิงตัวแปรจะถือว่าเป็นตัวอักษร (เช่น100%)
  • โดยทั่วไปในการทำงานอย่างปลอดภัยกับค่าตัวแปรที่อาจมีช่องว่างและอักขระพิเศษ :

    • ที่ได้รับมอบหมาย : แนบทั้งชื่อตัวแปรและความคุ้มค่าในที่เดียวคู่ของราคาสองครั้ง ; เช่นset "v=a & b"กำหนดค่าตามตัวอักษรa & bให้กับตัวแปร%v%(ในทางตรงกันข้ามset v="a & b"จะทำให้เครื่องหมายคำพูดคู่เป็นส่วนหนึ่งของค่า) หลีกเลี่ยง%อินสแตนซ์ตามตัวอักษรเป็น%%(ใช้ได้เฉพาะในไฟล์แบตช์ - ดูด้านบน)
    • การอ้างอิง : การอ้างอิงตัวแปร double-quoteเพื่อให้แน่ใจว่าค่าของพวกเขาไม่ได้ถูกแก้ไข เช่นecho "%v%"ไม่อยู่ภายใต้ค่าของ%v%การแก้ไขและการพิมพ์"a & b"(แต่โปรดทราบว่าเครื่องหมายคำพูดคู่จะถูกพิมพ์อย่างสม่ำเสมอด้วย) โดยคมชัดecho %v%ผ่านตัวอักษรaที่จะechoตำเป็นผู้ประกอบการลำดับคำสั่งและดังนั้นจึงพยายามที่จะดำเนินการคำสั่งที่มีชื่อว่า& โปรดสังเกตข้อแม้ข้างต้นที่ใช้ซ้ำกับคำสั่งb
      ^call
    • โดยทั่วไปโปรแกรมภายนอกจะดูแลการลบเครื่องหมายอัญประกาศคู่รอบพารามิเตอร์ แต่ตามที่ระบุไว้ในไฟล์แบตช์คุณต้องทำด้วยตัวเอง (เช่น%~1เพื่อลบการปิดเครื่องหมายอัญประกาศคู่ออกจากพารามิเตอร์ที่ 1) และน่าเศร้าที่ไม่มีโดยตรง วิธีที่ฉันรู้ที่จะได้รับechoการพิมพ์ค่าตัวแปรนับถือโดยไม่ต้องล้อมรอบราคาสองครั้ง
      • นีลมีวิธีแก้ปัญหาชั่นว่างานตราบเท่าที่คุ้มค่าได้ฝังไม่มีคำพูดสอง ; เช่น:for
        set "var=^&')|;,%!" for /f "delims=" %%v in ("%var%") do echo %%~v
  • cmd.exeไม่ได้รับรู้เดียว -quotesเป็นตัวคั่นสตริง - พวกเขาจะถือว่าเป็นตัวอักษรและไม่สามารถโดยทั่วไปจะใช้ในการสตริงคั่นด้วยช่องว่างฝัง; นอกจากนี้ยังเป็นไปตามที่โทเค็นที่ติดเครื่องหมายคำพูดเดี่ยวและโทเค็นใด ๆ ที่อยู่ระหว่างนั้นจะถือว่าไม่มีการอ้างสิทธิ์cmd.exeและตีความตามนั้น

    • อย่างไรก็ตามเนื่องจากโปรแกรมเป้าหมายดำเนินการแยกวิเคราะห์อาร์กิวเมนต์ของตนเองในท้ายที่สุดโปรแกรมบางโปรแกรมเช่น Ruby จะรู้จักสตริงที่มีเครื่องหมายอัญประกาศเดี่ยวแม้ใน Windows ในทางตรงกันข้ามไฟล์ปฏิบัติการ C / C ++, Perl และ Python ไม่รู้จักสิ่งเหล่านี้
      แม้ว่าได้รับการสนับสนุนโดยโปรแกรมเป้าหมาย cmd.exeแต่ก็ไม่แนะนำให้ใช้สายเดียวที่ยกมาระบุว่าเนื้อหาของพวกเขาจะไม่ได้รับการคุ้มครองจากการตีความที่ไม่พึงประสงค์ที่อาจเกิดขึ้นโดย

การอ้างอิงจากภายใน PowerShell:

Windows PowerShellเป็นเชลล์ขั้นสูงกว่าcmd.exeมากและเป็นส่วนหนึ่งของ Windows มาหลายปีแล้ว (และPowerShell Coreก็นำประสบการณ์ PowerShell มาสู่ macOS และ Linux เช่นกัน)

PowerShell ทำงานภายในอย่างสม่ำเสมอเกี่ยวกับการอ้างถึง:

  • ภายในสตริงที่ยกมาคู่ใช้`"หรือ""เพื่อหลีกเลี่ยงเครื่องหมายอัญประกาศคู่
  • ภายในสตริงที่ยกมาเดี่ยวใช้''เพื่อหลีกเลี่ยงเครื่องหมายคำพูดเดี่ยว

สิ่งนี้ใช้ได้กับบรรทัดคำสั่ง PowerShell และเมื่อส่งผ่านพารามิเตอร์ไปยังสคริปต์หรือฟังก์ชันPowerShell จากภายใน PowerShell

(ดังที่ได้กล่าวไว้ข้างต้นการส่งคำพูดคู่ที่ใช้ Escape ไปยัง PowerShell จากภายนอกต้องใช้\"หรือมีประสิทธิภาพมากขึ้น\""- ไม่มีอะไรทำงานได้อีก)

น่าเศร้าเมื่อเรียกใช้โปรแกรมภายนอกจาก PowerShell คุณต้องเผชิญกับความจำเป็นในการรองรับกฎการอ้างสิทธิ์ของ PowerShell และเพื่อหลีกเลี่ยงโปรแกรมเป้าหมาย :

พฤติกรรมที่เป็นปัญหานี้จะกล่าวถึงและสรุปไว้ในคำตอบนี้ด้วย

Double -quotes ภายในสตริงdouble -quoted :

พิจารณาสตริง"3`" of rain"ซึ่ง 3" of rainPowerShell-ภายในแปลเป็นตัวอักษร

หากคุณต้องการส่งสตริงนี้ไปยังโปรแกรมภายนอกคุณต้องใช้การหลบหนีของโปรแกรมเป้าหมายเพิ่มเติมจาก PowerShell's ; สมมติว่าคุณต้องการส่งสตริงไปยังโปรแกรม C ซึ่งคาดว่าเครื่องหมายคำพูดคู่ที่ฝังไว้จะถูก Escape เป็น\":

foo.exe "3\`" of rain"

หมายเหตุวิธีการทั้งสอง `" - เพื่อให้มีความสุข PowerShell - และ\ - เพื่อให้มีความสุขโปรแกรมเป้าหมาย - จะต้องนำเสนอ

ตรรกะเดียวกันนี้ใช้กับการเรียกไฟล์แบตช์โดยที่""ต้องใช้:

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

ในทางตรงกันข้ามการฝังsingle -quotes ในสตริงdouble -quotedไม่จำเป็นต้องมีการหลบหนีเลย

โสด -quotes ภายในเดียวสตริง -quotedไม่ไม่จำเป็นต้องเสริมหลบหนี; พิจารณา'2'' of snow'ซึ่งเป็นตัวแทน PowerShell 2' of snowของ

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

PowerShell แปลสตริงที่ยกมาเดี่ยวเป็นสตริงที่ยกมาสองครั้งก่อนที่จะส่งต่อไปยังโปรแกรมเป้าหมาย

อย่างไรก็ตามdouble -quotes ภายในสตริงsingle -quotedซึ่งไม่จำเป็นต้องมีการ Escape สำหรับPowerShellยังคงต้องมีการ Escape สำหรับโปรแกรมเป้าหมาย :

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

PowerShell v3แนะนำ--%ตัวเลือกเวทย์มนตร์ที่เรียกว่าสัญลักษณ์หยุดการแยกวิเคราะห์ซึ่งช่วยบรรเทาความเจ็บปวดบางอย่างโดยการส่งผ่านอะไรก็ได้หลังจากที่ไม่ได้ตีความไปยังโปรแกรมเป้าหมายบันทึกcmd.exeการอ้างอิงตัวแปรสภาพแวดล้อม - style (เช่น%USERNAME%) ซึ่งจะขยาย; เช่น:

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

หมายเหตุวิธีหนีฝังตัว"เป็น\"โปรแกรมเป้าหมายเท่านั้น (และไม่ยัง PowerShell เป็น\`") ก็เพียงพอแล้ว

อย่างไรก็ตามแนวทางนี้:

  • ไม่อนุญาตให้มีการหลีกเลี่ยง %อักขระเพื่อหลีกเลี่ยงการขยายตัวแปรสภาพแวดล้อม
  • ห้ามการใช้ตัวแปรและนิพจน์ PowerShell โดยตรง แทนที่จะสร้างบรรทัดคำสั่งในตัวแปรสตริงในขั้นตอนแรกจากนั้นเรียกใช้Invoke-Expressionในวินาที

ดังนั้นแม้จะมีความก้าวหน้ามากมาย แต่ PowerShell ก็ไม่ได้ทำให้การหลบหนีง่ายขึ้นเมื่อเรียกโปรแกรมภายนอก อย่างไรก็ตามมีการแนะนำการสนับสนุนสำหรับสตริงที่ยกมาเดี่ยว

ผมสงสัยว่ามันเป็นไปได้ที่ลึกซึ้งในโลกของ Windows เพื่อสลับเคยไปแบบ Unix ของการปล่อยให้เปลือกทำทุก tokenization และอ้างการกำจัดที่ไม่คาดฝัน , ขึ้นด้านหน้า , โดยไม่คำนึงถึงโปรแกรมเป้าหมายและจากนั้นเรียกใช้โปรแกรมเป้าหมายโดยผ่านราชสกุลที่เกิด .


คำอธิบายที่ดี! แต่ ... Literal use of ^ in double-quoted strings can, be problematic when applying ~ ...ไม่จริง เครื่องหมายทิลเดจะลบเฉพาะเครื่องหมายคำพูดภายนอกเมื่อมีอยู่ การสูญเสียคาเร็ตเป็นปัญหาของการจัดการเอง โดยปกติการset "param=%~1"แก้ปัญหานี้
jeb

ขอบคุณ @jeb ปัญหาไม่ได้เฉพาะเจาะจงสำหรับการใช้งาน~- ฉันได้อัปเดตคำตอบเพื่อสะท้อนความเข้าใจใหม่ของฉัน - โปรดแสดงความคิดเห็นหากคุณพบปัญหา คุณมีคำอธิบายเกี่ยวกับการเพิ่มขึ้นเป็นสองเท่า^โดยอัตโนมัติเมื่อคุณส่งผ่านพารามิเตอร์ไปยังcallคำสั่งหรือไม่?
mklement0

3
ฉันเดาได้แค่ว่ามีคนที่ MS คิดว่ามันยอดเยี่ยม คาเร็ตสองเท่าจะถูกลบออกโดยอัตโนมัติในขั้นตอนการแยกวิเคราะห์ที่สอง แต่นี่เป็นความล้มเหลวครั้งใหญ่เนื่องจากใช้ไม่ได้ในเครื่องหมายคำพูดและป้องกันไม่ให้อักขระพิเศษใด ๆ หลบหนีได้อย่างมีประสิทธิภาพ เหมือนกับcall echo cat^^&dogว่ามันไม่สามารถแก้ไขได้ด้วย
คาเร็ต

ขอบคุณ @jeb ฉันไม่ได้พิจารณาไฟล์ unquotedใช้^ด้วยcallซึ่งในขณะที่คุณชี้เสียไม่ดี ดูเหมือนว่าในcall echo cat^&dog(single ^ที่หลีกเลี่ยง&) คำสั่งเป้าหมาย ( echo) จะไม่ถูกเรียกด้วยซ้ำ (!) - คำสั่งทั้งหมดล้มเหลวอย่างเงียบ ๆ ฉันได้อัปเดตคำตอบตามนั้น
mklement0

คำตอบที่ดี อย่างไรก็ตามฉันไม่แนะนำให้""หนี"แต่เสมอไป\" (ดูคำตอบของฉันสำหรับวิธีที่อันตรายน้อยกว่าในการใช้จากภายใน cmd) ผมไม่ทราบว่าเอกสารที่เป็นทางการใด ๆ ที่กำหนด""เป็นอ้างหนี แต่อย่างน้อย 2 ที่กล่าวถึง\": .NETและVS แม้ว่าเอกสารจะไม่ถูกต้อง แต่Win32 api ก็ปฏิบัติตามกฎเหล่านี้เช่นกัน
TS

24

ในที่สุด Google ก็ได้คำตอบ ไวยากรณ์สำหรับการเปลี่ยนสตริงในแบตช์คือ:

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

ซึ่งสร้าง "จำลองฉัน" ตอนนี้สคริปต์ของฉันมีลักษณะดังนี้:

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

ซึ่งแทนที่อินสแตนซ์ทั้งหมด"ด้วยค่า\"Escape อย่างเหมาะสมสำหรับ bash


9

นอกเหนือจากคำตอบที่ยอดเยี่ยมของ mklement0 :

เกือบ executables ทั้งหมดยอมรับเป็นหนี\" "การใช้งานที่ปลอดภัยใน cmd อย่างไรก็ตามเกือบจะทำได้โดยใช้ DELAYEDEXPANSION เท่านั้น
หากต้องการส่งลิเทอรัล"ไปยังกระบวนการบางอย่างอย่างชัดเจนให้กำหนด\"กับตัวแปรสภาพแวดล้อมจากนั้นใช้ตัวแปรนั้นเมื่อใดก็ตามที่คุณต้องการส่งใบเสนอราคา ตัวอย่าง:

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

บันทึก SETLOCAL ENABLEDELAYEDEXPANSIONดูเหมือนว่าจะใช้งานได้ภายในไฟล์แบตช์เท่านั้น ที่จะได้รับ DELAYEDEXPANSION cmd /V:ONในเซสชั่นแบบโต้ตอบเริ่มต้น

หาก batchfile ของคุณไม่ทำงานกับ DELAYEDEXPANSION คุณสามารถเปิดใช้งานได้ชั่วคราว:

::region without DELAYEDEXPANSION

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

::region without DELAYEDEXPANSION

หากคุณต้องการส่งผ่านเนื้อหาแบบไดนามิกจากตัวแปรที่มีเครื่องหมายคำพูดที่เป็น Escape ซึ่ง""คุณสามารถแทนที่""ด้วย\"เมื่อขยายได้:

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

การเปลี่ยนนี้ไม่ปลอดภัยกับ %...%การขยายสไตล์!

ในกรณีของOP bash -c "g++-linux-4.1 !v_params:"=\"!"เป็นเวอร์ชันที่ปลอดภัย


หากด้วยเหตุผลบางประการแม้กระทั่งการเปิดใช้งาน DELAYEDEXPANSION ชั่วคราวไม่ใช่ตัวเลือกโปรดอ่านต่อ:

การใช้ \"จากภายใน cmd จะปลอดภัยกว่าเล็กน้อยหากต้องการหลีกเลี่ยงอักขระพิเศษเสมอแทนที่จะใช้เพียงบางครั้ง (มีโอกาสน้อยที่จะลืมคาเร็ตหากสอดคล้องกัน ... )

เพื่อให้บรรลุสิ่งนี้คำพูดหนึ่งนำหน้าคำพูดใด ๆ ที่มีเครื่องหมายคาเร็ต ( ^") เครื่องหมายคำพูดที่ควรเข้าถึงกระบวนการย่อยเนื่องจากตัวอักษรจะต้องถูกหลีกเลี่ยงด้วยเครื่องหมายแบ็กแลช ( \^") ต้องใช้อักขระเมตาเชลล์ทั้งหมดด้วย^เช่นกันเช่น&=> ^&; |=> ^|; >=>^> ; เป็นต้น

ตัวอย่าง:

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

ที่มา: ทุกคนเสนอราคาอาร์กิวเมนต์บรรทัดคำสั่งผิดวิธีโปรดดู "วิธีการอ้างอิงที่ดีกว่า"


ในการส่งผ่านเนื้อหาแบบไดนามิกเราต้องตรวจสอบสิ่งต่อไปนี้:
ส่วนของคำสั่งที่มีตัวแปรจะต้องได้รับการพิจารณาว่า "ยกมา" โดยcmd.exe(เป็นไปไม่ได้หากตัวแปรสามารถมีเครื่องหมายคำพูดได้ - อย่าเขียน%var:""=\"% ) เพื่อให้บรรลุสิ่งนี้ค่าสุดท้าย"ก่อนตัวแปรและตัวแรก"หลังตัวแปรจะไม่ใช้ค่า^Escape cmd-metacharacters ระหว่างทั้งสอง"ต้องไม่หนี ตัวอย่าง:

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

สิ่งนี้ไม่ปลอดภัยหาก%dynamic_content%สามารถมีคำพูดที่ไม่ตรงกัน


เข้าใจแล้วขอบคุณ ใช่การ^เว้นวรรคอย่างเด็ดขาด - การเว้นวรรคอักขระทั้งหมดทำงานได้อย่างมีประสิทธิภาพและสามารถนำไปใช้อย่างมีระบบได้มากขึ้น (แต่เห็นได้ชัดว่าเป็นความเจ็บปวดในส่วนของร่างกายที่คุณเลือก) ฉันได้อัปเดตคำตอบตามนั้นแล้ว (และให้เครดิตคุณ)
mklement0

@ mklement0 ขอบคุณ! ใช่มันเป็นความเจ็บปวดจริงๆ มันน่ารำคาญและยังง่ายเกินไปที่จะลืมอักขระเมตาคาแร็กเตอร์ (ดังนั้นฉันจึงใช้!q!วิธีนี้เป็นส่วนใหญ่) หมายเหตุ: คำตอบของคุณจะไม่สอดคล้องกันเล็กน้อยหลังจากการแก้ไขครั้งสุดท้ายของคุณ: ใกล้ด้านบนที่คุณพูดว่า: "ไม่ได้ใช้^"" หลังจากนั้นคุณใช้^"เป็นส่วนหนึ่งของวิธีแก้ปัญหาชั่วคราว บางทีคุณอาจอธิบายได้ทั้งสองวิธี? (1) การหลีกเลี่ยงอักขระเมตาทั้งหมด (เป็นระบบมากขึ้น) / (2) การหลีกเลี่ยงอักขระเมตาคาแร็กเตอร์ในพื้นที่ " foo.exe ^"danger ^& bar=\"%dynamic_content%\"^"ไม่ได้ใส่เครื่องหมายคำพูด" (บางครั้งจำเป็นต้องส่งผ่านเนื้อหาแบบไดนามิกเช่น- วิธีนี้ตัวแปรจะถูกยกมาสำหรับ cmd)
TS

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

1
@ mklement0 คำตอบของคุณสมเหตุสมผลเสมอ :-) ฉันแค่อยากให้แนวคิดบางอย่างสำหรับการปรับปรุงที่เป็นไปได้ ฉันยังเพิ่มตัวอย่างเกี่ยวกับ%dynamic_content%คำตอบของฉัน คุณคิดว่ามันมีรายละเอียดเพียงพอหรือไม่หรือฉันต้องอธิบายเพิ่มเติม?
TS

3
เจ๋งมากขอบคุณที่แจ้งให้เราทราบ ความคิดที่ดีในการแปลเป็นภาษาท้องถิ่นsetlocal delayedexpansionแต่คุณควรปิดกั้นด้วยendlocal(ไม่มีข้อโต้แย้ง) จริงๆแล้วหัวของฉันเริ่มหมุนมองไปที่ Gist ของคุณ เรากำลังจัดการกับกรณีพิเศษที่นี่และฉันคิดว่าผู้อ่านในอนาคตจะพบทุกสิ่งที่ต้องการระหว่างคำตอบสองคำ
mklement0

0

หากสตริงอยู่ในเครื่องหมายคำพูดแล้วให้ใช้เครื่องหมายคำพูดอื่นเพื่อลบล้างการกระทำ

echo "Insert tablename(col1) Values('""val1""')" 

-3

ตัวอย่างเช่นสำหรับเครื่องมือ Unreal engine Automation ทำงานจากไฟล์แบตช์ - สิ่งนี้ใช้ได้กับฉัน

เช่น -cmdline = "-Messaging" -device = device -addcmdline = "- SessionId = session -SessionOwner = 'owner' -SessionName = 'Build' -dataProviderMode = local -LogCmds = 'LogC Commodity OFF' -execcmds = 'รายการอัตโนมัติ ; รันการทดสอบ + คั่น + ด้วย + T1 + T2; เลิก '"- รัน

หวังว่านี่จะช่วยใครบางคนทำงานให้ฉัน


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