เกิดอะไรขึ้นกับ“ echo $ (สิ่งของ)” หรือ“ echo `stuff`”?


31

ฉันใช้ข้อใดข้อหนึ่งต่อไปนี้

echo $(stuff)
echo `stuff`

(ที่stuffเป็นเช่นpwdหรือdateหรือสิ่งที่มีความซับซ้อนมากขึ้น)

จากนั้นฉันก็บอกว่าไวยากรณ์นี้ผิดการปฏิบัติที่ไม่ดีไม่หรูหราเกินความซ้ำซ้อนซับซ้อนเกินไปการเขียนโปรแกรมลัทธิขนส่งสินค้า noobish ไร้เดียงสา ฯลฯ

แต่คำสั่งใช้งานได้ดังนั้นมีอะไรผิดปกติกับมัน?


11
มันซ้ำซ้อนและดังนั้นจึงไม่ควรใช้ เปรียบเทียบกับการใช้แมวที่ไร้ประโยชน์: porkmail.org/era/unix/award.html
dr01

7
echo $(echo $(echo $(echo$(echo $(stuff)))))ยังใช้งานได้และคุณอาจยังไม่ได้ใช้ใช่ไหม ;-)
Peter - Reinstate Monica

1
คุณพยายามทำอะไรกับการขยายตัวนั้น
curiousguy

@currguy ฉันพยายามช่วยเหลือผู้ใช้คนอื่นดังนั้นคำตอบของฉันด้านล่าง เมื่อเร็ว ๆ นี้ฉันเห็นคำถามอย่างน้อยสามข้อที่ใช้ไวยากรณ์นี้ ผู้เขียนพยายามทำ…หลายstuffอย่าง : D
Kamil Maciorowski

2
นอกจากนี้เราทุกคนไม่เห็นด้วยหรือเปล่าว่ามันไม่ฉลาดที่จะกักตัวตัวเองไว้ในห้อง echo? ;-)
Peter - Reinstate Monica

คำตอบ:


63

TL; DR

แต่เพียงผู้เดียวstuffจะส่วนใหญ่อาจจะทำงานให้คุณ



คำตอบแบบเต็ม

เกิดอะไรขึ้น

เมื่อคุณวิ่งfoo $(stuff)นี่คือสิ่งที่เกิดขึ้น:

  1. stuff วิ่ง;
  2. เอาท์พุท (stdout) แทนการถูกพิมพ์แทนที่$(stuff)ในการภาวนาของfoo;
  3. จากนั้นfooรันอาร์กิวเมนต์บรรทัดคำสั่งจะขึ้นอยู่กับสิ่งที่stuffส่งคืน

นี้$(…)กลไกที่เรียกว่า "คำสั่งเปลี่ยนตัว" ในกรณีของคุณคำสั่งหลักคือechoโดยทั่วไปจะพิมพ์อาร์กิวเมนต์บรรทัดคำสั่งไปที่ stdout ดังนั้นสิ่งที่stuffพยายามที่จะพิมพ์ไป stdout ถูกจับส่งผ่านไปechoและพิมพ์ที่ stdout echoโดย

หากคุณต้องการส่งออกของstuffที่จะพิมพ์ที่ stdout เพียงแค่เรียกใช้ stuffแต่เพียงผู้เดียว

`…`ไวยากรณ์ให้บริการวัตถุประสงค์เช่นเดียวกับ$(…)(ภายใต้ชื่อเดียวกัน: "คำสั่งเปลี่ยนตัว") มีความแตกต่างไม่กี่แม้ว่าดังนั้นคุณจะไม่สามารถสุ่มสี่สุ่มห้าแลกเปลี่ยนพวกเขา ดูคำถามที่พบบ่อยนี้และคำถามนี้


ฉันควรหลีกเลี่ยงecho $(stuff)ไม่ว่าจะเกิดอะไรขึ้น?

มีเหตุผลที่คุณอาจต้องการใช้echo $(stuff)ถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ ด้วยเหตุผลเดียวกันกับที่คุณควรหลีกเลี่ยงecho $(stuff)หากคุณไม่รู้ว่าคุณกำลังทำอะไรอยู่

ประเด็นคือstuffและecho $(stuff)ไม่เทียบเท่า หมายถึงการเรียกผู้ประกอบการหลัง glob แยก + ในการส่งออกของมีค่าเริ่มต้นของstuff $IFSการอ้างถึงการทดแทนคำสั่งสองครั้งจะป้องกันสิ่งนี้ การอ้างคำสั่งเดียวเป็นการแทนคำสั่งทำให้ไม่เป็นการทดแทนคำสั่งอีกต่อไป

เมื่อต้องการสังเกตสิ่งนี้เมื่อถึงการแยกให้รันคำสั่งเหล่านี้:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

และสำหรับการโค้ง:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

