Size: 1329
Comment:
|
Size: 4681
Comment: Discussion: use $?
|
Deletions are marked like this. | Additions are marked like this. |
Line 2: | Line 2: |
== How can I tell whether my script was sourced (dotted in) or exectued? == Usually when people ask this, it is because they are trying to detect user errors and provide a friendly message. There is one school of thought that says it's a bad idea to coddle Unix users in this way, and that if a Unix user really wants to execute your script instead of sourcing it, you shouldn't second-guess him or her. Setting that aside for now, we can rephrase the question to what's really being asked: |
== How can I tell whether my script was sourced (dotted in) or executed? == There seem to be two reasons why people ask this: either they're trying to detect user errors and provide a friendly message, or they're Python programmers who want to use one of Python's most idiosyncratic features in bash. |
Line 5: | Line 5: |
''I want to give an error message and abort, if the user runs my script from an interactive shell, instead of sourcing it.'' | === Detecting user errors === There is one school of thought that says it's a bad idea to coddle Unix users in this way, and that if a Unix user really wants to execute your script instead of sourcing it, you shouldn't second-guess him or her. Setting that aside for now, we can rephrase the question to what's really being asked: ''I want to give an error message and abort, if the user runs my script, instead of sourcing it from an interactive shell.'' |
Line 9: | Line 12: |
{{{ # POSIX(?) |
{{{#!highlight sh # POSIX |
Line 12: | Line 15: |
*i*) : ;; | *i*) : ;; |
Line 18: | Line 21: |
{{{ | {{{#!highlight bash |
Line 25: | Line 29: |
Of course, this doesn't work for tricky cases like "I want my file to be dotted in from a non-interactive script...". For those cases, see the first paragraph of this page. | Of course, this doesn't work for tricky cases like "I want my file to be dotted in from a non-interactive script...". For those cases, see the first paragraph of this section. More details/tests can be found here https://gist.github.com/dualbus/6811562 === Emulating Python weirdness === Python has a strange feature: some Python scripts are both a program and a library at the same time. The script detects whether it is being executed as a program or imported (is that the word??) as a library/package, and behaves differently. For example, when being imported as a library, only functions are defined. The command line arguments are not processed, and none of the functions are actually ''called''. Bash ''can'' do this, but it's not a natural style, and you shouldn't be doing it. That said, people will just pester us until we show them how to do this unnatural thing, so here we go. Bash has an internal variable called `FUNCNAME` that can be used to detect whether a script was sourced. Normally this variable only shows the names of functions on the call stack, but `source` or `.` works a lot like a function call, so it gets put on this stack. Now, since this is bash, nothing can ever be straightforward. This variable only exists when you're inside of an actual function. So you can't do this check in the main body of the script. You have to write a function, call it, and peek one slot down in the call stack to see where you ''were'' before the function was called. {{{#!highlight bash # Bash only libfunc1() { ...; } libfunc2() { ...; } sourced() { [[ ${FUNCNAME[1]} = source ]] } sourced && return # process command line arguments, etc. }}} Note that the name `source` gets put on the stack even if the script was dotted in using `.`, so this check is sufficient for both cases. If this script is executed, it will define the three functions, fail the check, and move on to the "process command line arguments, etc." section, which presumably contains some commands. If the script is sourced or dotted in, it will only define the three functions, and nothing more. === Discussion === Doesn't `return 2>/dev/null` work just as well to "emulate Python's weirdness"? —Crestwave That will only work in {{{bash}}} (not in POSIX mode): it will not work in {{{bash}}} running in POSIX mode or in other shells like {{{dash}}}; they exit when `return` is used outside of a function or sourced file. POSIX says: "If the shell is not currently executing a function or dot script, the results are unspecified.". —emanuele6 I think `(( ${#BASH_SOURCE[@]} > 1 ))` works better as a way to detect whether the file has been sourced. —emanuele6 Actually, `(( ${#BASH_SOURCE[@]} > 1 ))` does not handle the case in which the file is sourced from a shell that is reading commands from stdin since stdin is not included in the `BASH_SOURCE` array, so something like `[[ ${#BASH_SOURCE[@]} -gt 1 || ( ${#BASH_SOURCE[@]} = 1 && $- = *s* ) ]]` would be more correct. —emanuele6 You can use `*[sc]*` instead of just `*s*` to also support sourcing from `bash -c`: `[[ ${#BASH_SOURCE[@]} -gt 1 || ${#BASH_SOURCE[@]}$- = 1*[sc]* ]]`. —emanuele6 Take advantage of the exit code of the $- test for more compactness: `[[ $- != *[cs]* ]];(($?+${#BASH_SOURCE[@]}>1))`. - pj |
How can I tell whether my script was sourced (dotted in) or executed?
There seem to be two reasons why people ask this: either they're trying to detect user errors and provide a friendly message, or they're Python programmers who want to use one of Python's most idiosyncratic features in bash.
Detecting user errors
There is one school of thought that says it's a bad idea to coddle Unix users in this way, and that if a Unix user really wants to execute your script instead of sourcing it, you shouldn't second-guess him or her. Setting that aside for now, we can rephrase the question to what's really being asked:
I want to give an error message and abort, if the user runs my script, instead of sourcing it from an interactive shell.
The key here, and the reason I've rephrased the question this way, is that you can't actually determine what the user typed, but you can determine whether the code is being interpreted by an interactive shell. You do that by checking for an i in the contents of $-:
Or using non-POSIX syntax:
Of course, this doesn't work for tricky cases like "I want my file to be dotted in from a non-interactive script...". For those cases, see the first paragraph of this section.
More details/tests can be found here https://gist.github.com/dualbus/6811562
Emulating Python weirdness
Python has a strange feature: some Python scripts are both a program and a library at the same time. The script detects whether it is being executed as a program or imported (is that the word??) as a library/package, and behaves differently. For example, when being imported as a library, only functions are defined. The command line arguments are not processed, and none of the functions are actually called.
Bash can do this, but it's not a natural style, and you shouldn't be doing it. That said, people will just pester us until we show them how to do this unnatural thing, so here we go.
Bash has an internal variable called FUNCNAME that can be used to detect whether a script was sourced. Normally this variable only shows the names of functions on the call stack, but source or . works a lot like a function call, so it gets put on this stack.
Now, since this is bash, nothing can ever be straightforward. This variable only exists when you're inside of an actual function. So you can't do this check in the main body of the script. You have to write a function, call it, and peek one slot down in the call stack to see where you were before the function was called.
Note that the name source gets put on the stack even if the script was dotted in using ., so this check is sufficient for both cases.
If this script is executed, it will define the three functions, fail the check, and move on to the "process command line arguments, etc." section, which presumably contains some commands. If the script is sourced or dotted in, it will only define the three functions, and nothing more.
Discussion
Doesn't return 2>/dev/null work just as well to "emulate Python's weirdness"? —Crestwave
That will only work in bash (not in POSIX mode): it will not work in bash running in POSIX mode or in other shells like dash; they exit when return is used outside of a function or sourced file. POSIX says: "If the shell is not currently executing a function or dot script, the results are unspecified.". —emanuele6
I think (( ${#BASH_SOURCE[@]} > 1 )) works better as a way to detect whether the file has been sourced. —emanuele6
Actually, (( ${#BASH_SOURCE[@]} > 1 )) does not handle the case in which the file is sourced from a shell that is reading commands from stdin since stdin is not included in the BASH_SOURCE array, so something like [[ ${#BASH_SOURCE[@]} -gt 1 || ( ${#BASH_SOURCE[@]} = 1 && $- = *s* ) ]] would be more correct. —emanuele6
You can use *[sc]* instead of just *s* to also support sourcing from bash -c: [[ ${#BASH_SOURCE[@]} -gt 1 || ${#BASH_SOURCE[@]}$- = 1*[sc]* ]]. —emanuele6
Take advantage of the exit code of the $- test for more compactness: [[ $- != *[cs]* ]];(($?+${#BASH_SOURCE[@]}>1)). - pj