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?

2
  • 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. Aug 15, 2019 at 8:10
  • @StéphaneChazelas You're right. Can you convert your comment to an answer?
    – SebMa
    Aug 15, 2019 at 9:52

1 Answer 1

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).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .