Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Test shell variables to know how to determine path to the current shell script. Includes sample script output.
#!/bin/bash
# test-shell-default-variables.sh
# Usage examples (you might want to `sudo apt install zsh ksh`):
#
# ./test-shell-default-variables.sh dash bash
# ./test-shell-default-variables.sh dash bash zsh ksh
# ./test-shell-default-variables.sh dash bash zsh ksh | less -R
# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.
# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.
# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.
echolor() {
echo -e "\e[1;36m$@\e[0m"
}
tell_file() {
echo File \`"$1"\` is:
echo \`\`\`
cat "$1"
echo \`\`\`
echo
}
SHELL_ARRAY=("$@")
test_command() {
for shell in "${SHELL_ARRAY[@]}"
do
prepare "$shell"
cmd="$(eval echo $1)"
# echo "cmd: $cmd"
printf '%-4s: ' "$shell"
{ env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
teardown
done
echo
}
prepare () {
shell="$1"
PATH="$PWD/$shell/sh:$PATH"
}
teardown() {
PATH="${PATH#*:}"
}
###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
mkdir "$shell"
ln -sT "/bin/$shell" "$shell/sh"
done
echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"
tell_file sourcer.sh
###
### run
###
test_expression() {
local expr="$1"
# prepare
echo "echo $expr" > printer.sh
tell_file printer.sh
# run
cmd='$shell ./printer.sh'
echolor "\`$cmd\` (simple invocation) ($expr):"
test_command "$cmd"
# cmd='sh ./printer.sh'
# echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
# test_command "$cmd"
cmd='$shell ./sourcer.sh'
echolor "\`$cmd\` (via sourcing) ($expr):"
test_command "$cmd"
# cmd='sh ./sourcer.sh'
# echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
# test_command "$cmd"
cmd='$shell ./linked.sh'
echolor "\`$cmd\` (via symlink) ($expr):"
test_command "$cmd"
# cmd='sh ./linked.sh'
# echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
# test_command "$cmd"
echolor "------------------------------------------"
echo
}
test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'
###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
rm "$shell/sh"
rm -d "$shell"
done
rm sourcer.sh
rm linked.sh
rm printer.sh

What did we learn ?

$BASH_SOURCE

  • $BASH_SOURCE works in bash and only in bash.
  • The only difference with $0 is when the current file was sourced by another file. In that case, $BASH_PROFILE contains the name of the sourced file, rather than that of the souring file.

$0

  • In zsh, $0 has the same value as $BASH_SOURCE in bash.

$_

  • $_ is left untouched by dash and ksh.
  • In bash and zsh, $_ decays to the last argument of the last call.
  • bash initializes $_ to "bash".
  • zsh leaves $_ untouched. (when sourcing, it`s just the result of the "last argument" rule).

Symlinks

  • When a script is called through a symlink, no variable contains any reference to the destination of the link, only its name.

ksh

  • Regarding those tests, ksh behaves like dash.
