Is it possible for a shell script to test whether it was executed through source
? That is, for example,
$ source myscript.sh
$ ./myscript.sh
Can myscript.sh distinguish from these different shell environm开发者_开发百科ents?
I think, what Sam wants to do may be not possible.
To what degree a half-baken workaround is possible, depends on...
- ...the default shell of users, and
- ...which alternative shells they are allowed to use.
If I understand Sam's requirement correctly, he wants to have a 'script',
myscript
, that is...
- ...not directly executable via invoking it by its name
myscript
(i.e. that haschmod a-x
); - ...not indirectly executable for users by invoking
sh myscript
or invokingbash myscript
- ...only running its contained functions and commands if invoked by
sourcing it:
. myscript
The first things to consider are these
- Invoking a script directly by its name (
myscript
) requires a first line in the script like#!/bin/bash
or similar. This will directly determine which installed instance of the bash executable (or symlink) will be invoked to run the script's content. This will be a new shell process. It requires the scriptfile itself to have the executable flag set. - Running a script by invoking a shell binary with the script's (path+)name as
an argument (
sh myscript
), is the same as '1.' -- except that the executable flag does not need to be set, and said first line with the hashbang isn't required either. The only thing needed is that the invoking user needs read access to the scriptfile. - Invoking a script by sourcing its filename (
. myscript
) is very much the same as '1.' -- exept that it isn't a new shell that is invoked. All the script's commands are executed in the current shell, using its environment (and also "polluting" its environment with any (new) variables it may set or change. (Usually this is a very dangerous thing to do: but here it could be used to executeexit $RETURNVALUE
under certain conditions....)
For '1.':
Easy to achieve: chmod a-x myscript
will prevent myscript
from being
directly executable. But this will not fullfill requirements '2.' and '3.'.
For '2.' and '3.':
Much harder to achieve. Invokations by sh myscript
require reading
privileges for the file. So an obvious way out would seem to chmod a-r
myscript
. However, this will also dis-allow '3.': you will not be able to
source the script either.
So what about writting the script in a way that uses a Bashism? A Bashism is a
specific way to do something which other shells do not understand: using
specific variables, commands etc. This could be used inside the script to
discover this condition and "do something" about it (like "display warning.txt",
"mailto admin" etc.). But there is no way in hell that this will prevent sh
or
bash
or any other shell from reading and trying to execute all the following
commands/lines written into the script unless you kill the shell by invoking
exit
.
Examples: in Bash, the environment seen by the script knows of $BASH
,
$BASH_ARGV
, $BASH_COMMAND
, $BASH_SUBSHELL
, BASH_EXECUTION_STRING
... . If
invoked by sh
(also if sourced inside a sh
), the executing shell will see
all these $BASH_*
as empty environment variables. Again, this could be used
inside the script to discover this condition and "do something"... but not
prevent the following commands from being invoked!
I'm now assuming that...
- ...the script is using
#!/bin/bash
as its first line, - ...users have set Bash as their shell and are invoking commands in the following table from Bash and it is their login shell,
- ...
sh
is available and it is a symlink tobash
ordash
.
This will mean the following invokations are possible, with the listed values for environment variables
vars+invok's | ./scriptname | sh scriptname | bash scriptname | . scriptname ---------------+--------------+---------------+-----------------+------------- $0 | ./scriptname | ./scriptname | ./scriptname | -bash $SHLVL | 2 | 1 | 2 | 1 $SHELLOPTS | braceexpand: | (empty) | braceexpand:.. | braceexpand: $BASH | /bin/bash | (empty) | /bin/bash | /bin/bash $BASH_ARGV | (empty) | (empty) | (empty) | scriptname $BASH_SUBSHELL | 0 | (empty) | 0 | 0 $SHELL | /bin/bash | /bin/bash | /bin/bash | /bin/bash $OPTARG | (empty) | (empty) | (emtpy) | (emtpy)
Now you could put a logic into your text script:
- If
$0
is not equal to-bash
, then do anexit $SOMERETURNVALUE
.
In case the script was called via sh myscript
or bash myscript
, then it will
exit the calling shell. In case it was run in the current shell, it will
continue to run. (Warning: in case the script has any other exit
statements,
your current shell will be 'killed'...)
So put into your non-executable myscript.txt
near its beginning something like
this may do something close to your goal:
echo BASH=$BASH
test x${BASH} = x/bin/bash && echo "$? : FINE.... You're using 'bash ...'"
test x${BASH} = x/bin/bash || echo "$? : RATS !!! -- You're not using BASH and I will kick you out!"
test x${BASH} = x/bin/bash || exit 42
test x"${0}" = x"-bash" && echo "$? : FINE.... You've sourced me, and I'm your login shell."
test x"${0}" = x"-bash" || echo "$? : RATS !!! -- You've not sourced me (or I'm not your bash login shell) and I will kick you out!"
test x"${0}" = x"-bash" || exit 33
This may or may not be what the asker wanted but, on a similar situation, I wanted a script to indicate that it is meant to be sourced and not directly run.
To achieve this effect my script reads:
#!/bin/echo Should be run as: source
export SOMEPATH="/some/path/on/my/system"
echo "Your environment has been set up"
So when I run it either as a command or sourced I get:
$ ./myscript.sh
Should be run as: source ./myscript.sh
$ source ./myscript.sh
Your environment has been set up
You can of course fool the script by running it as sh ./myscript.sh
, but at least it gives the correct expected behaviour on 2 out of 3 cases.
This is what I was looking for:
[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"
I cannot add comment yet (stackexchange policies) so I add my own answer:
This one may works regardless if we do:
bash scriptname
scriptname
./scriptname
.
on both bash
and mksh
.
if [ "${0##/*}" == scriptname ] # if the current name is our script
then
echo run
else
echo sourced
fi
If you have a non-altering file path for regular users, then:
if [ "$(/bin/readlink -f "$0")" = "$KNOWN_PATH_OF_THIS_FILE" ]; then
# the file was executed
else
# the file was sourced
fi
(it can also easily be loosened to only check for the filename or whatever).
But your users need to have read permission to be able to source the file, so absolutely nothing can stop them from doing what they want with the file. But it might help them out to not use it in the wrong way.
This solution is not dependent on Bashisms.
Yes it is possible. In general you can do the following:
#! /bin/bash
sourced () {
echo Sourced
}
executed () {
echo Executed
}
if [[ ${0##*/} == -* ]]; then
sourced
else
executed $@
fi
Giving the following output:
$ ./myscript
Executed
$ . ./myscript
Sourced
Based on Kurt Pfeifle’s answer, this works for me
if [ $SHLVL = 1 ]
then
echo 'script was sourced'
fi
Example
Since all of our machines have history, I did this:
check_script_call=$(history |tail -1|grep myscript.sh )
if [ -z "$check_script_call" ];then
echo "This file should be called as a source."
echo "Please, try again this way:"
echo "$ source /path/to/myscript.sh"
exit 1
fi
Everytime you run a script (without source), your shell creates a new env without history.
If you want to care about performance you can try this:
if ! history |tail -1|grep set_vars ;then
echo -e "This file should be called as a source.\n"
echo "Please, try again this way:"
echo -e "$ source /path/to/set_vars\n"
exit 1
fi
PS: I think Kurt's answer is much more complete but I think this could help.
In the first case, $0
will be "myscript.sh". In the second case, it will be "./myscript". But, in general, there's no way to tell source
was used.
If you tell us what you're trying to do, instead of how you want to do it, a better answer might be forthcoming.
精彩评论