4

Usually, I see questions about people not being able to access variables from outside their scope. However, I seem to be experiencing the opposite: I am seeing variables still having values from inner scopes they should have given up afterwards. For example (making svn aliases similar to git aliases):

function svn() {
    case $@ in
        alias*) shift 1;
            for i in "$@"; do
                if [[ "$i" == "-t" ]];
                then
                    j="$i,$j"
                elif [[ "$i" == "-f" ]];
                    k="$i,$j"
                fi
            done

            echo "i = $i"
            echo "j = $j"
            echo "k = $k"
        ;;
    esac
}

I put this in a script and source it, so its function is made into an alias for bash (I think). Try running this with various combinations of "-t" and "-f", and you'll see that the variables "$i", "$j", and "$k" all keep their values when running the script again, and that they stay the same in the outer shell, after the script has exited. I am using an Ubuntu 15.04 laptop, and when I type Ctrl-X Ctrl-V my shell outputs GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu).

Everything I have read about bash tells me this should not happen (admittedly, I am somewhat of a beginner in this area). Variables should not stay set after the script (or function) exits, unless you use export on them, which I have not. So why is this happening?

trysis
  • 7,124
  • 12
  • 47
  • 74

1 Answers1

6

There are two different phenomena in play:

  1. When variables are exported, they are copied into the environments of child processes. Non-exported variables are not passed on. Variables aren't exported unless you explicitly use export to mark them for export.

     export LESS_OPTIONS=-R   # export so `less` sees this variable
     less
    

    Don't confuse this with scope, which is different.

  2. Inside functions variables have global scope by default. You have to use the local keyword to declare local variables. Otherwise a loop like for i in "$@" will modify the global variable $i rather than creating a local variable.

    svn() {
        local i j k
    
        case $@ in
            ...
        esac
    }
    

Exporting determines what child processes see. Scope determines whether functions modify global variables or not.

John Kugelman
  • 307,513
  • 65
  • 473
  • 519
  • I have not seen this in example bash scripts on the Internet. So I would use something like `local i=$i` in the example above? – trysis Jun 03 '15 at 02:52
  • 2
    Many scripts out in the wild are poorly written. They leak variables, they don't quote variables properly, they parse the output of `ps` and `ls`... all kinds of nonsense. The [Bash Guide](http://mywiki.wooledge.org/BashGuide/CompoundCommands#Functions) has an example that correctly uses `local i` before a `for` loop, along with the note, *"The local variable `i` inside the function is stored differently from the variable `i` in the outer script. This allows the two loops to operate without interfering with each other's counters."* – John Kugelman Jun 03 '15 at 03:01
  • It seems that bash's scoping is similar to JavaScript's. Both have 2 types of scoping: global scope (no identifier) and function scope (declared with `var` or `local`). Do I have that about right? I work mainly with JavaScript, so an analogy like this would be helpful to me. – trysis Jun 03 '15 at 03:22
  • Yes, you're right. I never made that connection. And I would say JavaScript wins because you can put the `var` declaration right in the `for` loop. In bash it has to be a separate command. – John Kugelman Jun 03 '15 at 03:47
  • `declare` can also be used for scoping. Not using `local` or `declare` is unfortunately a common error. – cdarke Jun 03 '15 at 08:58
  • Yes, according to [this question](http://stackoverflow.com/questions/4419704/differences-between-declare-typeset-and-local-variable-in-bash) declare, local, & typeset are all the same variable in bash. – trysis Jun 03 '15 at 10:51