ทุบตีแตกต่างระหว่างการขยายรั้งและการจัดกลุ่มคำสั่งได้อย่างไร


47

ฉันสังเกตเห็นว่า{สามารถใช้ในการขยายรั้ง:

echo {1..8}

หรือในการจัดกลุ่มคำสั่ง:

{ls;echo hi}

ทุบตีรู้ความแตกต่างได้อย่างไร?


1
คำถามยอดเยี่ยม +1 ดูเหมือนว่ามันอาจจะ{ถูกตีความว่าเป็นรายการคำสั่งหากมันปรากฏที่จุดเริ่มต้นของคำสั่งและเป็นส่วนขยายรั้งมิฉะนั้น แต่ฉันไม่แน่ใจ
Celada

16
{ls;echo hi}bashไม่ได้ตามกฎหมาย คุณต้องเว้นวรรคหลังวงเล็บปีกกาเปิดและเครื่องหมายอัฒภาคก่อนปิดช่องว่าง
PSkocik

คำตอบ:


38

เหตุผลที่ทำให้เข้าใจง่ายคือการมีอยู่ของตัวละครตัวหนึ่ง: space.

การขยายของ Brace ไม่ได้ดำเนินการกับช่องว่าง (ยกเลิกการอ้างอิง)

{...}รายการความต้องการ (ยกเลิกการอ้าง) ช่องว่าง

คำตอบรายละเอียดมากขึ้นคือวิธีการที่เปลือกแยกวิเคราะห์บรรทัดคำสั่ง


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

  1. แยกคำสั่งเป็นโทเค็นที่คั่นด้วยชุดเมตาอักขระที่คงที่: SPACE, TAB, NEWLINE,;, (,), <,>, |, และ & ประเภทของโทเค็นรวมถึงคำคำหลักตัวเปลี่ยนเส้นทางของ I / O และอัฒภาค

Meta-ตัวอักษร: และspacetabenter;,<>|&

หลังจากแยกคำอาจเป็นประเภท (ตามที่เข้าใจโดยเปลือก):

  • คำสั่งการมอบหมายล่วงหน้า: LC=ALL ...
  • คำสั่ง LC=ALL echo
  • ข้อโต้แย้ง LC=ALL echo "hello"
  • การเปลี่ยนเส้นทาง LC=ALL echo "hello" >&2

การขยายรั้ง

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

ดังนั้นสิ่งนี้: {ls,-l}มีคุณสมบัติเป็น "การขยายรั้ง" ที่จะกลายเป็นls -lไม่ว่าจะเป็นfirst wordหรือargument(ใน bash, zsh แตกต่างกัน)

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

{ls ,-l}แต่นี้จะไม่: Bash จะแยกspaceและแยกบรรทัดเป็นสองคำ: {lsและ,-l}ซึ่งจะทริกเกอร์command not found(อาร์กิวเมนต์,-l}หายไป):

 $ {ls ,-l}
 bash: {ls: command not found

สายของคุณ: {ls;echo hi}จะไม่กลายเป็น "การขยายตัวรั้ง" เพราะของสองตัวละครเมตาและ;space

มันจะถูกแบ่งออกเป็นสามส่วนนี้: คำสั่งใหม่:{ls echo hi}เข้าใจว่าเป็น;ต้นเหตุของการเริ่มต้นคำสั่งใหม่ คำสั่ง{lsจะไม่ได้พบและคำสั่งต่อไปจะพิมพ์hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

หากวางไว้หลังคำสั่งอื่นคำสั่งจะเริ่มคำสั่งใหม่หลังจาก;:

$ echo {ls;echo hi}
{ls
hi}

รายการ

หนึ่งใน "คำสั่งผสม" เป็นรายการ "รั้ง" { list; }(คำพูดของฉัน): ในขณะที่คุณสามารถดูจะถูกกำหนดให้มีช่องว่างและปิด
ช่องว่างและมีความจำเป็นเพราะทั้งสองและเป็น " คำสงวน" ;
;{}

และดังนั้นจึงจะได้รับการยอมรับว่าเป็นคำที่ต้องถูกล้อมรอบด้วยอักขระเมตา (เกือบเสมอspace)

ตามที่อธิบายไว้ในจุดที่ 2 ของหน้าเชื่อมโยง

  1. ตรวจสอบโทเค็นแรกของแต่ละคำสั่งเพื่อดูว่ามันเป็น .... , {, หรือ (จากนั้นคำสั่งนั้นเป็นคำสั่งผสม

ตัวอย่างของคุณ: {ls;echo hi}ไม่ใช่รายการ

มันต้องการการปิด;และช่องว่างหนึ่งช่อง (อย่างน้อย) หลังจาก{นั้น สุดท้ายจะถูกกำหนดโดยปิด};

{ ls;echo hi; }นี่คือรายการ และนี่{ ls;echo hi;}ก็เป็น (ใช้น้อยกว่าปกติ แต่ใช้ได้) (ขอบคุณ @choroba สำหรับความช่วยเหลือ)

$ { ls;echo hi; }
A-list-of-files
hi

แต่เนื่องจากอาร์กิวเมนต์ (เชลล์รู้ถึงความแตกต่าง) กับคำสั่งมันจะทริกเกอร์ข้อผิดพลาด:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

แต่ระวังสิ่งที่คุณเชื่อว่าเชลล์กำลังวิเคราะห์คำ:

$ echo { ls;echo hi;
{ ls
hi

2
นี่เป็นคำตอบที่ดีที่สุดเพราะคุณให้เราว่า bash parser ทำงานอย่างไร! และมีคำอธิบายรายละเอียด!
59

2
คุณไม่จำเป็นต้องช่องว่างระหว่างและ; ทำงานเป็นเครื่องหมายอัฒภาคได้แล้วเมตาอักขระ }{ ls;}
choroba

1
@loprpring ขอบคุณใช่ฉันใช้เวลาในการเขียน ฉันดีใจที่รู้ว่ามันมีประโยชน์ ขอบคุณอีกครั้ง

บทความที่ยอดเยี่ยมขอบคุณมากสำหรับการอ้างอิง
Edward Torvalds

16

บล็อก{เป็นคำหลักเปลือกดังนั้นจึงต้องแยกออกมาจากคำถัดไปด้วยช่องว่างในขณะที่ในการขยายตัวรั้งควรจะมีไม่มีช่องว่าง (ถ้าคุณต้องการที่จะรั้งขยายพื้นที่ที่คุณจะต้องหลบหนีมันecho {\ ,a}{b,c})

คุณสามารถใช้การขยายรั้งในตอนเริ่มต้นของคำสั่ง:

{ls,.}  # expands to "ls ."

คุณไม่สามารถใช้มันเพื่อขยายไปยังบล็อกได้เนื่องจากการแยกวิเคราะห์คำสั่งการจัดกลุ่มเกิดขึ้นก่อนการขยาย:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory

5

มันรู้โดยการตรวจสอบไวยากรณ์ของบรรทัดคำสั่ง ด้วยวิธีเดียวกันกับที่รู้ว่าในนิพจน์echo echoecho แรกควรถูกใช้เป็นคำสั่งและ echo ที่สองเป็นพารามิเตอร์ของ echo แรก

ในทุบตีมันง่ายมากเนื่องจาก{ cmd; }ควรมีช่องว่างและเครื่องหมายอัฒภาค อย่างไรก็ตามตัวอย่างเช่นใน zsh ไม่จำเป็น แต่ยังโดยการวิเคราะห์บริบทของ{}เชลล์สามารถบอกได้ว่าควรทำอะไรกับเนื้อหาของเชลล์

พิจารณาสิ่งต่อไปนี้:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

ทั้งส่งคืนวันที่ปัจจุบัน แต่

echo {1..3}

ส่งคืน1 2 3เนื่องจากเชลล์รู้{}ในอาร์กิวเมนต์สำหรับคำสั่งechoดังนั้นควรขยาย


{ตามด้วยพื้นที่ที่ไม่มีเครื่องหมายอัญประกาศจะไม่เริ่มต้นการขยายรั้งในทุบตี
choroba

@choroba ใช่และไม่เพียง แต่หลังจาก{นั้น พื้นที่ที่ไม่ได้ระบุไม่สามารถอยู่ที่ใดก็ได้เพราะเชลล์แบ่งบรรทัดคำสั่งทั้งหมดในช่องว่าง
jimmij

0

ประการแรกคอมโพสิตรั้งต้องเป็นคำด้วยตัวเองและคำแรกของบรรทัดคำสั่ง:

echo { these braces are just words }

ประการที่สองเครื่องมือจัดฟันแต่ละอันไม่พิเศษ (ดังที่คุณเห็นด้านบน) การจัดฟันที่ว่างเปล่านั้นไม่พิเศษเช่นกัน:

echo {} # just the token {}: familiar from the find command

สิ่งที่ไม่มีเครื่องหมายจุลภาคก็เป็นเพียงตัวเอง

echo {abc} # just {abc}

นี่คือที่ที่การกระทำเริ่มต้นขึ้น

echo {a,b} # braces disappear, a b results.

ดังนั้นโดยพื้นฐานแล้วสำหรับการขยายรั้งที่จะเตะเข้าเราจำเป็นต้องมีคำเดียว (ไม่แยกออกเป็นช่องบนช่องว่าง) ซึ่งภายในนั้นเกิดขึ้นอย่างน้อยหนึ่งตัวอย่างของ{...}ด้านในซึ่งมีเครื่องหมายจุลภาคอย่างน้อยหนึ่งรายการ

นี่อาจเป็นคำแรกในบรรทัดคำสั่งโดยวิธี:

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