View on GitHub

bash-funk

bash-funk is a collection of useful commands for Bash 3.2 or higher.

Bash-Funk “misc” module

The following statements are automatically executed when this module loads:

function -timeout() {
   if [[ $# < 2 || ${1:-} == "--help" ]]; then
      echo "Usage: ${FUNCNAME[0]} TIMEOUT COMMAND [ARG]..."
      echo "Executes the COMMAND and aborts if it does not finish within the given TIMEOUT in seconds."
      [[ ${1:-} == "--help" ]] && return 0 || return 1
   fi
   if hash timeout 2>/dev/null; then
      timeout "$@"
   elif hash gtimeout 2>/dev/null; then
      # MacOS: https://stackoverflow.com/a/21118126/5116073
      gtimeout "$@"
   else
      # see: http://mywiki.wooledge.org/BashFAQ/068
      perl -e 'alarm shift; exec @ARGV' "$@"
   fi
}

The following commands are available when this module is loaded:

  1. -choose
  2. -env
  3. -help
  4. -please
  5. -reload
  6. -root
  7. -test-all-misc
  8. -tweak-bash
  9. -update
  10. -var-exists
  11. -wait
  12. -weather

License

SPDX-FileCopyrightText: © Vegard IT GmbH (https://vegardit.com)
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

-choose

Usage: -choose [OPTION]... OPTION1 [OPTION]...

Prompts the user to choose one entry of the given list of options.

Parameters:
  OPTION (1 or more required)
      Allowed options to choose from.

Options:
    --assign VARNAME
        Assigns the selected value to the variable with the given name instead of printing to stdout.
    --default OPTION
        The option to pre-selected.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

local selectedIndex=0 ESC=$(echo -e "\033") redraw=1 index dialogFD option

# when in a subshell use stderr to render the dialog, so capturing stdout will only contain the selected value
[ -t 1 ] && dialogFD=1 || dialogFD=2

# pre-select if default value was given
if [[ ${_default:-} ]]; then
   for index in "${!_OPTION[@]}"; do
      if [[ $_default == ${_OPTION[$index]} ]]; then
         selectedIndex=$index
      fi
   done
fi

while true; do
   if [[ $redraw ]]; then
      for index in "${!_OPTION[@]}"; do
         option="${_OPTION[$index]}"
         option="${option//$'\n'/\\n}"
         option="${option:0:$(( COLUMNS - 6 ))}"
         if (( index == selectedIndex )); then
            >&$dialogFD echo -e " \033[1m* $option\033[22m"
         else
            >&$dialogFD echo "   $option"
         fi
      done
    fi
    local key= key2= key3= key4=
    read -sN1 key && \
    read -sN1 -t 0.001 key2 && \
    read -sN1 -t 0.001 key3 && \
    read -sN1 -t 0.001 key4 || true
    key=${key}${key2}${key3}${key4}

    case $key in
      ${ESC}*A)
         if (( selectedIndex > 0 )); then
            (( selectedIndex-- ))
            redraw=1
         fi
        ;;
      ${ESC}*B)
         if (( selectedIndex + 1 < ${#_OPTION[@]} )); then
            (( selectedIndex++ ))
            redraw=1
         fi
        ;;
      $ESC)
         echo >&2
         echo "Aborting on user request" >&2
         return 1
        ;;
      *)
         if [[ $key == "" || $key == $'\n' ]]; then
            if [[ $_assign ]]; then
               eval "$_assign=\"${_OPTION[$selectedIndex]//\"/\\\"}\""
            else
               echo "${_OPTION[$selectedIndex]}";
            fi
            return 0
         fi
         redraw=
        ;;
   esac

   if [[ $redraw ]]; then
      -cursor-pos --fd $dialogFD --up "$(( ${#_OPTION[@]} ))"
   fi
done

-env

Usage: -env [OPTION]...

Prints all exported environment variables.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

export -p | cut -d' ' -f3-

-help

Usage: -help [OPTION]...

Prints the online help of all bash-funk commands.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

for helpfunc in $(compgen -A function -- -help-); do
   $helpfunc
done | sort

-please

Usage: -please [OPTION]...

Re-runs the previously entered command with sudo.

Requirements:
  + Command 'sudo' must be available.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

local cmd="$(echo $(fc -ln -1))"

if [[ $cmd == sudo* ]]; then
   echo "-please: Last command '$cmd' was already executed with sudo."
   return 1
elif [[ $cmd == -please* ]]; then
   echo "-please: Executing last command '$cmd' with sudo has no use."
   return 1
fi

[[ $__interactive ]] && echo -e "Executing last command [\033[35m$cmd\033[0m] with sudo..." || true
sudo "$BASH" -c "$cmd"

-reload

Usage: -reload [OPTION]...

Reloads bash-funk.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ ! ${BASH_FUNK_ROOT} ]]; then
   echo "-reload: Error: BASH_FUNK_ROOT variable is not defined."
   return 1