ที่คุณสามารถดูecho "$(stuff)"เทียบเท่า (-ish *) stuffเพื่อ คุณสามารถใช้มันได้ แต่สิ่งที่ทำให้เกิดความซับซ้อนในลักษณะนี้คืออะไร?

ในทางกลับกันถ้าคุณต้องการให้เอาท์พุทของstuffการแบ่ง + กลมแล้วคุณอาจพบว่าecho $(stuff)มีประโยชน์ มันจะต้องมีการตัดสินใจอย่างมีสติแม้ว่า

มีคำสั่งที่สร้างผลลัพธ์ที่ควรได้รับการประเมิน (ซึ่งรวมถึงการแยก, การแยกและอื่น ๆ ) และดำเนินการโดยเชลล์ดังนั้นจึงeval "$(stuff)"เป็นไปได้ (ดูคำตอบนี้ ) ฉันไม่เคยเห็นคำสั่งที่จำเป็นต้องใช้เอาต์พุตเพื่อรับการแบ่ง + การวนซ้ำเพิ่มเติมก่อนที่จะพิมพ์ จงใจใช้echo $(stuff)ดูเหมือนว่าผิดปกติมาก


เกี่ยวกับvar=$(stuff); echo "$var"อะไร

จุดดี. ตัวอย่างนี้:

var=$(stuff)
echo "$var"

ควรจะเทียบเท่ากับecho "$(stuff)"เทียบเท่า (-ish *) stuffเพื่อ หากเป็นรหัสทั้งหมดให้เรียกใช้stuffแทน

อย่างไรก็ตามหากคุณจำเป็นต้องใช้เอาต์พุตstuffมากกว่าหนึ่งครั้งแล้ววิธีนี้

var=$(stuff)
foo "$var"
bar "$var"

มักจะดีกว่า

foo "$(stuff)"
bar "$(stuff)"

แม้ว่าfooจะเป็นechoและคุณได้รับecho "$var"ในรหัสของคุณมันอาจจะดีกว่าที่จะเก็บไว้ในลักษณะนี้ สิ่งที่ต้องพิจารณา:

  1. ด้วยการvar=$(stuff) stuffวิ่งครั้งเดียว; แม้ว่าคำสั่งจะเร็ว แต่การหลีกเลี่ยงการคำนวณผลลัพธ์เดียวกันสองครั้งเป็นสิ่งที่ถูกต้อง หรืออาจstuffมีเอฟเฟกต์อื่นนอกเหนือจากการเขียนไปยัง stdout (เช่นการสร้างไฟล์ชั่วคราวการเริ่มบริการการเริ่มต้นเครื่องเสมือนการแจ้งเตือนเซิร์ฟเวอร์ระยะไกล) ดังนั้นคุณไม่ต้องการเรียกใช้หลายครั้ง
  2. หากstuffสร้างเวลาขึ้นหรือการส่งออกค่อนข้างสุ่มคุณอาจได้รับผลลัพธ์ที่สอดคล้องกันจากและfoo "$(stuff)" bar "$(stuff)"หลังจากvar=$(stuff)ค่าของ$varได้รับการแก้ไขและคุณสามารถมั่นใจfoo "$var"และbar "$var"รับอาร์กิวเมนต์บรรทัดคำสั่งที่เหมือนกัน

ในบางกรณีแทนที่จะเป็นfoo "$var"คุณอาจต้องการ (ต้องการ) เพื่อใช้foo $varโดยเฉพาะถ้าstuffสร้างอาร์กิวเมนต์หลายตัวสำหรับfoo(ตัวแปรอาเรย์อาจจะดีกว่าถ้าเชลล์ของคุณสนับสนุน) รู้อีกครั้งว่าคุณกำลังทำอะไรอยู่ เมื่อมันมาถึงechoความแตกต่างระหว่างecho $varและecho "$var"เป็นเช่นเดียวกับระหว่างและecho $(stuff)echo "$(stuff)"


* เทียบเท่า ( -ish )?

