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


91

พิจารณาตัวอย่างนี้:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

นี่ฉันได้ตั้ง$SOMEVARไปAAAในบรรทัดแรก - และเมื่อฉันสะท้อนมันในบรรทัดที่สองผมได้รับAAAเนื้อหาตามที่คาดไว้

แต่ถ้าฉันพยายามระบุตัวแปรในบรรทัดคำสั่งเดียวกันกับecho:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... ฉันไม่ได้BBBตามที่ฉันคาดไว้ - ฉันได้รับค่าเก่า ( AAA)

นี่คือสิ่งที่ควรจะเป็น? ถ้าเป็นเช่นนั้นคุณสามารถระบุตัวแปรที่ชอบLD_PRELOAD=/... program args ...และใช้งานได้อย่างไร? ฉันขาดอะไรไป?


2
ใช้งานได้เมื่อคุณกำหนดคำสั่งแยกต่างหากหรือเมื่อเรียกใช้สคริปต์ที่มีสภาพแวดล้อมของตัวเอง แต่จะไม่ทำงานเมื่อมีคำสั่งล่วงหน้าในสภาพแวดล้อมปัจจุบัน น่าสนใจ!
Todd A.Jacobs

1
เหตุผลที่LD_PRELOADการทำงานคือตัวแปรที่มีการตั้งค่าในโปรแกรมของสภาพแวดล้อม - ไม่ได้ในบรรทัดคำสั่งของมัน
Dennis Williamson

คำตอบ:


103

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

ตัวเลือกทันทีของคุณ ได้แก่ :

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

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

อีกทางเลือกหนึ่งคือใช้สัญกรณ์ sub-shell (ตามคำแนะนำของMarcus Kuhnในคำตอบของเขา):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

ตัวแปรถูกตั้งค่าเฉพาะในเชลล์ย่อย


น่ากลัว @JonathanLeffler - ขอบคุณมากสำหรับคำอธิบาย ไชโย!
sdaau

การเพิ่มจาก @ markus-kuhn นั้นยากที่จะประเมินค่าสูงเกินไป
Alex Che

37

ปัญหามาเยือนแล้ว

ค่อนข้างตรงไปตรงมาคู่มือมีความสับสนในประเด็นนี้ คู่มือ GNU ทุบตีพูดว่า:

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

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

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

เนื่องจากสภาพแวดล้อมสำหรับคำสั่ง env ถูกแก้ไขก่อนที่จะดำเนินการ อย่างไรก็ตามสิ่งนี้จะไม่ทำงาน:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

เนื่องจากเมื่อทำการขยายพารามิเตอร์โดยเชลล์

ขั้นตอนล่าม

อีกส่วนหนึ่งของปัญหาคือ Bash กำหนดขั้นตอนเหล่านี้สำหรับล่าม:

  1. อ่านอินพุตจากไฟล์ (ดู Shell Scripts) จากสตริงที่ให้มาเป็นอาร์กิวเมนต์ไปยังอ็อพชันการเรียกใช้ -c (ดูที่การเรียกใช้ Bash) หรือจากเทอร์มินัลของผู้ใช้
  2. แบ่งการป้อนข้อมูลเป็นคำและตัวดำเนินการโดยปฏิบัติตามกฎการอ้างอิงที่อธิบายไว้ในการอ้างอิง โทเค็นเหล่านี้ถูกคั่นด้วยอักขระเมตา ขั้นตอนนี้ดำเนินการขยายนามแฝง (ดูนามแฝง)
  3. แยกวิเคราะห์โทเค็นเป็นคำสั่งแบบง่ายและแบบผสม (ดูคำสั่งเชลล์)
  4. ดำเนินการขยายเชลล์ต่างๆ (ดูที่การขยายเชลล์) โดยแยกโทเค็นที่ขยายออกเป็นรายการชื่อไฟล์ (ดูการขยายชื่อไฟล์) และคำสั่งและอาร์กิวเมนต์
  5. ดำเนินการเปลี่ยนเส้นทางที่จำเป็น (ดูการเปลี่ยนเส้นทาง) และลบตัวดำเนินการเปลี่ยนเส้นทางและตัวถูกดำเนินการออกจากรายการอาร์กิวเมนต์
  6. ดำเนินการคำสั่ง (ดูที่การดำเนินการคำสั่ง)
  7. เลือกที่จะรอให้คำสั่งดำเนินการเสร็จสิ้นและรวบรวมสถานะการออก (ดูสถานะการออก)

สิ่งที่เกิดขึ้นที่นี่คือบิวด์อินไม่ได้รับสภาพแวดล้อมการดำเนินการของตนเองดังนั้นจึงไม่เห็นสภาพแวดล้อมที่แก้ไข นอกจากนี้คำสั่งง่ายๆ (เช่น / bin / echo) จะได้รับสภาพแวดล้อมที่ถูกปรับเปลี่ยน (ซึ่งเป็นสาเหตุที่ทำให้ตัวอย่าง env ใช้งานได้) แต่การขยายเชลล์เกิดขึ้นในสภาพแวดล้อมปัจจุบันในขั้นตอนที่ 4

