ทำไมฉันไม่สามารถพิมพ์ตัวแปรที่ฉันเห็นได้ในผลลัพธ์ของ env


9

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

ผมสองกระบอกเปลือกหอย A และ B (PID 420) zshทั้งการทำงาน จาก AI เชลล์วิ่งต่อไปนี้

sudo gdb -p 420
(gdb) call setenv("FOO", "bar", 1)
(gdb) detach

จาก shell B เมื่อฉันรันenvฉันเห็นตัวแปร FOO ถูกตั้งค่าด้วยค่าบาร์ นี่ทำให้ฉันคิดว่า FOO ได้รับการเริ่มต้นได้สำเร็จในสภาพแวดล้อมของ shell B อย่างไรก็ตามถ้าฉันพยายามพิมพ์ FOO ฉันจะได้รับบรรทัดว่างซึ่งหมายความว่าไม่ได้ตั้งค่าไว้ สำหรับฉันมันรู้สึกเหมือนมีความขัดแย้งที่นี่

สิ่งนี้ได้รับการทดสอบทั้งในระบบ Arch GNU / Linux ของฉันและ Ubuntu VM ฉันยังทดสอบสิ่งนี้bashว่าตัวแปรไม่ได้แสดงใน env แม้ว่ามันจะน่าผิดหวังสำหรับฉัน แต่ก็สมเหตุสมผลถ้าเชลล์เก็บสำเนาของสภาพแวดล้อมในเวลาที่วางไข่และใช้สิ่งนั้นเท่านั้น (ซึ่งถูกเสนอให้กับหนึ่งในคำถามที่เชื่อมโยง) นี่ยังไม่ตอบว่าทำไมถึงzshเห็นตัวแปร

ทำไมการส่งออกของecho $FOOว่างเปล่า


แก้ไข

หลังจากป้อนข้อมูลในความคิดเห็นฉันตัดสินใจทำการทดสอบอีกเล็กน้อย ผลลัพธ์สามารถดูได้ในตารางด้านล่าง ในคอลัมน์แรกคือเชลล์ที่FOOตัวแปรถูกฉีดเข้าไป แถวแรกมีคำสั่งที่สามารถเห็นเอาต์พุตได้ด้านล่าง ตัวแปรFOOถูกฉีดโดยใช้: sudo gdb -p 420 -batch -ex 'call setenv("FOO", "bar", 1)'. คำสั่งเฉพาะสำหรับ zsh: zsh -c '...'ถูกทดสอบโดยใช้ bash ผลลัพธ์ที่ได้เหมือนกันเอาท์พุทของพวกเขาถูกละเว้นสำหรับความกะทัดรัด

Arch GNU / Linux, zsh 5.3.1, ทุบตี 4.4.12 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

Ubuntu 16.04.2 LTS, zsh 5.1.1, ทุบตี 4.3.48 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

ด้านบนดูเหมือนว่าจะบ่งบอกว่าผลลัพธ์ที่มีการกระจายไม่เชื่อเรื่องพระเจ้า สิ่งนี้ไม่ได้บอกอะไรมากไปกว่าzshและbashจัดการกับการตั้งค่าตัวแปรต่างกัน นอกจากนี้export FOOยังมีพฤติกรรมที่แตกต่างกันมากในบริบทนี้ขึ้นอยู่กับเชลล์ หวังว่าการทดสอบเหล่านี้จะทำให้คนอื่นเห็นได้ชัดเจน


จะเกิดอะไรขึ้นถ้าคุณทำzsh -c 'echo $FOO'(ใช้เครื่องหมายคำพูดเดี่ยว!) แทน? คุณเห็นมันไหม?
user1934428

ค่าที่ถูกต้องจะถูกพิมพ์จากเชลล์ย่อยใหม่ (ทดสอบสำหรับ bash child ด้วย) เห็นได้ชัดว่าสภาพแวดล้อมยังคงอยู่อย่างใดอย่างหนึ่งในขณะที่เด็กสามารถสืบทอด แต่ทำไมผู้ปกครองไม่ให้เกียรติมัน?
rlf

