Shell – Parameters passed to a sourced script are wrong

aixparametershell-scriptsource-command

A script on AIX which checks the number of parameters passed and complains if they are not correct:-

if [ "$#" -ge 1 ]; then
  ...
else
  echo 'Usage: myscript <a> [b] [c]'
fi

The script sets some environment variables so it is sourced. By that I mean I type the following on my command line. There is no second script, I am not sourcing a script from within another script.

. ./myscript.sh

If I pass it no parameters, I expect it to print out the usage statement but instead it runs using the parameters passed to the previous command that was sourced (not necessarily even this script!).

To double check this I added this line at the beginning of my script to prove that this was the case.

echo $# $@

If I pass it no parameters, it prints out the number and the list of parameters from a previously sourced script.

How do I get it to say $# is zero when I pass it zero parameters and source it?

The smallest possible recreate is a script thus:-

myscript.sh

echo $# $@

and then run it thus:-

. ./myscript.sh a b c

which prints out

3 a b c

then run it again without any parameters thus:-

. ./myscript.sh

which prints out the same

3 a b c

when you would expect it to print out

0

If you don't source the script and just run it thus:-

./myscript.sh

then it works as expected and prints out

0

I hope that clarifies the issue at hand.

Since I seem to be getting lots of answers to explain why the above doesn't work and no answers to tell me what I should do instead, I thought I would have one more attempt at trying to explain what I am trying to do.

Requirement

I need to write a script that expects at least one parameter and complains if it is not invoked with a said parameter (see the first block of code in my question above), and this script needs to set some environment variables. Because of this second requirement I have assumed that I need to source the script so that the environment variables are still set when the script completes. How can you write a script that can tell when it has not been passed parameters that is also able to set an environment variable?

Best Answer

In

. path/to/script at least one argument

As others have said, depending on the shell, those extra arguments are either ignored (Bourne, Almquist) or replace the current set of positional parameters ($@, $1, $2...) for the interpretation of that script (most other shells). POSIX doesn't specify the behaviour when extra arguments are passed to .. In that case, there is also some variation between shells as to whether any change to the positional parameters (like with set a b) made within the script will remain after . finishes.

In:

. path/to/script

however, the behaviour is specified. The positional parameters are required not to be affected, so will stay the same as before for the interpretation of the script.

If you want the positional parameters to be an empty list during the interpretation of that script, you'll need to reset it before hand, or use a function:

set -- # or shift "$#"
. path/to/script

(though the positional parameters are lost). Or use a function:

dot() { file=$1; shift; . "$file"; }
dot path/to/myscript

At the time the . command is executed, the positional parameters will be that of the function. After the shift, it will be the arguments passed to the function minus the first one, so in the above, an empty list; but that means that with that function you can portably pass an arbitrary list of positional parameters to the sourced script as in:

dot path/to/myscript extra args

Where those extra args will be available as $1 and $2 within the sourced script.

And if myscript calls set to modify the list of positional parameters, that will only affect the function's positional parameters, and those changes will not persist after the function returns.

Related Question