กล่าวอีกนัยหนึ่งคุณไม่ได้ส่ง 'aaa $ TESTVAR ccc' ไปยัง / bin / echo; คุณกำลังส่งสตริง interpolated (ตามที่ขยายในสภาพแวดล้อมปัจจุบัน) ไปยัง / bin / echo ในกรณีนี้เนื่องจากสภาพแวดล้อมปัจจุบันไม่มีTESTVARคุณเพียงแค่ส่ง 'aaa ccc' ไปยังคำสั่ง

สรุป

เอกสารอาจชัดเจนกว่านี้มาก สิ่งที่ดีมี Stack Overflow!

ดูสิ่งนี้ด้วย

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment


ฉันได้เพิ่มคะแนนสิ่งนี้แล้ว - แต่ฉันเพิ่งกลับมาที่คำถามนี้และโพสต์นี้มีคำแนะนำที่ฉันต้องการ ขอบคุณมาก @CodeGnome!
sdaau

ฉันไม่รู้ว่า Bash มีการเปลี่ยนแปลงในพื้นที่นี้หรือไม่เนื่องจากคำตอบนี้ถูกโพสต์ แต่ตอนนี้การกำหนดตัวแปรที่มีคำนำหน้าจะใช้งานได้กับบิวด์อิน ตัวอย่างเช่นFOO=foo eval 'echo $FOO'พิมพ์fooตามที่คาดไว้ IFS="..." read ...ซึ่งหมายความว่าคุณสามารถทำสิ่งที่ชอบ
Will Vousden

ฉันคิดว่าสิ่งที่เกิดขึ้นคือ Bash ปรับเปลี่ยนสภาพแวดล้อมของตัวเองชั่วคราวและเรียกคืนเมื่อคำสั่งเสร็จสิ้นซึ่งอาจมีผลข้างเคียงแปลก ๆ
Will Vousden

22

เพื่อให้บรรลุสิ่งที่คุณต้องการใช้

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

เหตุผล:

  • คุณต้องแยกการกำหนดด้วยเครื่องหมายอัฒภาคหรือบรรทัดใหม่จากคำสั่งถัดไปมิฉะนั้นจะไม่ถูกดำเนินการก่อนที่การขยายพารามิเตอร์จะเกิดขึ้นสำหรับคำสั่งถัดไป (echo)

  • คุณต้องทำการมอบหมายภายในสภาพแวดล้อมsubshellเพื่อให้แน่ใจว่าจะไม่อยู่เกินบรรทัดปัจจุบัน

วิธีการแก้ปัญหานี้สั้นกว่าและมีประสิทธิภาพมากกว่าวิธีอื่น ๆ ที่แนะนำโดยเฉพาะอย่างยิ่งไม่ได้สร้างกระบวนการใหม่


3
สำหรับ Googler ในอนาคตที่จบลงที่นี่: นี่อาจเป็นคำตอบที่ดีที่สุดสำหรับคำถามนี้ หากต้องการทำให้ซับซ้อนยิ่งขึ้นหากคุณต้องการให้การมอบหมายพร้อมใช้งานในสภาพแวดล้อมของคำสั่งคุณต้องส่งออก subshell ยังคงป้องกันไม่ให้งานยังคงอยู่ (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")

@eaj ในการเอ็กซ์พอร์ตตัวแปรเชลล์ไปยังการเรียกโปรแกรมภายนอกรายการเดียวดังในตัวอย่างของคุณเพียงแค่ใช้SOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn

10

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

หากต้องการดูเอฟเฟกต์คุณสามารถทำสิ่งต่อไปนี้

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

ที่นี่ตัวแปรจะไม่ขยายจนกว่ากระบวนการย่อยจะดำเนินการดังนั้นคุณจะเห็นค่าที่อัปเดต หากคุณตรวจสอบSOME_VARIABLEอีกครั้งในพาเรนต์เชลล์ก็ยังคงAAAเป็นไปตามที่คาดไว้


1
+1 สำหรับคำอธิบายที่ถูกต้องว่าเหตุใดจึงไม่ทำงานตามที่เขียนไว้และสำหรับวิธีแก้ปัญหาที่ได้ผล
Jonathan Leffler

1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

ใช้ ; เพื่อแยกคำสั่งที่อยู่ในบรรทัดเดียวกัน


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

ขอบคุณที่ @Kyros; ไม่รู้ว่าตอนนี้ฉันพลาดไปได้อย่างไร :) ยังคงหลงทางLD_PRELOADและวิธีการดังกล่าวสามารถทำงานได้โดยไม่มีเครื่องหมายอัฒภาค แต่ ... ขอบคุณมากอีกครั้ง - ไชโย!
sdaau

@JonathanLeffler - นั่นคือความคิด; ฉันไม่ทราบว่าอัฒภาคทำให้การเปลี่ยนแปลงถาวร - ขอบคุณที่สังเกตว่า!
sdaau

1

นี่คือทางเลือกหนึ่ง:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

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