2

I have the following script called .bash_functions.test which is already sourced by my .bash_functions script:

# vim: set syn=sh noet:

mp4Options_BIS="-movflags +frag_keyframe"
declare -A audioExtension=( [libspeex]=spx [speex]=spx [opus]=opus [vorbis]=ogg [aac]=m4a [mp3]=mp3 [mp2]=mp2 [ac3]=ac3 [wmav2]=wma [pcm_dvd]=wav [pcm_s16le]=wav )

function test1 {
    echo "=> mp4Options_BIS = $mp4Options_BIS"
    echo "=> audioExtension = ${audioExtension[*]}"
}

And when I run the test1 function I see this:

=> mp4Options_BIS = -movflags +frag_keyframe
=> audioExtension = 

Finally, when I source the script once more and re-run the test1 function I see this:

=> mp4Options_BIS = -movflags +frag_keyframe
=> audioExtension = ac3 wma opus mp3 wav mp2 wav spx m4a spx ogg

In fact, I use my Source function in the first source call and the source builtin and the second source call:

$ grep -r .bash_functions.test 
.bash_functions:source $initDir/.bash_functions.test
$ type Source 
Source is a function
Source () 
{ 
    test "$debug" -gt 0 && time source "$@" && echo || source "$@"
}

And here is what happens:

$ Source .initBash/.bash_functions.test
$ test1
=> mp4Options_BIS = -movflags +frag_keyframe
=> audioExtension = 
$ source .initBash/.bash_functions.test
$ test1
=> mp4Options_BIS = -movflags +frag_keyframe
=> audioExtension = ac3 wma opus mp3 wav mp2 wav spx m4a spx ogg

Why is it working like this?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
SebMa
  • 2,149
  • 1
    Does your .bash_functions source .bash_functions.test from within a function by any chance? If that was the case, the declare would cause that associative array to be declared local to that function and you'd need declare -gA to make sure the variable is declared in the global scope. – Stéphane Chazelas Aug 15 '19 at 08:10
  • @StéphaneChazelas You're right. Can you convert your comment to an answer? – SebMa Aug 15 '19 at 09:52

1 Answers1

2

declare/typeset without -g declares variables in the current scope in addition to setting the type.

Here, because declare -A audioExtension=(...) ends up being run within the Source function, that causes the audioExtension variable to be declared local to that function, and as a consequence its definition to be lost once Source returns.

You could change it to typeset -Ag audioExtension=(...) which always declares the variable in the global scope (it's different from zsh/mksh/yash where typeset -g only prevents the variable from being made local (only updates the type/attributes and value); it makes a difference when your Source function is called from another function itself; see bash vs zsh: scoping and `typeset -g` for details).

If you used ksh93 instead of bash (that's the shell bash borrowed that associative array syntax from), you could define your Source function as:

Source() {
  ...
}

as opposed to:

function Source {
  ...
}

In ksh93, functions defined with the Bourne-style func() cmd syntax don't have a local scope while function func { ones have static local scope (there, there's only one global scope and one local per-function scope, not a stack of local scopes).

In bash (and other shells with local scoping), there's no similar way to have a function that doesn't introduce a new scope. You could use an alias instead like:

shopt -s expand_aliases
if [ "$debug" -gt 0 ]; then
  alias Source='time source'
else
  alias Source=source
fi

(note that the aliases are expanded at the time code is read, not at the time it's executed).

It would make a functional difference compared to your function approach in that in:

Source ./myfile | other-cmd

We would be timing both source myfile and other-cmd as it's expanded to time source ./myfile | other-cmd while in the function approach we would only by timing source ./myfile.

Using source=(time source); "${source[@]}" /some/file wouldn't work (it would invoke the time standalone utility instead of the bash time keyword).