fi

if [[ ! -r ${BASH_FUNK_ROOT}/bash-funk.sh ]]; then
   echo "-reload: Error: File [${BASH_FUNK_ROOT}/bash-funk.sh] is not readable by user [$USER]."
   return 1
fi

source ${BASH_FUNK_ROOT}/bash-funk.sh

-root

Usage: -root [OPTION]...

Starts an interactive shell as root user. Same as 'sudo -i'.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

sudo -i

-test-all-misc

Usage: -test-all-misc [OPTION]...

Performs a selftest of all functions of this module by executing each function with option '--selftest'.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

-choose --selftest && echo || return 1
-env --selftest && echo || return 1
-help --selftest && echo || return 1
-please --selftest && echo || return 1
-reload --selftest && echo || return 1
-root --selftest && echo || return 1
-tweak-bash --selftest && echo || return 1
-update --selftest && echo || return 1
-var-exists --selftest && echo || return 1
-wait --selftest && echo || return 1
-weather --selftest && echo || return 1

-tweak-bash

Usage: -tweak-bash [OPTION]...

Performs some usability configurations of Bash.

Options:
-v, --verbose
        Prints additional information during command execution.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

#
# enable and configure command history
#
set -o history
export HISTFILE=~/.bash_funk_history
export HISTSIZE=10000
export HISTFILESIZE=$HISTSIZE
export HISTCONTROL=ignorespace:ignoredups
export HISTTIMEFORMAT="%F %T "
export HISTIGNORE="&:?:??:clear:exit:pwd"
history -r

#
# Readline productivity tweaks, see https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html
#
if [[ $- == *i* ]]; then
   bind '"\e[A": history-search-backward' # enable history searching backward using arrow-up
   bind '"\e[B": history-search-forward'  # enable history searching forward using arrow-down
   bind 'set enable-keypad on'            # try to enable the application keypad

   # improve auto-completion
   bind '"\e[6~": menu-complete'           # enable Pg-Up/Down to cycle through completion candidates
   bind '"\e[5~": menu-complete-backward'  # enable Pg-Up/Down to cycle through completion candidates
   bind 'set show-all-if-ambiguous on'     # show words which have more than one possible completion immediately instead of ringing the bell
   bind 'set show-all-if-unmodified on'    # show words which have more than one possible completion without any possible partial completion immediately instead of ringing the bell.
   bind 'set colored-completion-prefix on' 2>/dev/null # highlights the common prefix of the set of possible completions. Requires Bash 4.4 or higher
   bind 'set completion-ignore-case on'    # perform case-insensitive filename matching and completion
fi

# make ls colorful by default except on MacOS where it is not supported
[[ $OSTYPE == "darwin"* ]] || alias -- ls="command ls --color=auto"

#
# aliases
#
alias -- grep="command grep --colour=auto"
alias -- gh='command history | grep'
alias -- l="ll"
alias -- ll="-ll"
alias -- ++="-cd-down"
alias -- --="-cd-hist"
alias -- ..="-cd-up"
alias -- ...="command cd ../.."
alias -- -="command cd -"
if hash mc 2>/dev/null && [[ -e /usr/lib/mc/mc-wrapper.sh ]]; then
   # see https://stackoverflow.com/questions/39017391/how-to-make-midnight-commander-exit-to-its-current-directory
   alias mc='. /usr/lib/mc/mc-wrapper.sh'
