ทำไมเครื่องหมายอัศเจรีย์ "!" บางครั้งทำให้ไม่พอใจทุบตี?


14

ฉันรู้ว่า!มีความสำคัญเป็นพิเศษใน commandline ในบริบทของประวัติศาสตร์ commandline แต่นอกเหนือจากนั้นในสคริปต์การเรียกใช้เครื่องหมายอัศเจรีย์ในบางครั้งอาจทำให้เกิดข้อผิดพลาดในการแยกวิเคราะห์
ฉันคิดว่ามันมีบางอย่างเกี่ยวข้องกับการeventแต่ฉันไม่ทราบว่าเหตุการณ์คืออะไรหรือทำอะไร ถึงกระนั้นคำสั่งเดียวกันก็สามารถทำงานต่างกันในสถานการณ์ที่ต่างกัน
ตัวอย่างสุดท้ายด้านล่างทำให้เกิดข้อผิดพลาด แต่ทำไมเมื่อรหัสเดียวกันทำงานนอกการทดแทนคำสั่ง? .. ใช้ GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found


@Warren .. ขอบคุณ ฉันเห็นว่า QA แต่จริงๆแล้วมันพูดถึงวิธีการหลบหนีแบ็กสแลชเท่านั้น ... ปัญหาของฉันเกี่ยวข้องกับสาเหตุที่รหัสที่ดูเหมือนว่าจะใช้งานได้แล้วดูเหมือนจะหนีหายไปในสถานการณ์เดียวและไม่ใช่เรื่องอื่น ...
Peter.O

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

@Caleb ใช่ฉันใช้คำผิด .. protectedจะเหมาะสมกว่า (ได้รับการคุ้มครองโดย 'คำพูดเดียว')
Peter.O

หากคุณกังวลกับการมอบหมายอย่างง่ายเท่านั้นคุณสามารถใช้var=$(…)(ไม่มีเครื่องหมายคำพูดคู่) และมันจะทำงานเหมือน (ฉันคิดว่า) ที่คุณคาดหวัง นี้ยังคงเป็น“ปลอดภัย” เพราะส่วนมูลค่าของการกำหนดง่ายๆคือไม่ใช่เรื่องที่จะแยกคำหรือ globbing (แม้ว่านี้อาจไม่เป็นจริงของการมอบหมายงานทำผ่าน builtins (เช่นexport, localฯลฯ ) ภายใต้เปลือกหอยทั้งหมด) น่าเสียดายที่สิ่งนี้ไม่ได้ขยายออกไปนอกเหนือจากการมอบหมายอย่างง่าย ๆ เนื่องจากการเสนอราคาซ้ำเป็นวิธีการป้องกันการแยกคำและการโค้งขณะที่ยังคงได้รับการขยายประเภทอื่น ๆ ในบริบทอื่น ๆ
Chris Johnsen

คำตอบ:


12

!จะเรียกตัวละครของทุบตีเปลี่ยนตัวประวัติศาสตร์ เมื่อตามด้วยสตริง (ในตัวอย่างที่ล้มเหลวของคุณ) มันจะพยายามขยายไปยังเหตุการณ์ประวัติครั้งสุดท้ายที่เริ่มต้นด้วยสตริงนั้น เช่นเดียวกับการ$varขยายไปยังค่าของสตริงนั้น!echoจะขยายเป็นคำสั่ง echo สุดท้ายในประวัติของคุณ

Space เป็นตัวละครที่แตกในการขยายตัว ก่อนอื่นให้สังเกตว่ามันจะทำงานกับตัวแปรอย่างไร:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

สิ่งเดียวกันจะเกิดขึ้นสำหรับการขยายประวัติศาสตร์ ตัวอักษรปัง ( !) เริ่มออกลำดับการเปลี่ยนประวัติ แต่ถ้าตามมาด้วยสตริง ตามด้วยช่องว่างทำให้มันเป็นตัวอักษรปังแทนที่จะเป็นส่วนหนึ่งของลำดับการแทนที่

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


ขอบคุณ Caleb .. อีกหนึ่งแนวคิดก่อนหน้าของฉันถูกไล่ออก ... ฉันคิดว่าการทุบตี bash นั้นทำมาจากตัวยึดด้านในสุดหรือรั้งแล้วก็ออกไปข้างนอก ...
Peter.O

1
การอ้างถึงทำให้เกิดความสับสนมากพอในการทุบตีโดยไม่มีการเปลี่ยนแปลงในสตริงที่ซ้อนกัน เนื่องจากเป็นสิ่งทดแทนเกิดขึ้นเร็วมากในกระบวนการ ลองพิจารณาตัวอย่างนี้:var=word; echo "test '$var'"; echo 'test "$var"'
คาเลบ