File `sourcer.sh` is:
```
. ./printer.sh
```
File `printer.sh` is:
```
echo $BASH_SOURCE
```
`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash:
bash: ./printer.sh
zsh :
ksh :
`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash:
bash: ./printer.sh
zsh :
ksh :
`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash:
bash: ./linked.sh
zsh :
ksh :
------------------------------------------
File `printer.sh` is:
```
echo $0
```
`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh
`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh
`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh
------------------------------------------
File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```
`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
------------------------------------------
File `printer.sh` is:
```
echo $_
```
`$shell ./printer.sh` (simple invocation) ($_):
dash:
bash: bash
zsh :
ksh :
`$shell ./sourcer.sh` (via sourcing) ($_):
dash:
bash: bash
zsh : ./printer.sh
ksh :
`$shell ./linked.sh` (via symlink) ($_):
dash:
bash: bash
zsh :
ksh :
------------------------------------------
#!/bin/bash
# test-shell-default-variables.sh
# Usage examples (you might want to `sudo apt install zsh ksh`):
#
# ./test-shell-default-variables.sh dash bash
# ./test-shell-default-variables.sh dash bash zsh ksh
# ./test-shell-default-variables.sh dash bash zsh ksh | less -R
# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.
# The "when named `sh`" tests are commented because for every shell I tested
# (dash, bash, zsh and ksh), the output was the same as that of dash.
# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.
echolor() {
echo -e "\e[1;36m$@\e[0m"
}
tell_file() {
echo File \`"$1"\` is:
echo \`\`\`
cat "$1"
echo \`\`\`
echo
}
SHELL_ARRAY=("$@")
test_command() {
for shell in "${SHELL_ARRAY[@]}"
do
prepare "$shell"
cmd="$(eval echo $1)"
# echo "cmd: $cmd"
printf '%-4s: ' "$shell"
{ env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
teardown
done
echo
}
prepare () {
shell="$1"
PATH="$PWD/$shell/sh:$PATH"
}
teardown() {
PATH="${PATH#*:}"
}
###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
mkdir "$shell"
ln -sT "/bin/$shell" "$shell/sh"
done
echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"
tell_file sourcer.sh
###
### run
###
test_expression() {
local expr="$1"
# prepare
echo "echo $expr" > printer.sh
tell_file printer.sh
# run
cmd='$shell ./printer.sh'
echolor "\`$cmd\` (simple invocation) ($expr):"
test_command "$cmd"
# cmd='sh ./printer.sh'
# echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
# test_command "$cmd"
cmd='$shell ./sourcer.sh'
echolor "\`$cmd\` (via sourcing) ($expr):"
test_command "$cmd"
# cmd='sh ./sourcer.sh'
# echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
# test_command "$cmd"
cmd='$shell ./linked.sh'
echolor "\`$cmd\` (via symlink) ($expr):"
test_command "$cmd"
# cmd='sh ./linked.sh'
# echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
# test_command "$cmd"
echolor "------------------------------------------"
echo
}
test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'
###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
rm "$shell/sh"
rm -d "$shell"
done
rm sourcer.sh
rm linked.sh
rm printer.sh
true '
# Sample output:
`$ ./test-shell-default-variables.sh {da,ba,z,k}sh`
``````
File `sourcer.sh` is:
```
. ./printer.sh
```
File `printer.sh` is:
```
echo $BASH_SOURCE
```
`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash:
bash: ./printer.sh
zsh :
ksh :
`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash:
bash: ./printer.sh
zsh :
ksh :
`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash:
bash: ./linked.sh
zsh :
ksh :
------------------------------------------
File `printer.sh` is:
```
echo $0
```
`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh
`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh
`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh
------------------------------------------
File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```
`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash:
bash: c
zsh : c
ksh :
------------------------------------------
File `printer.sh` is:
```
echo $_
```
`$shell ./printer.sh` (simple invocation) ($_):
dash:
bash: bash
zsh :
ksh :
`$shell ./sourcer.sh` (via sourcing) ($_):
dash:
bash: bash
zsh : ./printer.sh
ksh :
`$shell ./linked.sh` (via symlink) ($_):
dash:
bash: bash
zsh :
ksh :
------------------------------------------
``````
'
true '
# What did we learn ?
### `$BASH_SOURCE`
* `$BASH_SOURCE` works in bash and only in bash.
* The only difference with `$0` is when the current file was sourced by
another file. In that case, `$BASH_PROFILE` contains the name of the
sourced file, rather than that of the souring file.
### `$0`
* In zsh, `$0` has the same value as `$BASH_SOURCE` in bash.
### `$_`
* `$_` is left untouched by dash and ksh.
* In bash and zsh, `$_` decays to the last argument of the last call.
* bash initializes `$_` to "bash".
* zsh leaves `$_` untouched.
(when sourcing, it`s just the result of the "last argument" rule).
### Symlinks
* When a script is called through a symlink, no variable contains any
reference to the destination of the link, only its name.
### ksh
* Regarding those tests, ksh behaves like `dash`.
'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.