ตัวแปรเชลล์มีขอบเขตอะไรบ้าง?


42

ฉันพบปัญหาที่แสดงให้ฉันเห็นว่าฉันไม่ชัดเจนเกี่ยวกับขอบเขตของตัวแปรเชลล์

ฉันพยายามใช้bundle installซึ่งเป็นคำสั่ง Ruby ที่ใช้ค่าของ$GEM_HOMEการทำงาน ผมได้ตั้ง$GEM_HOMEแต่คำสั่งละเว้นค่าที่จนกว่าฉันจะใช้ในขณะที่exportexport GEM_HOME=/some/path

ฉันอ่านว่าสิ่งนี้ทำให้ตัวแปร "โลก" อย่างใดอย่างหนึ่ง (หรือที่เรียกว่าตัวแปรสภาพแวดล้อม ) แต่ฉันไม่เข้าใจสิ่งที่หมายถึง ฉันรู้เกี่ยวกับ globals ในการเขียนโปรแกรม แต่ไม่ใช่ในโปรแกรมที่แตกต่าง

นอกจากนี้เนื่องจากการตั้งค่าของฉันตัวแปรดังกล่าวใช้เฉพาะกับเซสชันเชลล์ปัจจุบันฉันจะตั้งค่าให้พวกเขาสำหรับพูดกระบวนการ daemonized อย่างไร

ตัวแปรเชลล์มีขอบเขตอะไรบ้าง?

คำตอบ:


33

กระบวนการถูกจัดเป็นแผนผัง: ทุกกระบวนการมีพาเรนต์เฉพาะนอกเหนือจากinitที่PIDเป็น 1 เสมอและไม่มีพาเรนต์

โดยทั่วไปการสร้างกระบวนการใหม่จะดำเนินการผ่านการเรียกfork/ execvระบบคู่โดยที่สภาพแวดล้อมของกระบวนการลูกเป็นสำเนาของกระบวนการหลัก

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

พิจารณาว่ากระบวนการลูกสามารถเปลี่ยนแปลงสภาพแวดล้อมได้ตัวอย่างเช่นสามารถรีเซ็ตเป็นค่าเริ่มต้นได้loginเช่นกัน


1
Ah! ตกลงเรามาดูว่าฉันเข้าใจสิ่งนี้ไหม ในเปลือกถ้าฉันพูดFOO=barว่าจะตั้งค่าสำหรับกระบวนการเปลือกปัจจุบัน ถ้าฉันเรียกใช้โปรแกรมเช่น ( bundle install) นั่นจะเป็นการสร้างกระบวนการลูกซึ่งไม่สามารถเข้าถึงFOOได้ แต่ถ้าฉันบอกว่าexport FOO=barกระบวนการของเด็ก (และลูกหลานของมัน) จะสามารถเข้าถึงมันได้ หนึ่งในนั้นสามารถเรียกexport FOO=buzzเพื่อเปลี่ยนค่าสำหรับการสืบทอดหรือเพียงแค่FOO=buzzเปลี่ยนค่าสำหรับตัวเองเท่านั้น มันเกี่ยวกับใช่มั้ย
Nathan Long

2
@NathanLong นั่นไม่ใช่สิ่งที่แน่นอน: ในเชลล์สมัยใหม่ทั้งหมดตัวแปรจะถูกส่งออก (และดังนั้นการเปลี่ยนแปลงใด ๆ ของค่าจะถูกสะท้อนในสภาพแวดล้อมของผู้สืบทอด) หรือไม่ส่งออก (หมายถึงตัวแปรไม่ได้อยู่ในสภาพแวดล้อม) โดยเฉพาะถ้าตัวแปรนั้นอยู่ในสภาพแวดล้อมเมื่อเชลล์เริ่มทำงานมันจะถูกส่งออก
Gilles 'หยุดความชั่วร้าย'

2
ฉันรู้สึกสับสนเล็กน้อยกับประโยค "ถ้าเด็กเปลี่ยนค่าของตัวแปรค่าที่เปลี่ยนแปลงจะเห็นได้เฉพาะมันและกระบวนการทั้งหมดที่สร้างขึ้นหลังจากการเปลี่ยนแปลงนั้น" มันจะถูกต้องมากขึ้นถ้าจะพูดว่า "... มองเห็นได้และกระบวนการสืบทอดทั้งหมดที่สร้างขึ้นหลังจากการเปลี่ยนแปลงนั้น" - ลูกคนอื่น ๆ ของกระบวนการหลักแม้กระทั่งกระบวนการที่เริ่มต้นหลังจากกระบวนการลูกไม่ได้รับผลกระทบ
Jaan

26

อย่างน้อยภายใต้kshและbashตัวแปรสามารถมีสามขอบเขตไม่ใช่สองแบบเหมือนคำตอบที่เหลือทั้งหมดกำลังบอกอยู่

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

ตัวแปรที่ประกาศในฟังก์ชั่นเชลล์พร้อมtypesetโทเค็นจะสามารถมองเห็นได้ภายในฟังก์ชั่นที่มีการประกาศในและฟังก์ชั่น (ย่อย) ที่เรียกจากที่นั่น

