10

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:

  1. Why does it make a difference when I call Bash's source command from the _import function as opposed to directly?
  2. 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.

6

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.

4
  • "And in bash, if no positional parameters is provided, they are unchanged" -> I can't understand this phrase. Could you please elaborate? – Rafael Eyng Jan 2 '20 at 16:07
  • Your answer uses 3 times the word "unchanged", but I'm having a really hard time trying to understand what you mean by that. Could you elaborate what you mean by "the parameters are unchanged"? I'm guessing you mean "the parameters provided to <something> are passed to <other thing> without any change", I just can't fill the placeholders in my assumption. – Rafael Eyng Jan 3 '20 at 2:47
  • "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)." -> this explains why _import bar prints _import bar::1:bar:, but does not explain why _import bar foo prints _import bar foo::1:foo:, instead of _import bar foo::2:bar foo: – Rafael Eyng Jan 3 '20 at 2:59
  • @RafaelEyng with _import bar foo above, source "$@" expands to source "bar" "foo", so the positional parameters are changed: instead of sourceing bar with no parameters (the 'unchanged' scenario), bar is now sourceed with 1 parameter foo (the 'changed' scenario); it just so happens that because it goes from zero to one parameter, the output is confusing. This confusion stems from _import bar, where source reveals that it is aware of what is passed to the calling _import function; for further clarity/confusion, change $@ to $1 ;P – Darren Bishop Jun 21 '20 at 10:42
5

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.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.