Last active
December 14, 2018 09:35
-
-
Save gliech/184dc7566821442202f21dfe15e2b7ff to your computer and use it in GitHub Desktop.
Speed comparison of some common ways to check for truthy values in Bash and a snippet of my full implementation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Preface: | |
# There is nothing wrong with using 'if [[ $VAR == yes ]]; then' for evaluating | |
# internal variables. The following is mainly intended for cases where you do | |
# not have full control over the variable in question, like user input or envi- | |
# ronment variables. I am of the belief however that no one should under any | |
# condition use something like this: | |
# | VAR=true | |
# | if $VAR; then | |
# | do_something | |
# | fi | |
# I could harp on about how calling an external command is much less effective | |
# than using a bash built-in (and believe me, it is over 3000 times slower). But | |
# I won't, because the time you have spent in your life waiting for | |
# /usr/bin/true to execute is probably less than it took to read this paragraph | |
# so far, and the main reason to use this is because it looks good. The real | |
# problem with this method is that if you use it, it will become a habit. And if | |
# it becomes a habit, at some point you will use it to evaluate some form of ex- | |
# ternal input. And when that happens, this: | |
# | SOME_ENV_VAR=true | |
# will work just fine, while this: | |
# | SOME_ENV_VAR=True | |
# will either evaluate to false or crash your script, depending on how errors | |
# are handled, and this: | |
# | SOME_ENV_VAR=yes | |
# will fuck you up. Don't believe me? Just execute 'yes &' in a terminal emula- | |
# tor and see what happens. | |
# Here is what I ended up doing: | |
# Bash 4 or bust | |
if (( BASH_VERSINFO[0] < 4 )); then | |
echo "Please get a newer version of Bash." | |
else | |
# Basic safe boolean evaluation. See this Gist for details: | |
# https://gist.github.com/gliech/184dc7566821442202f21dfe15e2b7ff | |
function truthy { | |
if [[ "${1,,}" == @(y|yes|on|true|1) ]]; then | |
return 0 | |
fi | |
return 1 | |
} | |
function falsy { | |
if [[ "${1,,}" == @(n|no|off|false|0) ]]; then | |
return 0 | |
fi | |
return 1 | |
} | |
# The following function is meant to be used like this: | |
# | true_false_default $VAR command -with -arguments | |
# The function will still fail if the default is reached. If a different | |
# behaviour is desired, it can be achieved like so: | |
# | true_false_default $VAR "{ | |
# | command -with -arguments | |
# | another \"command with an argument\" | |
# | return 0 | |
# | }" | |
# The encapsulation in a compound command is not strictly necessary here, but | |
# it makes the whole construct look a lot neater imo. Also make sure to use | |
# return not exit unless you really mean to. | |
function true_false_default { | |
case "${1,,}" in | |
y | yes | on | true | 1) | |
return 0 ;; | |
n | no | off | false | 0) | |
return 1 ;; | |
esac | |
shift | |
eval "$@" | |
return 1 | |
} | |
# Uncomment this if you include these functions in your bashrc and want to use | |
# them abso-fucking-lutely everywhere. | |
# export -f truthy falsy true_false_default | |
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# The version you will find in /etc/init.d/functions | |
function truthy_init { | |
case "$1" in | |
[tT] | [yY] | [yY][eE][sS] | [oO][nN] | [tT][rR][uU][eE] | 1) | |
return 0 ;; | |
esac | |
return 1 | |
} | |
# What many people apperantly do | |
function truthy_shopt { | |
last_nocasematch=$(shopt -p nocasematch; true) | |
shopt -s nocasematch | |
case "$1" in | |
t | y | yes | on | true | 1) | |
code=0 ;; | |
* ) | |
code=1 ;; | |
esac | |
$last_nocasematch | |
return $code | |
} | |
# What I would do | |
function truthy_stringop_match { | |
# @() supposedly requires extglob but it seems to work with shopt -u extglob | |
# for me | |
if [[ "${1,,}" == @(t|y|yes|on|true|1) ]]; then | |
return 0 | |
fi | |
return 1 | |
} | |
# For when you really don't want to use @(). ${,,} still requires Bash 4 though. | |
function truthy_stringop_case { | |
case "${1,,}" in | |
t | y | yes | on | true | 1) | |
return 0 ;; | |
esac | |
return 1 | |
} | |
# Continue was added to measure the execution time of the test setup. The test | |
# will execute 'continue 1' which is what continue defaults to anyway. | |
function_array=( | |
truthy_init | |
truthy_shopt | |
truthy_stringop_match | |
truthy_stringop_case | |
continue | |
) | |
for func in "${function_array[@]}"; do | |
printf $func | |
time (for i in {1..2000}; do $func 1; done) | |
echo ' ' | |
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
truthy_init | |
real 0m0,034s | |
user 0m0,034s | |
sys 0m0,000s | |
truthy_shopt | |
real 0m1,267s | |
user 0m0,757s | |
sys 0m0,614s | |
truthy_stringop_match | |
real 0m0,034s | |
user 0m0,033s | |
sys 0m0,000s | |
truthy_stringop_case | |
real 0m0,033s | |
user 0m0,032s | |
sys 0m0,001s | |
continue | |
real 0m0,007s | |
user 0m0,007s | |
sys 0m0,000s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
variable=yes | |
printf "/usr/bin/true" | |
time (for i in {1..50000}; do /usr/bin/true ; done) | |
printf "\nCondtional Expression" | |
time (for i in {1..50000}; do [[ $variable == yes ]]; done) | |
printf "\nControl" | |
time (for i in {1..50000}; do :; done) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/usr/bin/true | |
real 0m51,519s | |
user 0m15,902s | |
sys 0m40,313s | |
Condtional Expression | |
real 0m0,113s | |
user 0m0,109s | |
sys 0m0,004s | |
Control | |
real 0m0,097s | |
user 0m0,093s | |
sys 0m0,004s | |
=> /usr/bin/true is (51.519s - 0.097s) / (0.113s - 0.097s) = 3,213.875 times slower than conditional expressions | |
‾‾‾‾‾‾‾‾‾ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment