Skip to content

Instantly share code, notes, and snippets.

@mathieucaroff
Created November 6, 2018 22:39
Show Gist options
  • Save mathieucaroff/4e8178861a3b42cd3fc9c856404ceba1 to your computer and use it in GitHub Desktop.
Save mathieucaroff/4e8178861a3b42cd3fc9c856404ceba1 to your computer and use it in GitHub Desktop.
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