ขอบเขตของตัวแปรท้องถิ่นในฟังก์ชั่นเชลล์


28

หลังจากอ่าน24.2 ตัวแปรท้องถิ่นผมคิดว่าการประกาศตัวแปรvarด้วยคำหลักlocalนั่นหมายความว่าvarค่า 's สามารถเข้าถึงได้เฉพาะภายในบล็อกของรหัสที่คั่นด้วยวงเล็บปีกกาของฟังก์ชั่น

อย่างไรก็ตามหลังจากรันตัวอย่างต่อไปนี้ฉันพบว่าvarสามารถเข้าถึงได้อ่านและเขียนจากฟังก์ชั่นที่เรียกใช้โดยบล็อกของรหัสนั้น - เช่นแม้ว่าจะvarถูกประกาศlocalไปouterFuncแล้วinnerFuncก็ยังสามารถอ่านและแก้ไขค่าได้

Run It Online

#!/usr/bin/env bash

function innerFunc() {
    var='new value'
    echo "innerFunc:                   [var:${var}]"
}

function outerFunc() {
    local var='initial value'

    echo "outerFunc: before innerFunc: [var:${var}]"
    innerFunc
    echo "outerFunc: after  innerFunc: [var:${var}]"
}

echo "global:    before outerFunc: [var:${var}]"
outerFunc
echo "global:    after  outerFunc: [var:${var}]"

เอาท์พุท:

global:    before outerFunc: [var:]               # as expected, `var` is not accessible outside of `outerFunc`
outerFunc: before innerFunc: [var:initial value]
innerFunc:                   [var:new value]      # `innerFunc` has access to `var` ??
outerFunc: after  innerFunc: [var:new value]      # the modification of `var` by `innerFunc` is visible to `outerFunc` ??
global:    after  outerFunc: [var:]

ถาม: นั่นเป็นจุดบกพร่องในเชลล์ของฉัน (bash 4.3.42, Ubuntu 16.04, 64 บิต) หรือเป็นพฤติกรรมที่คาดหวังหรือไม่?

แก้ไข:แก้ไขแล้ว ตามที่ระบุไว้โดย @MarkPlotnick นี่เป็นพฤติกรรมที่คาดหวัง


มันเป็นพฤติกรรมที่คาดหวัง
fpmurphy

2
ฉันเป็นคนเดียวที่คิดว่ามันแปลกที่ในบรรทัดสุดท้ายของผลลัพธ์ที่varว่างเปล่า? varมีการตั้งค่าทั่วโลกในinnerFuncดังนั้นทำไมมันไม่ติดจนกว่าจะสิ้นสุดของสคริปต์?
Harold Fischer

คำตอบ:


22

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

มีข้อยกเว้นสองประการ:

  1. ใน ksh93 ถ้าฟังก์ชั่นถูกกำหนดด้วยfunction_name () { … }ไวยากรณ์มาตรฐานแล้วตัวแปรท้องถิ่นของมันจะปฏิบัติตามการกำหนดขอบเขตแบบไดนามิก แต่ถ้าฟังก์ชั่นถูกกำหนดด้วยไวยากรณ์ ksh function function_name { … }ดังนั้นตัวแปรท้องถิ่นของมันจะเชื่อฟังการกำหนดขอบเขต / คำศัพท์แบบคงที่ดังนั้นจึงไม่สามารถมองเห็นได้ในฟังก์ชั่นอื่น ๆ ที่ถูกเรียกโดยสิ่งนี้

  2. zsh/privateปลั๊กอิน autoloadable ในzshให้กับprivateคำหลัก / builtin ซึ่งสามารถใช้ในการประกาศตัวแปรที่มีขอบเขตคงที่

Ash, ทุบตี, pdksh และอนุพันธ์, bosh มีขอบเขตแบบไดนามิกเท่านั้น


ตัวแปรทั้งหมดในเชลล์มีขอบเขตแบบไดนามิกหรือใช้กับตัวแปรที่ประกาศด้วยlocalเท่านั้น
Harold Fischer

@HaroldFischer ตัวแปรทั้งหมดมีขอบเขตแบบไดนามิก ด้วยtypesetหรือdeclareหรือlocalการประกาศขอบเขตคือจนกว่าฟังก์ชั่นกลับมา ขอบเขตที่เป็นสากล
Gilles 'หยุดชั่วร้าย'

6

ไม่ใช่ข้อผิดพลาดการโทรภายในบริบทของ outerFunc ใช้สำเนา $ var ของโลคอลนั้น "ท้องถิ่น" ใน outerFunc หมายความว่าโลกจะไม่เปลี่ยนแปลง หากคุณเรียกใช้ innerFunc ภายนอก outerFunc จะมีการเปลี่ยนแปลงเป็น $ var ทั่วโลก แต่ไม่ใช่ $ var ท้องถิ่นของ outerFunc หากคุณเพิ่ม "local" ใน innerFunc ดังนั้น $ var ของ outerFunc จะไม่เปลี่ยนแปลงโดยสาระสำคัญแล้วจะมี 3 อย่างต่อไปนี้:

  • ทั่วโลก $ var ::
  • $ outerFunc :: var
  • $ innerFunc :: var

เพื่อใช้รูปแบบเนมสเปซของ Perl


2

คุณสามารถใช้ฟังก์ชั่นเพื่อบังคับขอบเขตภายใน:

sh_local() {
  eval "$(set)" command eval '\"\$@\"'
}

ตัวอย่าง:

x() {
  z='new value'
  printf 'function x, z = [%s]\n' "$z"
}
y() {
  z='initial value'
  printf 'function y before x, z = [%s]\n' "$z"
  sh_local x
  printf 'function y after x, z = [%s]\n' "$z"
}
printf 'global before y, z = [%s]\n' "$z"
y
printf 'global after y, z = [%s]\n' "$z"

ผล:

global before y, z = []
function y before x, z = [initial value]
function x, z = [new value]
function y after x, z = [initial value]
global after y, z = [initial value]

แหล่ง


2

ในไม่ได้ถูกประกาศให้เป็นท้องถิ่นจึงมีอยู่ในขอบเขตที่มองเห็น (ครั้งเดียวฟังก์ชั่นที่ได้รับการเรียกว่า) function innerFunc()var='new value'

ในทางกลับกันในfunction outerFunc()การlocal var='initial value'ประกาศเป็นท้องถิ่นจึงไม่สามารถใช้ได้ในขอบเขตทั่วโลก (แม้ว่าฟังก์ชั่นที่ได้รับการเรียก)

เพราะinnerFunc()ถูกเรียกว่าเป็นเด็กของouterFunc(), var outerFunc()อยู่ภายในขอบเขตของท้องถิ่น

man 1 bash อาจช่วยชี้แจง

ท้องถิ่น [ตัวเลือก] [ชื่อ [= ค่า] ... ]

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

พฤติกรรมส่อให้เห็นว่าคาดว่าในรายละเอียดจะประสบความสำเร็จด้วยการประกาศในlocal var='new valuefunction innerFunc()

ดังที่คนอื่น ๆ ได้กล่าวมานี่ไม่ใช่ข้อผิดพลาดในเชลล์ bash ทุกอย่างทำงานได้ตามที่ควร


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