fi

#
# Bash productivity options, see http://wiki.bash-hackers.org/internals/shell_options
#
local opt opts=(autocd checkwinsize dirspell direxpand extglob globstar histappend)
for opt in ${opts[@]}; do
   if shopt -s $opt &>/dev/null; then
      [[ $_verbose ]] && echo "shopt -s $opt => ENABLED"
   else
      [[ $_verbose ]] && echo "shopt -s $opt => UNSUPPORTED"
   fi
done

#
# cygwin/msys tweaks
#
case "$OSTYPE" in
   cygwin)
      for drive in {a..z}; do
         if [[ -e /cygdrive/${drive} ]]; then
            alias -- "${drive}:"="cd /cygdrive/${drive}"
            alias -- "${drive^^}:"="cd /cygdrive/${drive}"
         fi
      done
      if ! hash sudo &>/dev/null; then
         alias -- sudo="cygstart --action=runas"
      fi
     ;;
   msys)
      for drive in {a..z}; do
         if [[ -e /${drive} ]]; then
            alias -- "${drive}:"="cd /${drive}"
            alias -- "${drive^^}:"="cd /${drive}"
         fi
      done
      if ! hash sudo &>/dev/null; then
         alias -- sudo="cygstart --action=runas"
      fi
     ;;
esac

-update

Usage: -update [OPTION]...

Updates bash-funk to the latest code from github (https://github.com/vegardit/bash-funk). All local modifications are overwritten.

Options:
-r, --reload
        Reloads the bash-funk after updating.
-y, --yes
        Answer interactive prompts with 'yes'.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ ! ${BASH_FUNK_ROOT} ]]; then
   echo "-update: Error: BASH_FUNK_ROOT variable is not defined."
   return 1
fi

if [[ ! -w ${BASH_FUNK_ROOT} ]]; then
   echo "-update: Error: Directory [${BASH_FUNK_ROOT}] is not writeable by user [$USER]."
   return 1
fi

if [[ ! $_yes ]]; then
   read -p "Are you sure you want to update bash-funk located in [${BASH_FUNK_ROOT}]? (y) " -n 1 -r
   echo
   if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      echo "-update: Aborting on user request."
      return 0
   fi
fi

# update via SVN
if [[ -e "${BASH_FUNK_ROOT}/.svn" ]]; then
   svn revert -R "${BASH_FUNK_ROOT}" || return
   svn update "${BASH_FUNK_ROOT}" || return
   [[ $_reload ]] && -reload || true
   return
fi

# update via Git
if [[ -e "${BASH_FUNK_ROOT}/.git" ]]; then
   ( cd "${BASH_FUNK_ROOT}" && git config core.autocrlf false && git fetch && git reset origin/main --hard && git pull ) || return
   [[ $_reload ]] && -reload || true
   return
fi

# update via curl/wget
local get
if hash curl &>/dev/null; then
   get="curl -#L"
else
   if wget --help | grep -- --show-progress &>/dev/null; then
      get="wget -qO- --show-progress"
   else
      get="wget -qO-"
   fi
