Check if a script is sourced or executed
Detect at runtime whether the current script was sourced into the parent shell (`. script` / `source script`) or executed in a subshell (`bash script`) — so the same file can be used both as a library and as a CLI.
How to check if a script is sourced or executed in each shell
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo sourced || echo executed`BASH_SOURCE[0]` is the path to the CURRENT FILE; `$0` is the name of the invoking process. When sourced, they differ (BASH_SOURCE points at the source file, $0 points at the parent shell). When executed, they match.
[[ "${ZSH_EVAL_CONTEXT:-}" == *":file"* ]] && echo sourced || echo executed`ZSH_EVAL_CONTEXT` is a colon-separated stack: `toplevel:cmdsubst:file` etc. The presence of `:file` (or just `file`) in the chain means we're being sourced from a file.
if test (status fish-path) = (status filename); echo executed; else; echo sourced; end`status filename` is the path to the running script; `status fish-path` is the path to the fish binary itself. When sourced INTERACTIVELY, fish-path == filename. Compare by querying `status -i` (interactive shell — most likely sourced from rc) and `status -t` (subjob).
if (`$MyInvocation.InvocationName -eq '.') { 'sourced' } else { 'executed' }pwsh dot-sourcing uses literal `.` (`. .\script.ps1`) — `$MyInvocation.InvocationName` reflects this. Module-imported scripts (`Import-Module .\script.ps1`) show `Import-Module` as the InvocationName.
echo cmd has no native sourced-vs-executed distinction`call script.bat` (invoke within current cmd) vs `script.bat` (start fresh cmd) are the two patterns, but the script itself can't tell which mode it was called in (no introspection variable like BASH_SOURCE).
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **The "dual-mode script" use case**: a single file that's `source-able` to expose helper functions AND `executable` to run a default action. Pattern: `# function definitions here …` then at the bottom: `if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@"; fi`. When sourced (e.g. `source utils.sh`), the file just defines functions and returns. When executed (`./utils.sh`), `main "$@"` runs at the bottom. This is the Python `if __name__ == "__main__":` equivalent — extremely common in well-designed shell libraries.
- **Why `return` vs `exit` is the practical difference**: `return` is only valid INSIDE a function OR in a sourced script (it stops processing without killing the shell). `exit` always kills the current process. If your script may be sourced and you `exit 1` on an error, you just killed the user's terminal session! Defensive pattern: define `die() { [[ "${BASH_SOURCE[0]}" != "${0}" ]] && return 1 || exit 1; }` and call `die` instead of `exit`. The `return` works when sourced, `exit` works when executed.
- **Cross-shell portability is hard**: `BASH_SOURCE` is bash-only, `ZSH_EVAL_CONTEXT` is zsh-only, fish has its own `status` API. A truly portable detector is hard. One workaround: write two scripts — the bash/zsh detector idiom in one file, the fish one in another. Another: use Python or another scripting language with predictable behaviour if you need true cross-shell. POSIX sh has NOTHING here — `$0` reflects the invoking process even when sourced, with no other introspection. If your audience is "POSIX sh users", you cannot reliably support the dual-mode pattern.
- **pwsh dot-sourcing vs module-import vs script-execution** are THREE distinct modes: (1) `.\script.ps1` — executes in a CHILD scope (variables defined inside don't leak out); (2) `. .\script.ps1` — dot-sourcing, runs in the CALLER's scope (variables leak out — used for rc files); (3) `Import-Module .\script.ps1` — formally imports as a module with explicit `Export-ModuleMember` controls. `$MyInvocation.InvocationName` returns `.\script.ps1`, `.`, and `Import-Module` respectively. To support all three modes, check `$MyInvocation.MyCommand.Path` for the file path, and use `$MyInvocation.InvocationName` for behavioural branching.
- **Subprocess vs sourced — different lifecycle implications**: sourced code SHARES the parent shell's environment (variables, functions, cwd, traps); subprocess code starts fresh. A sourced script that does `cd /tmp` changes the parent's cwd; an executed one does not. A sourced script's `trap "echo bye" EXIT` fires when the PARENT exits (potentially never within your test session); an executed one fires at script end. Both behaviours are useful — but using the wrong one causes "why does my deployment cd into /tmp permanently after running deploy.sh" or "why is the trap not firing when I source the file" bugs. Knowing whether you're sourced is the first step to handling it.
Related commands
Related tasks
- Enable strict mode for a shell script— Make scripts fail loudly on errors — abort on the first failed command, treat unset variables as errors, and propagate pipe failures. The opposite of "fail silently and corrupt data".
- Source an env file into the current shell— Load variables from a `.env`-style file into the current shell session — for project-specific secrets, dev-mode flags, and tool-version locks.
- Set an environment variable persistently— Make an environment variable available in every NEW shell session (not just the current one) — for setting `JAVA_HOME`, adding to `PATH`, configuring API tokens for daily-use scripts.
- Detect the current shell— Determine which shell (bash / zsh / fish / pwsh / cmd) is currently running — for conditional dotfile loading and script-detection scenarios.