นี้ksh/ bashรหัส:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

สร้างผลลัพธ์นี้:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

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

โปรดทราบว่าพฤติกรรมหลังนี้ค่อนข้างแตกต่างจาก Windows หนึ่งซึ่งคุณสามารถใช้ตัวแปรระบบซึ่งเป็นแบบโกลบอลและแบ่งใช้โดยกระบวนการทั้งหมด


12

มีการกำหนดขอบเขตโดยกระบวนการ

answerers อื่น ๆ ที่ช่วยให้ผมเข้าใจว่าขอบเขตตัวแปรเปลือกเป็นเรื่องเกี่ยวกับกระบวนการและลูกหลานของพวกเขา

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

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

สมมติว่าคุณมี bash shell ซึ่งเราจะเรียกว่า A คุณพิมพ์bashซึ่งจะสร้าง child child bash shell ซึ่งเราจะเรียก B สิ่งที่คุณเรียกexportใน A จะยังคงถูกตั้งค่าใน B

ขณะนี้ใน B FOO=bคุณพูด หนึ่งในสองสิ่งจะเกิดขึ้น:

  • ถ้า B ไม่ได้รับ (จาก A) ตัวแปรสภาพแวดล้อมที่เรียกว่าFOOมันจะสร้างตัวแปรท้องถิ่น เด็ก ๆ ของ B จะไม่ได้รับมัน (ยกเว้นว่าจะโทร B export)
  • ถ้า B ไม่ได้รับ (จาก A) ตัวแปรสภาพแวดล้อม callled FOOก็จะแก้ไขได้สำหรับตัวเองและลูก ๆ ของมันคดเคี้ยวภายหลัง Children of B จะเห็นค่าที่ B กำหนดไว้ อย่างไรก็ตามสิ่งนี้จะไม่ส่งผลกระทบต่อ A เลย

นี่คือตัวอย่างรวดเร็ว

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

ทั้งหมดนี้อธิบายถึงปัญหาดั้งเดิมของฉัน: ฉันตั้งค่าGEM_HOMEในเปลือกของฉัน แต่เมื่อฉันเรียกว่าbundle installที่สร้างกระบวนการเด็ก เพราะผมไม่ได้ใช้กระบวนการเด็กไม่ได้รับของเปลือกexportGEM_HOME

ยกเลิกการส่งออก

คุณ "ยกเลิกการส่งออก" ตัวแปรสามารถ - ป้องกันไม่ให้มันจากการถูกส่งผ่านไปยังเด็ก - export -n FOOโดยใช้

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable

1
เมื่อคุณพูดว่า "มันจะแก้ไขมันสำหรับตัวเองและลูก ๆ ของมัน" คุณควรชี้แจงว่าเด็ก ๆ ที่สร้างขึ้นหลังจากการปรับเปลี่ยนเท่านั้นที่จะเห็นค่าที่ปรับเปลี่ยน
enzotib

1
@enzotib - จุดดี Updated
Nathan Long

3

คำอธิบายที่ดีที่สุดที่ฉันสามารถหาได้เกี่ยวกับการส่งออกคือ:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

ตัวแปรที่ตั้งค่าภายใน subshell หรือ child shell นั้นจะเห็นได้เฉพาะ subshell ที่มันถูกกำหนดไว้ ตัวแปรที่ส่งออกถูกสร้างขึ้นมาเพื่อเป็นตัวแปรสภาพแวดล้อม ดังนั้นเพื่อล้างของคุณbundle installดำเนินการเชลล์ของตัวเองซึ่งไม่เห็น$GEM_HOMEเว้นแต่จะทำให้environmentตัวแปร aka ส่งออก

คุณสามารถดูเอกสารประกอบสำหรับขอบเขตตัวแปรได้ที่นี่:

http://www.tldp.org/LDP/abs/html/subshells.html


อาดังนั้นฉันไม่ถูกต้องที่จะใช้คำว่า "ตัวแปรสภาพแวดล้อม" สำหรับFOO=bar; คุณต้องใช้exportเพื่อทำให้เป็นหนึ่ง คำถามถูกแก้ไขตาม
Nathan Long

ลองดูที่ลิงก์ที่ฉันเพิ่มเข้าไป
Karlson

3

มีลำดับชั้นของขอบเขตตัวแปรตามที่คาดไว้

สิ่งแวดล้อม

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

ตัวแปรเชลล์

เชลล์มีแนวคิดเกี่ยวกับตัวแปรของตัวเอง นี่คือสิ่งที่เริ่มจะสับสนเล็กน้อย

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

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

ตัวแปรท้องถิ่น

ตัวแปรโลคัลคือตัวแปรเชลล์ที่กำหนดขอบเขตไว้ที่บล็อกโค้ดที่มี คุณประกาศตัวแปรท้องถิ่นด้วยtypesetคำหลัก (พกพา) หรือlocalหรือdeclare(ทุบตี) เช่นเดียวกับตัวแปรเชลล์อื่น ๆ ตัวแปรโลคัลไม่ได้รับการสืบทอดโดยกระบวนการลูก นอกจากนี้ตัวแปรท้องถิ่นไม่สามารถส่งออกได้

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