fi
( cd "${BASH_FUNK_ROOT}" && $get https://github.com/vegardit/bash-funk/tarball/main | tar -xzv --strip-components 1 ) || return
[[ $_reload ]] && -reload || true
return

-var-exists

Usage: -var-exists [OPTION]... VARIABLE_NAME

Determines if the given variable is declared.

Parameters:
  VARIABLE_NAME (required)
      Name of the Bash variable to check.

Options:
-v, --verbose
        Prints additional information during command execution.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$ -var-exists USER

$ -var-exists -v USER
Bash variable 'USER' exists.
$ -var-exists -v NON_EXISTANT_VARIABLE
Bash variable 'NON_EXISTANT_VARIABLE' does not exist.

Implementation:

if ${!_VARIABLE_NAME+false}; then
   [[ $_verbose ]] && echo "Bash variable '$_VARIABLE_NAME' does not exist." || true
   return 1
else
   [[ $_verbose ]] && echo "Bash variable '$_VARIABLE_NAME' exists." || true
   return 0
fi

-wait

Usage: -wait [OPTION]... SECONDS

Waits for the given number of seconds or until the key 's' pressed.

Parameters:
  SECONDS (required)
      Number of seconds to wait.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

local green="\033[1;32m"
local reset="\033[0m"
local saveCursor="\033[s"
local restoreCursor="\033[u"
local cursor9Right="\033[9C"
local cursor9Left="\033[9D"

echo -ne "Waiting for [$(date +%T --date=@$(($_SECONDS - 3600)))] until $(date +%T --date=@$(($(date +%s) + $_SECONDS))). Press [s] to skip: $cursor9Right"
for (( i = 0; i < _SECONDS; i++ )); do
   if [[ $__interactive ]]; then
      local newLine=
   else
      # adding a \n new line character to the end of the line to make the output parseable by sed which is line oriented
      local newLine="$saveCursor\n$restoreCursor"
   fi
   echo -ne "$cursor9Left$green$(date +%T --date=@$(($_SECONDS - ${i} - 3600))) $reset$newLine"
   local char=
   read -s -n1 -t1 char || :
   [[ $char == "s" ]] && break
done
echo

-weather

Usage: -weather [OPTION]... [LOCATION]

Displays local weather conditions/forecast. Uses https://wttr.in.

Parameters:
  LOCATION
      The location (name or GPS coordinates) for the weather region. If not specified location is determined based on public IP address.

Options:
    --color [WHEN] (default: 'auto', one of: [always,auto,never])
        Indicates when to colorize the output.
-f, --forcast [DAYS] (default: '1', integer: 0-3)
        Number of days to show weather forecast (1=today, 2=today+tomorrow,...).
-g, --geoservice [ID] (default: 'ubuntu.com', one of: [ubuntu.com,ip-api.com,ipapi.co,ipinfo.io])
        The geo location serivce to be used when auto-detecting the location.
-l, --lang [LANG] (default: 'en')
        Language, e.g. 'en', 'de'.
-u, --units UNITS (one of: [m,u])
        System of measurement units to be used: m = metric, u = USCS.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$  -weather Berlin
    \   /     Sunny
     .-.      26 °C
  ― (   ) ―   ↗ 6 km/h
     \`-’      11 km
    /   \     0.0 mm

$  -weather Paris -l fr
     .-.      Pluie légère
    (   ).    +4(-2) °C
   (___(__)   ↗ 33 km/h
    ‘ ‘ ‘ ‘   10 km
   ‘ ‘ ‘ ‘    0.2 mm

Implementation:

local request_options+="${_units:-}${_forcast:-2}"

case "${_color:-auto}" in
  never) request_options+='T' ;;
  auto)  if ! -ansi-colors-supported 8 || [[ ! -t 1 ]]; then request_options+='T'; fi ;;
esac

hash wget &>/dev/null && local http_get="wget --timeout 5 -qO-" || local http_get="curl -sSf --max-time 5"
if [[ -z $_LOCATION ]]; then
  case ${_geoservice:-ipinfo.io} in
    ubuntu.com) _LOCATION=$($http_get http://geoip.ubuntu.com/lookup | sed 's/^.*<Latitude>\([0-9.]\{,7\}\)<\/Latitude><Longitude>\([0-9.]\{,7\}\).*$/\1,\2/') ;;
    ip-api.com) _LOCATION=$($http_get ip-api.com/line/?fields=lat,lon | paste -sd ',' -) ;;
    ipapi.co)   _LOCATION=$($http_get https://ipapi.co/latlong) ;;
    ipinfo.io)  _LOCATION=$($http_get https://ipinfo.io | grep loc | cut -d'"' -f4) ;;
  esac
fi

$http_get --header "Accept-Language: ${_lang:-en}" wttr.in/$_LOCATION?$request_options