Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash array export?

Tags:

bash

Is it possible to declare and export a bash array from a single statement within a function?

My current workaround is to first declare, then to export.

f() { foo=(1 2 3); export foo; }; f; export -p | grep foo=
declare -ax foo='([0]="1" [1]="2" [2]="3")'

I observe that:

f() { export bar=(1 2 3); }; f; export -p | grep bar=
<no output>

and:

f() { export baz="(1 2 3)"; }; f; export -p | grep baz=
declare -x baz="(1 2 3)" # not an array

I use bash v3.2.48(1)-release and can't upgrade.


Some background:

I have a friend with whome I am trying to study "some" Django.

He's more clueless than me at the command line and needs the following, on OSX hackintosh:

  • launch an interactive shell
  • find the PATH variable including the django bin dir, as per my specification
  • find an updated PYTHONPATH env var, with the various django libs visible
  • a nice interactive ipython shell to start typing commands in after double-clicking
  • (tricky) an interactive shell to fall back to once he CTRL-D exits from ipython

On Windows, I would alias a command shortcut, like cmd.exe, set custom environment variables and start ipython. This works: after exiting ipython one still finds oneself in a command interpreter.

Is this possible at all with OSX's standard bash? I played with bash -c but many things don't work, like changing into a directory, being able to exit python and stay in a terminal, etc. Also played with -s.

like image 453
Robottinosino Avatar asked Apr 05 '13 03:04

Robottinosino


2 Answers

You can't export an array in Bash (or any other shell). Bash will never export an array to the environment (until maybe implemented someday, see the bugs section in the manpage).

There are a couple things going on here. Again, setting the -x attribute on an array is nonsensical because it won't actually be exported. The only reason you're seeing those results is because you defined the array before localizing it, causing it to drop down to the next-outermost scope in which that name has been made local (or the global scope).

So to be clear, whenever you use a declaration command, you're always creating a local except when you use export or readonly with a non-array assignment as an argument, because these are POSIX, which doesn't specify either locals or arrays.

function f {
    typeset -a a # "a" is now local to "f"
    g
    printf 'Now back in "f": %s\n' "$(typeset -p a)"
}

function g {
    a=(1 2 3)                               # Assigning f's localized "a"
    typeset -a a                            # A new local a
    printf 'In "g": %s\n' "$(typeset -p a)" # g's local is now empty.
    a=(a b c)                               # Now set g's local to a new value.
    printf 'Still in "g": %s\n' "$(typeset -p a)"
}

f

# In "g": declare -a a='()'
# Still in "g": declare -a a='([0]="a" [1]="b" [2]="c")'
# Now back in "f": declare -a a='([0]="1" [1]="2" [2]="3")'

It seems if you give an array as an argument to export then Bash will make it local like any other declaration command. Basically don't use export to define an array. export is a POSIX builtin and its behavior is undefined for arrays, which is probably why bash just treats it as though you had used typeset -ax a=(1 2 3). Use typeset, local, or declare. There's really no way to know what's "correct", or even compare with other shells. ksh93 is the only other that accepts array assignments as arguments to declaration commands, and its behavior doesn't agree with Bash.

The important thing to understand is that the environment has nothing to do with it, you're just playing with the quirks of locals when trying to do nonstandard things with POSIX-only commands.

In order to actually get the effect you want, you may use typeset -p.

function f {
    typeset -a "${1}=(1 2 3)"
    typeset -p "$1"
}

typeset -a arr
eval "$(f arr)"
typeset -p arr

Bash guarantees you'll get out the correct result, but I find this isn't very useful and rarely use this approach. It's usually better to just let the caller define a local and use dynamic scope do the work (as you've already discovered... just do it without exporting).

In Bash 4.3 you can use typeset -n, which is really the most correct way to handle this.

like image 60
ormaaj Avatar answered Sep 20 '22 12:09

ormaaj


The first option seems to be the only one that's going to work. I experimented with bash 4.2 as well as 3.2.48. The interesting information, to me, were these minor variants of your examples:

$ f() { declare -a bar=(1 2 3); export bar; export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ f() { export  bar=(1 2 3); export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ f() { bar=(1 2 3); export bar; export -p | grep bar=; }; f; export -p | grep bar=
declare -ax bar='([0]="1" [1]="2" [2]="3")'
declare -ax bar='([0]="1" [1]="2" [2]="3")'
$ unset bar
$ f() { bar=(1 2 3); }; f; set | grep bar=
bar=([0]="1" [1]="2" [2]="3")
    bar=(1 2 3)
$

In these examples, I test the export inside the function as well as outside the function. Because the variables are being defined in the function, they appear to be scope-limited to the function. The exception is when the variable is defined before any attributes are applied — the last two functions. There a global variable is created, and then exported (in one case).

So, if you're going to get the array exported from the function, you have to create it without the declare or export statements (because those make the variable local to the function), and then export it.

I hope that explains it; I can see fuzzily what's going on and it makes sense, after a fashion. I'm not sure I explained it as well as it should be explained.


In the declare section of the bash manual, it says:

When used in a function, declare makes each name local, as with the local command.

There isn't equivalent wording in the export. However, the observed behaviour is as if export is implemented by declare -x.

like image 30
Jonathan Leffler Avatar answered Sep 19 '22 12:09

Jonathan Leffler