Why does Bash's "source" command behave differently when called from a function?
Given the following sourceable bar
Bash script …
echo :$#:"$@":
… and the following executable foo
Bash script:
echo -n source bar:
source bar
echo -n source bar foo:
source bar foo
function _import {
source "$@"
}
echo -n _import bar:
_import bar
echo -n _import bar foo:
_import bar foo
I get the following output when running the foo
Bash script, i.e. ./foo
:
source bar::0::
source bar foo::1:foo:
_import bar::1:bar:
_import bar foo::1:foo:
Here are my questions:
- Why does it make a difference when I call Bash’s
source
command from the_import
function as opposed to directly? - How can I normalize the behavior of Bash’s
source
command?
I’m using Bash version 4.2.47(1)-release on Fedora version 20.
The problem because source
execute file in current environment. And in bash
, if no positional parameters is provided, they are unchanged. From bash
Bourne Shell Builtins man page:
. (a period)
. filename [arguments]
Read and execute commands from
the filename argument in the current shell context. If filename does
not contain a slash, the PATH variable is used to find filename. When
Bash is not in POSIX mode, the current directory is searched if
filename is not found in $PATH. If any arguments are supplied, they
become the positional parameters when filename is executed. Otherwise
the positional parameters are unchanged. The return status is the exit
status of the last command executed, or zero if no commands are
executed. If filename is not found, or cannot be read, the return
status is non-zero. This builtin is equivalent to source.
POSIX defines dot (source
is synonym to dot
in bash
):
The shell shall execute commands from the file in the current
environment.
And it also explained the KornShell version of dot can take optional arguments, that are set to the positional parameters:
The KornShell version of dot takes optional arguments that are set to
the positional parameters. This is a valid extension that allows a dot
script to behave identically to a function.
So when you call source bar
inside _import
function, you don’t provide any positional parameters, so they are unchanged. They are identical with _import
function scope, $@
contains bar
and $#
is 1
(Because you run _import bar
).
When you call source bar
outside _import
function scope, it’s identical to global scope (or foo
script). In this case, because you run ./foo
, you run foo
without any arguments, $@
is null and $#
is zero.
Gnouc’s answer explains my first question: “Why does it make a difference when I call Bash’s source command from the _import function as opposed to directly?”
Regarding my second question: “How can I normalize the behavior of Bash’s source command?”
I think I found the following answer:
By changing the _import
function to:
function _import {
local -r file="$1"
shift
source "$file" "$@"
}
I get the following output when running the foo
Bash script, i.e. ./foo
:
source bar::0::
source bar foo::1:foo:
_import bar::0::
_import bar foo::1:foo:
Rationale behind my question and this answer: An “imported” Bash script should be able to evaluate its own set of arguments via Bash’s positional and special parameters even when none were given while importing it. Any arguments that MAY be passed to the importing Bash script MUST NOT be implicitly passed to the imported Bash script.