Zakres zmiennych lokalnych w funkcjach powłoki


28

Po przeczytaniu 24.2. Zmienne lokalne , pomyślałem, że zadeklarowanie zmiennej varze słowem kluczowym localoznacza, że varwartość jest dostępna tylko w bloku kodu ograniczonym nawiasami klamrowymi funkcji.

Jednak po uruchomieniu następujący przykład, okazało się, że varmożna również uzyskać, odczytywane i zapisywane z funkcji powołuje tego bloku kodu - czyli choć vardeklaruje localsię outerFunc, innerFuncnadal jest w stanie go odczytać i zmienić jego wartość.

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}]"

Wydajność:

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:]

P: Czy to błąd w mojej powłoce (bash 4.3.42, Ubuntu 16.04, 64bit), czy jest to oczekiwane zachowanie?

EDYCJA: rozwiązana. Jak zauważył @MarkPlotnick, jest to rzeczywiście oczekiwane zachowanie.


Jest to oczekiwane zachowanie
fpmurphy

2
Czy jestem jedynym, który uważa, że ​​to dziwne, że w ostatnim wierszu wyniku wartość varjest pusta? varjest ustawiony globalnie innerFunc, więc dlaczego nie przykleja się do końca skryptu?
Harold Fischer

Odpowiedzi:


22

Zmienne powłoki mają zakres dynamiczny . Jeśli zmienna zostanie zadeklarowana jako lokalna dla funkcji, zakres ten pozostaje do momentu powrotu funkcji.

Istnieją dwa wyjątki:

  1. w ksh93, jeśli funkcja jest zdefiniowana ze standardową function_name () { … }składnią, wówczas jej zmienne lokalne podlegają dynamicznemu zakresowi. Ale jeśli funkcja jest zdefiniowana za pomocą składni ksh, function function_name { … }wówczas jej zmienna lokalna jest zgodna z zasięgiem leksykalnym / statycznym, więc nie są widoczne w innych wywoływanych przez nią funkcjach.

  2. zsh/privateautoloadable plugin zshdostarcza z privateHasło / wbudowane, które mogą być wykorzystane do zadeklarować zmienną z zakresu statycznego.

ash, bash, pdksh i pochodne, bosh mają tylko dynamiczne określanie zakresu.


Czy wszystkie zmienne w powłoce mają zakres dynamiczny, czy dotyczy to tylko zmiennych zadeklarowanych za pomocą local?
Harold Fischer,

@HaroldFischer Wszystkie zmienne mają zakres dynamiczny. W przypadku deklaracji typesetlub declarelub localzasięg jest do momentu powrotu funkcji. Bez takiej deklaracji zakres jest globalny.
Gilles „SO- przestań być zły”

6

To nie jest błąd, wywołanie w kontekście externalFunc wykorzystuje lokalną kopię $ var. „Lokalny” w outerFunc oznacza, że ​​globalny nie został zmieniony. Jeśli wywołasz innerFunc poza outerFunc, nastąpi zmiana globalnego $ var, ale nie lokalny $ var varFunc. Jeśli dodałeś „local” do innerFunc, wtedy $ var w externalFunc nie zostałby zmieniony - w istocie byłyby 3 z nich:

  • $ global :: var
  • $ outerFunc :: var
  • $ innerFunc :: var

aby użyć formatu przestrzeni nazw Perla, w pewnym sensie.


2

Możesz użyć funkcji, aby wymusić zasięg lokalny:

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

Przykład:

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"

Wynik:

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]

Źródło


2

W function innerFunc()var='new value' nie został zadeklarowany jako lokalny , dlatego jest ona dostępna w widzialnym zakresie (gdy funkcja została wywołana).

I odwrotnie, w function outerFunc()local var='initial value' został uznany jako lokalny , więc to nie jest dostępne w zasięgu globalnym (nawet jeśli funkcja została wywołana).

Ponieważ innerFunc()został wywołany jako dziecko outerFunc(), var jest w lokalnym zasięgu outerFunc().

man 1 bash może pomóc wyjaśnić

lokalny [opcja] [nazwa [= wartość] ...]

Dla każdego argumentu tworzona jest lokalna zmienna o nazwie name i przypisywana wartość. Opcją może być dowolna z opcji zaakceptowanych przez deklarację. Gdy lokalny jest używany w funkcji, powoduje, że nazwa zmiennej ma widoczny zasięg ograniczony do tej funkcji i jej potomków. ...

Implikowana zachowanie, które jest spodziewane w opisie można osiągnąć poprzez uznanie local var='new valuewfunction innerFunc() .

Jak powiedzieli inni, nie jest to błąd w powłoce bash. Wszystko działa tak, jak powinno.


Twoje pierwsze stwierdzenie jest sprzeczne z tym, co widzi użytkownik. Drukowanie wartości varw zakresie globalnym, po wywołaniu innerFuncprzez outFunc, nie drukuje new value.
Kusalananda
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.