การเปลี่ยนเส้นทางไฟล์ bash เป็นมาตรฐานแตกต่างจาก shell (`sh`) บน Linux อย่างไร


9

ฉันเขียนสคริปต์ที่สลับผู้ใช้ในขณะที่ทำงานและดำเนินการโดยใช้การเปลี่ยนเส้นทางไฟล์ไปที่มาตรฐานระบบดังนั้นจึงuser-switch.shเป็น ...

#!/bin/bash

whoami
sudo su -l root
whoami

และการวิ่งด้วยbashทำให้ฉันมีพฤติกรรมที่ฉันคาดหวัง

$ bash < user-switch.sh
vagrant
root

อย่างไรก็ตามถ้าฉันรันสคริปต์ด้วยshฉันจะได้ผลลัพธ์ที่ต่างออกไป

$ sh < user-switch.sh 
vagrant
vagrant

ทำไมจึงbash < user-switch.shให้ผลลัพธ์ที่แตกต่างกว่าsh < user-switch.sh?

หมายเหตุ:

  • เกิดขึ้นในสองกล่องที่แตกต่างกันที่ใช้ Debian Jessie

1
ไม่ใช่คำตอบ แต่เกี่ยวข้องกับsudo su: unix.stackexchange.com/questions/218169/…
Kusalananda

1
is / bin / sh = dash บนเดเบียนไหม ฉันไม่สามารถพูดซ้ำใน RHEL โดยที่ / bin / sh = bash
Jeff Schaller

1
/bin/shเปิด (สำเนาของฉัน) Debian คือ Dash ใช่ ฉันไม่รู้ด้วยซ้ำจนกระทั่งตอนนี้ว่า ฉันติดอยู่กับทุบตีจนถึงขณะนี้
popedotninja

1
ทุบตีพฤติกรรมไม่รับประกันว่าจะทำงานโดยความหมายเอกสารใดใด
Charles Duffy

มีคนบางคนชี้ให้ฉันเห็นในวันนี้ว่าสคริปต์ที่ฉันเรียกใช้นั้นละเมิดความหมายของวิธีการใช้ทุบตี มีคำตอบที่ดีสำหรับคำถามนี้สองสามข้อ แต่ก็คุ้มค่าที่จะชี้ให้เห็นว่าอาจไม่ควรสลับผู้ใช้กลางสคริปต์ ตอนแรกฉันพยายามuser-switch.shทำงานเจนกินส์และสคริปต์ก็ดำเนินการ การรันสคริปต์เดียวกันนอกเจนกินส์นั้นให้ผลลัพธ์ที่แตกต่างกันและฉันก็หันไปใช้การเปลี่ยนเส้นทางไฟล์เพื่อรับพฤติกรรมที่ฉันเห็นในเจนกินส์
popedotninja

คำตอบ:


12

สคริปต์ที่คล้ายกันโดยไม่มีsudoแต่ผลลัพธ์ที่คล้ายกัน:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

ด้วยbashส่วนที่เหลือของสคริปต์ไปเป็นข้อมูลป้อนเข้าsedด้วยdashเชลล์ตีความมัน

ทำงานstraceกับสิ่งเหล่านั้น: dashอ่านบล็อกของสคริปต์ (แปด kB ที่นี่มากกว่าพอที่จะเก็บสคริปต์ทั้งหมด) แล้ววางไข่sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

ซึ่งหมายความว่า filehandle อยู่ท้ายไฟล์และsedจะไม่เห็นอินพุตใด ๆ dashส่วนที่เหลือถูกบัฟเฟอร์ภายใน (หากสคริปต์ยาวกว่าขนาดบล็อก 8 kB ส่วนที่เหลือจะถูกอ่านด้วยsed)

ในทางกลับกัน Bash จะค้นหากลับไปที่ส่วนท้ายของคำสั่งสุดท้าย:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

หากอินพุตมาจากไพพ์เช่นเดียวกับที่นี่:

$ cat script.sh | bash