3
นั่นคือสิ่งที่ฉันคิดว่า. ฉันเดาว่าเชลล์มีตารางสัญลักษณ์ของตัวแปรบางแห่งบางอันถูกทำเครื่องหมายเป็น "เอ็กซ์พอร์ต" ซึ่งหมายความว่าเมื่อเปิดเชลล์ย่อยพวกเขาจะถูกวางไว้ในสภาพแวดล้อมของกระบวนการลูก เริ่มต้น (เมื่อเชลล์เริ่มต้น) ตัวแปรจากสภาพแวดล้อมในเวลานั้นจะถูกคัดลอกลงในตารางสัญลักษณ์ (แน่นอนว่าเป็นตัวแปร "ส่งออก" ด้วย) เมื่อคุณเปลี่ยนสภาพแวดล้อมเชลล์จะไม่ได้รับแจ้งให้อัพเดตตารางสัญลักษณ์ แต่กระบวนการลูก (เช่นenv) จะเห็นสภาพแวดล้อมที่แก้ไข
user1934428

2
ฉันทดสอบบน Ubuntu 16.04 ด้วย zsh 5.1.1 และ bash 4.3.48 (1) และดูเหมือนว่าการตั้งค่าตัวแปรสภาพแวดล้อมสำหรับzshใน GDB ไม่ทำให้มองเห็นได้เป็นตัวแปรเชลล์ แต่ทำให้ส่งผ่านไปยังกระบวนการลูก (เช่น คุณได้สังเกต) ในขณะที่การตั้งค่าหนึ่งสำหรับbash ไม่ให้มองเห็นเป็นตัวแปรเปลือก แต่ไม่ได้ทำให้มันจะถูกส่งผ่านไปยังกระบวนการที่เด็ก! ดูเหมือนว่า zsh และ bash ใช้กลยุทธ์ที่แตกต่างกันสำหรับการจัดการตัวแปรด้วยการติดตาม zsh ตัวแปรที่ไม่ใช่สภาพแวดล้อมและทุบตีเก็บทุกอย่างในสภาพแวดล้อมที่มันฆ่าเชื้อเมื่อเปิดตัวเด็ก (ไม่ใช่ subshell)
Eliah Kagan

@EliahKagan น่าสนใจ; คุณควรโพสต์สิ่งนั้นเป็นคำตอบ ฉันยังสงสัยว่ามันจะสร้างความแตกต่างถ้าคุณทำงานexport FOOในbash?
ไวด์การ์ด

คำตอบ:


2

กระสุนส่วนใหญ่ไม่ใช้getenv()/ setenv()/ putenv()API

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

ในทำนองเดียวกันและด้วยเหตุผลที่ว่าพวกเขาจะไม่ใช้execlp(), execvp()เพื่อรันคำสั่ง แต่โทรexecve()สายระบบโดยตรงคำนวณenvp[]อาร์เรย์ขึ้นอยู่กับรายชื่อของตัวแปรที่ส่งออกของพวกเขา

ดังนั้นในของgdbคุณคุณจะต้องเพิ่มรายการลงในตารางตัวแปรภายในของ shells หรืออาจเรียกฟังก์ชันที่ถูกต้องที่จะทำให้มันตีความexport VAR=valueรหัสเพื่อให้มันอัพเดตตารางด้วยตัวเอง

ว่าทำไมคุณเห็นความแตกต่างระหว่างbashและzshเมื่อคุณโทรsetenv()ในgdbฉันสงสัยว่าเพราะคุณกำลังเรียกsetenv()ก่อน initialises main()เปลือกเช่นเมื่อเข้าสู่

คุณจะสังเกตเห็นbashว่าmain()เป็นint main(int argc, char* argv[], char* envp[])(และbashจับคู่ตัวแปรจาก env vars ในenvp[]) ในขณะที่zshเป็นint main(int argc, char* argv[])และzshรับตัวแปรจากenvironแทน setenv()ทำการแก้ไขenvironแต่ไม่สามารถแก้ไขenvp[]แบบแทนที่ได้ (อ่านอย่างเดียวในหลายระบบรวมถึงสตริงที่ตัวชี้เหล่านั้นชี้ไป)

ไม่ว่าในกรณีใดหลังจากเชลล์อ่านenvironเมื่อเริ่มทำงานการใช้setenv()จะไม่ได้ผลเนื่องจากเชลล์ไม่ได้ใช้environ(หรือgetenv()) อีกต่อไป

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