ผมบอกว่าecho "$(stuff)"เทียบเท่า (-ish) stuffเพื่อ มีปัญหาอย่างน้อยสองเรื่องที่ทำให้ไม่เท่าเทียมกัน:

  1. $(stuff)วิ่งstuffใน subshell จึงดีกว่าที่จะพูดecho "$(stuff)"จะเทียบเท่า (-ish) (stuff)เพื่อ คำสั่งที่ส่งผลกระทบต่อเชลล์ที่พวกเขาทำงานหากอยู่ในเชลล์ย่อยจะไม่ส่งผลกระทบต่อเชลล์หลัก

    ในตัวอย่างนี้stuffคือa=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    เปรียบเทียบกับ

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    และด้วย

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    อีกตัวอย่างเริ่มต้นด้วยstuffการเป็นcd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    และการทดสอบstuffและ(stuff)รุ่น

  2. echoไม่ใช่เครื่องมือที่ดีในการแสดงข้อมูลที่ไม่สามารถควบคุมได้ นี้เราได้พูดคุยเกี่ยวกับควรจะได้รับecho "$var" printf '%s\n' "$var"แต่เนื่องจากคำถามที่กล่าวถึงechoและเนื่องจากวิธีแก้ปัญหาที่น่าจะเป็นไปได้มากที่สุดechoในตอนแรกฉันจึงตัดสินใจไม่แนะนำprintfจนถึงตอนนี้

  3. stuffหรือ(stuff)จะ interleave เอาต์พุต stdout และ stderr ขณะที่echo $(stuff)จะพิมพ์เอาต์พุต stderr ทั้งหมดstuff(ซึ่งรันก่อน) และจากนั้นเอาต์พุต stdout จะถูกย่อยโดยecho(ซึ่งจะทำงานครั้งสุดท้าย)

  4. $(…)ตัดการขึ้นบรรทัดใหม่ต่อท้ายจากนั้นechoเพิ่มกลับ ดังนั้นจะช่วยให้การส่งออกที่แตกต่างกว่าecho "$(printf %s 'a')" | xxdprintf %s 'a' | xxd

  5. บางคำสั่ง ( lsตัวอย่าง) ทำงานแตกต่างกันไปขึ้นอยู่กับว่าเอาต์พุตมาตรฐานเป็นคอนโซลหรือไม่ จึงls | catไม่เหมือนกันlsไม่ ในทำนองเดียวกันจะทำงานที่แตกต่างกว่าecho $(ls)ls

    ใส่lsกันในกรณีทั่วไปถ้าคุณมีการบังคับพฤติกรรมอื่น ๆ แล้วstuff | catจะดีกว่าecho $(ls)หรือecho "$(ls)"เพราะมันไม่ได้เรียกทุกประเด็นอื่น ๆ ที่กล่าวถึงที่นี่

  6. สถานะการออกที่แตกต่างกันอาจเป็นไปได้ (กล่าวถึงเพื่อความสมบูรณ์ของคำตอบ wiki นี้สำหรับรายละเอียดดูคำตอบอื่นที่สมควรได้รับเครดิต)


5
หนึ่งความแตกต่างมากขึ้นคือstuffวิธีที่จะแทรกstdoutและstderrการส่งออกในขณะที่echo `stuff` จะพิมพ์ทั้งหมดที่stderrส่งออกมาstuffและเพียงแล้วstdoutเอาท์พุท
Ruslan

3
และนี่เป็นอีกตัวอย่างสำหรับ: อาจมีการเสนอราคามากเกินไปในสคริปต์ทุบตี echo $(stuff)สามารถทำให้คุณมีปัญหามากขึ้นราวกับecho "$(stuff)"ว่าคุณไม่ต้องการโค้งและขยายอย่างชัดเจน
rexkogitans

2
ความแตกต่างเล็กน้อยอีกประการหนึ่ง: $(…)ตัดการขึ้นบรรทัดใหม่ใด ๆ แล้วechoเพิ่มกลับ ดังนั้นจะช่วยให้การส่งออกที่แตกต่างกว่าecho "$(printf %s 'a')" | xxd printf %s 'a' | xxd
Derobert

1
คุณไม่ควรลืมว่าบางคำสั่ง ( lsตัวอย่าง) ทำงานแตกต่างกันไปขึ้นอยู่กับว่าเอาต์พุตมาตรฐานเป็นคอนโซลหรือไม่ จึงls | catไม่เหมือนกันlsไม่ และแน่นอนจะทำงานที่แตกต่างกว่าecho $(ls) ls
Martin Rosenau

@Ruslan คำตอบคือตอนนี้ชุมชน wiki ฉันได้เพิ่มความคิดเห็นที่เป็นประโยชน์ของคุณไปที่วิกิ ขอขอบคุณ.
Kamil Maciorowski

5

ข้อแตกต่างอื่น: รหัสออกจากเปลือกย่อยหายไปดังนั้นรหัสทางออกของechoจะถูกดึงมาแทน

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0

3
ยินดีต้อนรับสู่ Super User! คุณสามารถอธิบายความแตกต่างที่คุณแสดงออกได้หรือไม่? ขอบคุณ
bertieb

4
ผมเชื่อว่านี้แสดงให้เห็นว่ารหัสที่ส่งกลับมา$?จะเป็นรหัสการกลับมาของecho(สำหรับecho $(stuff)) แทนหนึ่งเอ็ดreturn ฟังก์ชั่น(รหัสออก) แต่ชุดเป็น "0" โดยไม่คำนึงถึงรหัสการกลับมาของ ยังคงควรจะอยู่ในคำตอบ stuffstuffreturn 1echostuff
Ismael Miguel

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