ไม่สามารถกรอกลับได้เนื่องจากท่อและซ็อกเก็ตหาไม่ได้ ในกรณีนี้ Bash จะกลับไปอ่านอินพุตทีละตัวเพื่อหลีกเลี่ยงการโอเวอร์เฮด ( fd_to_buffered_stream()ในinput.c ) ทำการโทรแบบเต็มระบบสำหรับแต่ละไบต์ไม่ได้มีประสิทธิภาพมากในหลักการ ในทางปฏิบัติฉันไม่คิดว่าการอ่านจะเป็นค่าใช้จ่ายที่ดีเมื่อเทียบกับความจริงที่ว่าสิ่งที่เชลล์ส่วนใหญ่เกี่ยวข้องกับการวางไข่กระบวนการใหม่ทั้งหมด

สถานการณ์คล้ายกันคือ:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

เชลล์ย่อยต้องตรวจสอบให้แน่ใจว่าreadอ่านขึ้นบรรทัดใหม่แรกเท่านั้นเพื่อให้headเห็นบรรทัดถัดไป (ใช้ได้กับdashด้วย)


กล่าวอีกนัยหนึ่งทุบตีไปที่ความยาวเพิ่มเติมเพื่อรองรับการอ่านแหล่งเดียวกันสำหรับสคริปต์ตัวเองและสำหรับคำสั่งดำเนินการจากมัน dashไม่ zshและksh93บรรจุใน Debian ไปกับทุบตีเกี่ยวกับเรื่องนี้


1
ตรงไปตรงมาฉันประหลาดใจเล็กน้อยที่ได้ผล
ilkkachu

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

2
ใช่ปฏิกิริยาของฉันในการอ่านคำถามนั้นน่าประหลาดใจที่มันใช้งานได้กับทุบตี - ฉันให้มันไปเพราะบางสิ่งที่ไม่ได้ผลแน่นอน ผมคิดว่าผมเป็นเพียงครึ่งขวา :)
ฮอบส์

12

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

วิธีที่เชลล์ทำงานคือพวกเขาอ่านกลุ่มซอร์สโค้ดแยกวิเคราะห์และหากพบคำสั่งทั้งหมดให้รันคำสั่งจากนั้นดำเนินการกับส่วนที่เหลือของ chunk และส่วนที่เหลือของไฟล์ หาก chunk ไม่มีคำสั่งที่สมบูรณ์ (ด้วยอักขระสิ้นสุดในตอนท้าย - ฉันคิดว่า shell ทั้งหมดอ่านจนสุดบรรทัด) เชลล์อ่านอีกอันหนึ่งและอื่น ๆ

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

Bash พยายามไปยังจุดสิ้นสุดของซอร์สโค้ดของคำสั่งในสคริปต์ก่อนดำเนินการคำสั่ง นี่ไม่ใช่สิ่งที่คุณสามารถไว้ใจได้ไม่ใช่เพียงเพราะเชลล์ตัวอื่นไม่ได้ทำมัน แต่เพราะมันใช้งานได้ก็ต่อเมื่อเชลล์อ่านจากไฟล์ปกติ หากเชลล์อ่านจากไพพ์ (เช่นssh remote-host.example.com <local-script-file.sh) ข้อมูลที่อ่านจะถูกอ่านและไม่สามารถอ่านได้

หากคุณต้องการที่จะผ่านเข้ากับคำสั่งในสคริปต์ที่คุณจะต้องทำเช่นนั้นอย่างชัดเจนโดยทั่วไปแล้วจะมีเอกสารที่นี่ (โดยทั่วไปแล้วเอกสารที่นี่จะสะดวกที่สุดสำหรับอินพุตหลายบรรทัด แต่วิธีการใด ๆ จะทำได้) โค้ดที่คุณเขียนจะใช้งานได้ในเชลล์เพียงไม่กี่ตัวเท่านั้นหากสคริปต์ถูกส่งเป็นอินพุตไปยังเชลล์จากไฟล์ปกติ หากคุณคาดว่าwhoamiจะส่งผ่านวินาทีเป็นอินพุตให้sudo …คิดอีกครั้งโปรดจำไว้ว่าเวลาส่วนใหญ่สคริปต์จะไม่ถูกส่งไปยังอินพุตมาตรฐานของเชลล์

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

sudo -i rootโปรดทราบว่าทศวรรษนี้คุณสามารถใช้ การวิ่งsudo suเป็นแฮ็คจากอดีต

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