.. ใช่ undestood ฉันตระหนักถึงการซ้อนคำพูดในเครื่องหมายคำพูด ... ความเข้าใจผิดของฉันคือฉันคิดว่ารหัสในวงเล็บของการแทนที่คำสั่งจะถูกแยกวิเคราะห์แยกจากกันตามสิ่งที่อยู่รอบ ๆ วงเล็บเหล่านั้น แต่ดูเหมือนจะไม่ .. ขอบคุณ
Peter.O

6

เป็นแล้วกล่าวว่าโดยคาเลบ , !จะใช้ในการวิงวอนขอเปลี่ยนตัวประวัติทุบตีของ

ถ้าชอบฉันคุณรู้สึกว่าคุณไม่ต้องการคุณสมบัติดังกล่าวคุณสามารถปิดการใช้งานการแทรกบรรทัดต่อไปนี้ใน~/.bashrc:

set +H

ฉันไม่ต้องการเพราะประวัติสามารถกู้คืนได้ด้วยลูกศรขึ้นและCtrl- การrค้นหาย้อนกลับที่เพิ่มขึ้น ดูที่หน้าคู่มือของ bash ส่วนคำสั่งสำหรับการจัดการประวัติสำหรับรายการทางลัดโดยละเอียด


2
คุณอยู่โดยไม่ได้!!อย่างไร
คาเลบ

ขอบคุณ. ฉันคิดว่าอาจเป็นปัญหาพกพาง่าย แต่การใช้set +Hงานสคริปต์ก็ทำได้ดีเช่นกัน :) +1
Peter.O

2
@ เฟร็ด: แปลกการขยายประวัติโดยทั่วไปคือ "เปิด" สำหรับเชลล์แบบโต้ตอบเท่านั้น
enzotib

@enzo .. ขอบคุณอีกครั้ง .. ฉันได้ทำการทดสอบจาก commandline .. Ah! ถ้าการเรียนรู้ไม่สนุกนักมันน่าเบื่อ ... ฉันพูดถึงกาแฟเหรอ? มันก็ช่วยได้เช่นกัน :)
Peter.O

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

2

ตัวอย่างแรกของคุณ:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

อาจลดลงเป็น

echo '! p' 
echo '!p'

ภายในเครื่องหมายคำพูดเดี่ยวอักขระทั้งหมดจะเก็บรักษาค่าตัวอักษร ดังนั้นการ!สูญเสียความหมายพิเศษและการขยายประวัติศาสตร์นั้นไม่ได้มีการขึ้นรูปล่วงหน้า

ตัวอย่างที่สองและสามของคุณ:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

อาจลดลงเป็น

echo "'! p'"

echo "'!p'"

'! p'และ'!p'เป็นส่วนสำคัญของสตริงที่ยกมาสองครั้ง

ภายในราคาคู่ตัวละครทุกตัวรักษาค่าที่แท้จริงของพวกเขายกเว้น $ , `, และ\!

นั่นหมายถึงคำพูดเดียวของ'! p'และ'!p'ได้สูญเสียความหมายพิเศษของพวกเขา (เช่น: ไม่สามารถหลบหนี!) แต่!ยังคงความหมายพิเศษของมันดังนั้นการขยายประวัติศาสตร์จะดำเนินการ

อย่างไรก็ตามเมื่อ!ตามด้วยอักขระเว้นวรรคการขยายประวัติจะไม่ถูกดำเนินการ

ข้อความจากman bash:

quoting

[ ... ]

การใส่อักขระในเครื่องหมายคำพูดเดียวจะคงคุณค่าของตัวอักษรแต่ละตัวไว้ในเครื่องหมายคำพูด [ ... ]

การใส่อักขระในเครื่องหมายคำพูดคู่จะเก็บรักษาค่าตัวอักษรของอักขระทั้งหมดภายในเครื่องหมายคำพูดยกเว้น $, `, \, และเมื่อเปิดใช้งานการขยายประวัติ,! [... ] หากเปิดใช้งานการขยายประวัติจะดำเนินการเว้นแต่! ที่ปรากฏในเครื่องหมายคำพูดคู่หนีออกมาโดยใช้เครื่องหมายทับขวา แบ็กสแลชก่อนหน้า! จะไม่ถูกลบ

การขยายประวัติศาสตร์

[ ... ]

การขยายประวัติถูกนำเสนอโดยการปรากฏตัวของตัวละครการขยายประวัติศาสตร์ซึ่งก็คือ! โดยค่าเริ่มต้น. เครื่องหมายแบ็กสแลช (\) และเครื่องหมายคำพูดเดี่ยวเท่านั้นที่สามารถอ้างถึงอักขระส่วนขยายประวัติ

อักขระจำนวนมากยับยั้งการขยายประวัติหากพบทันทีหลังจากอักขระการขยายประวัติแม้ว่าจะไม่ได้กล่าวถึง: space, tab, newline, carriage return และ = หากเปิดใช้งานตัวเลือกเปลือก extglob (จะยับยั้งการขยายตัวด้วยเช่นกัน

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