mirror of
https://github.com/octoleo/hosts.git
synced 2024-11-22 12:55:11 +00:00
1115 lines
29 KiB
Bash
Executable File
1115 lines
29 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# __ __
|
|
# / /_ ____ _____/ /______
|
|
# / __ \/ __ \/ ___/ __/ ___/
|
|
# / / / / /_/ (__ ) /_(__ )
|
|
# /_/ /_/\____/____/\__/____/
|
|
#
|
|
# A program for managing host file entries.
|
|
#
|
|
# Based on Bash Boilerplate: https://github.com/alphabetum/bash-boilerplate
|
|
#
|
|
# Based on prior work by:
|
|
#
|
|
# - https://github.com/nddrylliog
|
|
# - https://gist.github.com/nddrylliog/1368532
|
|
# - https://github.com/dfeyer
|
|
# - https://gist.github.com/dfeyer/1369760
|
|
#
|
|
# Original idea and interface (since changed) via:
|
|
#
|
|
# https://github.com/macmade/host-manager
|
|
#
|
|
# Updates copyright (c) 2015 William Melody • hi@williammelody.com
|
|
|
|
###############################################################################
|
|
# Strict Mode
|
|
###############################################################################
|
|
|
|
set -o nounset
|
|
set -o errexit
|
|
set -o pipefail
|
|
IFS=$'\n\t'
|
|
|
|
###############################################################################
|
|
# Globals
|
|
###############################################################################
|
|
|
|
_VERSION="2.1.6"
|
|
|
|
# DEFAULT_COMMAND
|
|
#
|
|
# The command to be run by default, when no command name is specified. If the
|
|
# environment has an existing $DEFAULT_COMMAND set, then that value is used.
|
|
DEFAULT_COMMAND="${DEFAULT_COMMAND:-list}"
|
|
|
|
# HOSTS_PATH
|
|
#
|
|
# The path to the hosts file. This will almost always be /etc/hosts
|
|
HOSTS_PATH="${HOSTS_PATH:-/etc/hosts}"
|
|
|
|
# Space and tab for regular expressions
|
|
#
|
|
# sed regular expressions have slightly different behaviors dependending on
|
|
# the environment, and POSIX [[:space:]] matches whitespace characters other
|
|
# than just space and tab. These variables provide an easier, portable way to
|
|
# test for just these two characters.
|
|
export _TAB_=$'\t'
|
|
export _SPACE_=$' '
|
|
export _TAB_SPACE_="${_TAB_}${_SPACE_}"
|
|
export _TAB_SPACE_CC_="[${_TAB_SPACE_}]"
|
|
|
|
###############################################################################
|
|
# Debug
|
|
###############################################################################
|
|
|
|
# _debug()
|
|
#
|
|
# Usage:
|
|
# _debug printf "Debug info. Variable: %s\n" "$0"
|
|
#
|
|
# A simple function for executing a specified command if the `$_USE_DEBUG`
|
|
# variable has been set. The command is expected to print a message and
|
|
# should typically be either `echo`, `printf`, or `cat`.
|
|
_debug() {
|
|
if [[ "${_USE_DEBUG:-"0"}" -eq 1 ]]
|
|
then
|
|
# Prefix debug message with "bug (U+1F41B)"
|
|
printf "🐛 "
|
|
"${@}"
|
|
printf "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
|
|
fi
|
|
}
|
|
# debug()
|
|
#
|
|
# Usage:
|
|
# debug "Debug info. Variable: $0"
|
|
#
|
|
# Print the specified message if the `$_USE_DEBUG` variable has been set.
|
|
#
|
|
# This is a shortcut for the _debug() function that simply echos the message.
|
|
debug() {
|
|
_debug echo "${@}"
|
|
}
|
|
|
|
###############################################################################
|
|
# Die
|
|
###############################################################################
|
|
|
|
# _die()
|
|
#
|
|
# Usage:
|
|
# _die printf "Error message. Variable: %s\n" "$0"
|
|
#
|
|
# A simple function for exiting with an error after executing the specified
|
|
# command. The command is expected to print a message and should typically
|
|
# be either `echo`, `printf`, or `cat`.
|
|
_die() {
|
|
# Prefix die message with "cross mark (U+274C)", often displayed as a red x.
|
|
printf "❌ "
|
|
"${@}" 1>&2
|
|
exit 1
|
|
}
|
|
# die()
|
|
#
|
|
# Usage:
|
|
# die "Error message. Variable: $0"
|
|
#
|
|
# Exit with an error and print the specified message.
|
|
#
|
|
# This is a shortcut for the _die() function that simply echos the message.
|
|
die() {
|
|
_die echo "${@}"
|
|
}
|
|
|
|
###############################################################################
|
|
# Options
|
|
###############################################################################
|
|
|
|
# Get raw options for any commands that expect them.
|
|
_RAW_OPTIONS="${*:-}"
|
|
|
|
# Steps:
|
|
#
|
|
# 1. set expected short options in `optstring` at beginning of the "Normalize
|
|
# Options" section,
|
|
# 2. parse options in while loop in the "Parse Options" section.
|
|
|
|
# Normalize Options ###########################################################
|
|
|
|
# Source:
|
|
# https://github.com/e36freak/templates/blob/master/options
|
|
|
|
# The first loop, even though it uses 'optstring', will NOT check if an
|
|
# option that takes a required argument has the argument provided. That must
|
|
# be done within the second loop and case statement, yourself. Its purpose
|
|
# is solely to determine that -oARG is split into -o ARG, and not -o -A -R -G.
|
|
|
|
# Set short options -----------------------------------------------------------
|
|
|
|
# option string, for short options.
|
|
#
|
|
# Very much like getopts, expected short options should be appended to the
|
|
# string here. Any option followed by a ':' takes a required argument.
|
|
#
|
|
# In this example, `-x` and `-h` are regular short options, while `o` is
|
|
# assumed to have an argument and will be split if joined with the string,
|
|
# meaning `-oARG` would be split to `-o ARG`.
|
|
optstring=h
|
|
|
|
# Normalize -------------------------------------------------------------------
|
|
|
|
# iterate over options, breaking -ab into -a -b and --foo=bar into --foo bar
|
|
# also turns -- into --endopts to avoid issues with things like '-o-', the '-'
|
|
# should not indicate the end of options, but be an invalid option (or the
|
|
# argument to the option, such as wget -qO-)
|
|
unset options
|
|
# while the number of arguments is greater than 0
|
|
while ((${#}))
|
|
do
|
|
case ${1} in
|
|
# if option is of type -ab
|
|
-[!-]?*)
|
|
# loop over each character starting with the second
|
|
for ((i=1; i<${#1}; i++))
|
|
do
|
|
# extract 1 character from position 'i'
|
|
c=${1:i:1}
|
|
# add current char to options
|
|
options+=("-${c}")
|
|
|
|
# if option takes a required argument, and it's not the last char
|
|
# make the rest of the string its argument
|
|
if [[ ${optstring} = *"${c}:"* && ${1:i+1} ]]
|
|
then
|
|
options+=("${1:i+1}")
|
|
break
|
|
fi
|
|
done
|
|
;;
|
|
# if option is of type --foo=bar, split on first '='
|
|
--?*=*)
|
|
options+=("${1%%=*}" "${1#*=}")
|
|
;;
|
|
# end of options, stop breaking them up
|
|
--)
|
|
options+=(--endopts)
|
|
shift
|
|
options+=("${@}")
|
|
break
|
|
;;
|
|
# otherwise, nothing special
|
|
*)
|
|
options+=("${1}")
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
# set new positional parameters to altered options. Set default to blank.
|
|
set -- "${options[@]:-}"
|
|
unset options
|
|
|
|
# Parse Options ###############################################################
|
|
|
|
# Initialize $_COMMAND_ARGV array
|
|
#
|
|
# This array contains all of the arguments that get passed along to each
|
|
# command. This is essentially the same as the program arguments, minus those
|
|
# that have been filtered out in the program option parsing loop. This array
|
|
# is initialized with $0, which is the program's name.
|
|
_COMMAND_ARGV=("${0}")
|
|
# Initialize $_CMD and `$_USE_DEBUG`, which can continue to be blank depending
|
|
# on what the program needs.
|
|
_CMD=""
|
|
_USE_DEBUG=0
|
|
|
|
while [ ${#} -gt 0 ]
|
|
do
|
|
opt="${1}"
|
|
shift
|
|
case "${opt}" in
|
|
-h|--help)
|
|
_CMD="help"
|
|
;;
|
|
--version)
|
|
_CMD="version"
|
|
;;
|
|
--debug)
|
|
_USE_DEBUG=1
|
|
;;
|
|
*)
|
|
# The first non-option argument is assumed to be the command name.
|
|
# All subsequent arguments are added to $_COMMAND_ARGV.
|
|
if [[ -n ${_CMD} ]]
|
|
then
|
|
_COMMAND_ARGV+=("${opt}")
|
|
else
|
|
_CMD="${opt}"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Set $_COMMAND_PARAMETERS to $_COMMAND_ARGV, minus the initial element, $0. This
|
|
# provides an array that is equivalent to $* and $@ within each command
|
|
# function, though the array is zero-indexed, which could lead to confusion.
|
|
#
|
|
# Use `unset` to remove the first element rather than slicing (e.g.,
|
|
# `_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]:1}")`) because under bash 3.2 the
|
|
# resulting slice is treated as a quoted string and doesn't easily get coaxed
|
|
# into a new array.
|
|
_COMMAND_PARAMETERS=(${_COMMAND_ARGV[*]})
|
|
unset _COMMAND_PARAMETERS[0]
|
|
|
|
_debug printf \
|
|
"\${_CMD}: %s\n" \
|
|
"${_CMD}"
|
|
_debug printf \
|
|
"\${_RAW_OPTIONS} (one per line):\n%s\n" \
|
|
"${_RAW_OPTIONS}"
|
|
_debug printf \
|
|
"\${_COMMAND_ARGV[*]}: %s\n" \
|
|
"${_COMMAND_ARGV[*]}"
|
|
_debug printf \
|
|
"\${_COMMAND_PARAMETERS[*]:-}: %s\n" \
|
|
"${_COMMAND_PARAMETERS[*]:-}"
|
|
|
|
###############################################################################
|
|
# Environment
|
|
###############################################################################
|
|
|
|
# $_ME
|
|
#
|
|
# Set to the program's basename.
|
|
_ME=$(basename "${0}")
|
|
|
|
_debug printf "\${_ME}: %s\n" "${_ME}"
|
|
|
|
###############################################################################
|
|
# Load Commands
|
|
###############################################################################
|
|
|
|
# Initialize $_DEFINED_COMMANDS array.
|
|
_DEFINED_COMMANDS=()
|
|
|
|
# _load_commands()
|
|
#
|
|
# Usage:
|
|
# _load_commands
|
|
#
|
|
# Loads all of the commands sourced in the environment.
|
|
_load_commands() {
|
|
|
|
_debug printf "_load_commands(): entering...\n"
|
|
_debug printf "_load_commands() declare -F:\n%s\n" "$(declare -F)"
|
|
|
|
# declare is a bash built-in shell function that, when called with the '-F'
|
|
# option, displays all of the functions with the format
|
|
# `declare -f function_name`. These are then assigned as elements in the
|
|
# $function_list array.
|
|
local function_list=($(declare -F))
|
|
|
|
for c in "${function_list[@]}"
|
|
do
|
|
# Each element has the format `declare -f function_name`, so set the name
|
|
# to only the 'function_name' part of the string.
|
|
local function_name
|
|
function_name=$(printf "%s" "${c}" | awk '{ print $3 }')
|
|
|
|
_debug printf "_load_commands() \${function_name}: %s\n" "${function_name}"
|
|
|
|
# Add the function name to the $_DEFINED_COMMANDS array unless it starts
|
|
# with an underscore or is one of the desc(), debug(), or die() functions,
|
|
# since these are treated as having 'private' visibility.
|
|
if ! ( [[ "${function_name}" =~ ^_(.*) ]] || \
|
|
[[ "${function_name}" == "desc" ]] || \
|
|
[[ "${function_name}" == "debug" ]] || \
|
|
[[ "${function_name}" == "die" ]]
|
|
)
|
|
then
|
|
_DEFINED_COMMANDS+=("${function_name}")
|
|
fi
|
|
done
|
|
|
|
_debug printf \
|
|
"commands() \${_DEFINED_COMMANDS[*]:-}:\n%s\n" \
|
|
"${_DEFINED_COMMANDS[*]:-}"
|
|
}
|
|
|
|
###############################################################################
|
|
# Main
|
|
###############################################################################
|
|
|
|
# _main()
|
|
#
|
|
# Usage:
|
|
# _main
|
|
#
|
|
# The primary function for starting the program.
|
|
#
|
|
# NOTE: must be called at end of program after all commands have been defined.
|
|
_main() {
|
|
_debug printf "main(): entering...\n"
|
|
_debug printf "main() \${_CMD} (upon entering): %s\n" "${_CMD}"
|
|
|
|
# If $_CMD is blank, then set to `$DEFAULT_COMMAND`
|
|
if [[ -z ${_CMD} ]]
|
|
then
|
|
_CMD="${DEFAULT_COMMAND}"
|
|
fi
|
|
|
|
# Load all of the commands.
|
|
_load_commands
|
|
|
|
# If the command is defined, run it, otherwise return an error.
|
|
if _contains "${_CMD}" "${_DEFINED_COMMANDS[*]:-}"
|
|
then
|
|
# Pass all comment arguments to the program except for the first ($0).
|
|
${_CMD} "${_COMMAND_PARAMETERS[@]:-}"
|
|
else
|
|
_die printf "Unknown command: %s\n" "${_CMD}"
|
|
fi
|
|
}
|
|
|
|
###############################################################################
|
|
# Utility Functions
|
|
###############################################################################
|
|
|
|
# _function_exists()
|
|
#
|
|
# Usage:
|
|
# _function_exists "possible_function_name"
|
|
#
|
|
# Takes a potential function name as an argument and returns whether a function
|
|
# exists with that name.
|
|
_function_exists() {
|
|
[ "$(type -t "${1}")" == 'function' ]
|
|
}
|
|
|
|
# _command_exists()
|
|
#
|
|
# Usage:
|
|
# _command_exists "possible_command_name"
|
|
#
|
|
# Takes a potential command name as an argument and returns whether a command
|
|
# exists with that name.
|
|
#
|
|
# For information on why `hash` is used here, see:
|
|
# http://stackoverflow.com/a/677212
|
|
_command_exists() {
|
|
hash "${1}" 2>/dev/null
|
|
}
|
|
|
|
# _contains()
|
|
#
|
|
# Usage:
|
|
# _contains "$item" "${list[*]}"
|
|
#
|
|
# Takes an item and a list and determines whether the list contains the item.
|
|
_contains() {
|
|
local test_list=(${*:2})
|
|
for _test_element in "${test_list[@]:-}"
|
|
do
|
|
_debug printf "_contains() \${_test_element}: %s\n" "${_test_element}"
|
|
if [[ "${_test_element}" == "${1}" ]]
|
|
then
|
|
_debug printf "_contains() match: %s\n" "${1}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# _join()
|
|
#
|
|
# Usage:
|
|
# _join "," a b c
|
|
# _join "${an_array[@]}"
|
|
#
|
|
# Takes a separator and a list of items, joining that list of items with the
|
|
# separator.
|
|
_join() {
|
|
local separator
|
|
local target_array
|
|
local dirty
|
|
local clean
|
|
separator="${1}"
|
|
target_array=(${@:2})
|
|
dirty="$(printf "${separator}%s" "${target_array[@]}")"
|
|
clean="${dirty:${#separator}}"
|
|
printf "%s" "${clean}"
|
|
}
|
|
|
|
# _command_argv_includes()
|
|
#
|
|
# Usage:
|
|
# _command_argv_includes "an_argument"
|
|
#
|
|
# Takes a possible command argument and determines whether it is included in
|
|
# the command argument list.
|
|
#
|
|
# This is a shortcut for simple cases where a command wants to check for the
|
|
# presence of options quickly without parsing the options again.
|
|
_command_argv_includes() {
|
|
_contains "${1}" "${_COMMAND_ARGV[*]}"
|
|
}
|
|
|
|
# _blank()
|
|
#
|
|
# Usage:
|
|
# _blank "$an_argument"
|
|
#
|
|
# Takes an argument and returns true if it is blank.
|
|
_blank() {
|
|
[[ -z "${1:-}" ]]
|
|
}
|
|
|
|
# _present()
|
|
#
|
|
# Usage:
|
|
# _present "$an_argument"
|
|
#
|
|
# Takes an argument and returns true if it is present.
|
|
_present() {
|
|
[[ -n "${1:-}" ]]
|
|
}
|
|
|
|
# _verify_write_permissions
|
|
#
|
|
# Print a helpful error message when the specified operation can't be
|
|
# performed due to the lack of write permissions.
|
|
_verify_write_permissions() {
|
|
if ! test -w "${HOSTS_PATH}"
|
|
then
|
|
_die printf \
|
|
"You don't have permission to perform this operation. Try again with:
|
|
sudo !!\n"
|
|
fi
|
|
}
|
|
|
|
###############################################################################
|
|
# desc
|
|
###############################################################################
|
|
|
|
# desc()
|
|
#
|
|
# Usage:
|
|
# desc command "description"
|
|
#
|
|
# Create a description for a specified command name. The command description
|
|
# text can be passed as the second argument or as standard input.
|
|
#
|
|
# To make the description text available to other functions, desc() assigns the
|
|
# text to a variable with the format $_desc_function_name
|
|
#
|
|
# NOTE:
|
|
#
|
|
# The `read` form of assignment is used for a balance of ease of
|
|
# implementation and simplicity. There is an alternative assignment form
|
|
# that could be used here:
|
|
#
|
|
# var="$(cat <<'EOM'
|
|
# some message
|
|
# EOM
|
|
# )
|
|
#
|
|
# However, this form appears to require trailing space after backslases to
|
|
# preserve newlines, which is unexpected. Using `read` simply requires
|
|
# escaping backslashes, which is more common.
|
|
desc() {
|
|
set +e
|
|
[[ -z ${1} ]] && _die printf "desc: No command name specified.\n"
|
|
if [[ -n ${2:-} ]]
|
|
then
|
|
read -d '' "_desc_${1}" <<EOM
|
|
${2}
|
|
EOM
|
|
_debug printf "desc() set with argument: _desc_%s\n" "${1}"
|
|
else
|
|
read -d '' "_desc_${1}"
|
|
_debug printf "desc() set with pipe: _desc_%s\n" "${1}"
|
|
fi
|
|
set -e
|
|
}
|
|
|
|
# _print_desc()
|
|
#
|
|
# Usage:
|
|
# _print_desc <command>
|
|
#
|
|
# Prints the description for a given command, provided the description has been
|
|
# set using the desc() function.
|
|
_print_desc() {
|
|
local var="_desc_${1}"
|
|
if [[ -n ${!var:-} ]]
|
|
then
|
|
printf "%s\n" "${!var}"
|
|
else
|
|
printf "No additional information for \`%s\`\n" "${1}"
|
|
fi
|
|
}
|
|
|
|
###############################################################################
|
|
# Default Commands
|
|
###############################################################################
|
|
|
|
# Version #####################################################################
|
|
|
|
desc "version" <<EOM
|
|
Usage:
|
|
${_ME} (version | --version)
|
|
|
|
Description:
|
|
Display the current program version.
|
|
|
|
To save you the trouble, the current version is ${_VERSION}
|
|
EOM
|
|
version() {
|
|
printf "%s\n" "${_VERSION}"
|
|
}
|
|
|
|
# Help ########################################################################
|
|
|
|
desc "help" <<EOM
|
|
Usage:
|
|
${_ME} help [<command>]
|
|
|
|
Description:
|
|
Display help information for ${_ME} or a specified command.
|
|
EOM
|
|
help() {
|
|
if [[ ${#_COMMAND_ARGV[@]} = 1 ]]
|
|
then
|
|
cat <<EOM
|
|
__ __
|
|
/ /_ ____ _____/ /______
|
|
/ __ \/ __ \/ ___/ __/ ___/
|
|
/ / / / /_/ (__ ) /_(__ )
|
|
/_/ /_/\____/____/\__/____/
|
|
|
|
A program for managing host file entries.
|
|
|
|
Version: ${_VERSION}
|
|
|
|
Usage:
|
|
${_ME}
|
|
${_ME} add <ip> <hostname> [<comment>]
|
|
${_ME} disable (<ip> | <hostname> | <search string>)
|
|
${_ME} disabled
|
|
${_ME} edit
|
|
${_ME} enable (<ip> | <hostname> | <search string>)
|
|
${_ME} enabled
|
|
${_ME} file
|
|
${_ME} list [enabled | disabled | <search string>]
|
|
${_ME} show (<ip> | <hostname> | <search string>)
|
|
${_ME} remove (<ip> | <hostname> | <search string>) [--force]
|
|
${_ME} -h | --help
|
|
${_ME} --version
|
|
|
|
Options:
|
|
-h --help Display this help information.
|
|
--version Display version information.
|
|
|
|
Help:
|
|
${_ME} help [<command>]
|
|
|
|
$(commands)
|
|
EOM
|
|
else
|
|
_print_desc "${1}"
|
|
fi
|
|
}
|
|
|
|
# Command List ################################################################
|
|
|
|
desc "commands" <<EOM
|
|
Usage:
|
|
${_ME} commands [--raw]
|
|
|
|
Options:
|
|
--raw Display the command list without formatting.
|
|
|
|
Description:
|
|
Display the list of available commands.
|
|
EOM
|
|
commands() {
|
|
if _command_argv_includes "--raw"
|
|
then
|
|
printf "%s\n" "${_DEFINED_COMMANDS[@]}"
|
|
else
|
|
printf "Available commands:\n"
|
|
printf " %s\n" "${_DEFINED_COMMANDS[@]}"
|
|
fi
|
|
}
|
|
|
|
###############################################################################
|
|
# Commands
|
|
# ========.....................................................................
|
|
#
|
|
# Example command group structure:
|
|
#
|
|
# desc example "" - Optional. A short description for the command.
|
|
# example() { : } - The command called by the user.
|
|
#
|
|
#
|
|
# desc example <<EOM
|
|
# Usage:
|
|
# $_ME example
|
|
#
|
|
# Description:
|
|
# Print "Hello, World!"
|
|
#
|
|
# For usage formatting conventions see:
|
|
# - http://docopt.org/
|
|
# - http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
|
|
# EOM
|
|
# example() {
|
|
# printf "Hello, World!\n"
|
|
# }
|
|
#
|
|
###############################################################################
|
|
|
|
# ------------------------------------------------------------------------- add
|
|
|
|
desc "add" <<EOM
|
|
Usage:
|
|
${_ME} add <ip> <hostname> [<comment>]
|
|
|
|
Description:
|
|
Add a given IP address and hostname pair, along with an optional comment.
|
|
EOM
|
|
add() {
|
|
_debug printf "add() \${1}: %s\n" "${1:-}"
|
|
_debug printf "add() \${2}: %s\n" "${2:-}"
|
|
_debug printf "add() \${3}: %s\n" "${3:-}"
|
|
|
|
_verify_write_permissions
|
|
local ip=${1:-}
|
|
local hostname=${2:-}
|
|
local comment=${*:3}
|
|
if [[ -z ${ip} ]]
|
|
then
|
|
${_ME} help add
|
|
exit 1
|
|
elif [[ -z ${hostname} ]]
|
|
then
|
|
printf "Please include a hostname\n"
|
|
${_ME} help add
|
|
exit 1
|
|
elif grep \
|
|
-e "^${ip}\t${hostname}$" \
|
|
-e "^${ip}\t${hostname}\t.*$" "${HOSTS_PATH}"
|
|
then
|
|
_die printf \
|
|
"Duplicate address/host combination, %s unchanged.\n" \
|
|
"${HOSTS_PATH}"
|
|
else
|
|
if [[ -n ${comment} ]]
|
|
then
|
|
local formatted_comment
|
|
formatted_comment=$(_join " " "${comment[@]}")
|
|
printf "%s\t%s\t# %s\n" \
|
|
"${ip}" \
|
|
"${hostname}" \
|
|
"${formatted_comment}" >> "${HOSTS_PATH}"
|
|
printf "Added:\n%s\t%s\t# %s\n" \
|
|
"${ip}" \
|
|
"${hostname}" \
|
|
"${formatted_comment}"
|
|
else
|
|
printf "%s\t%s\n" \
|
|
"${ip}" \
|
|
"${hostname}" >> "${HOSTS_PATH}"
|
|
printf "Added:\n%s\t%s\n" \
|
|
"${ip}" \
|
|
"${hostname}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# --------------------------------------------------------------------- disable
|
|
|
|
desc "disable" <<EOM
|
|
Usage:
|
|
${_ME} disable (<ip> | <hostname> | <search string>)
|
|
|
|
Description:
|
|
Disable one or more records based on a given ip address, hostname, or
|
|
search string.
|
|
EOM
|
|
disable() {
|
|
_verify_write_permissions
|
|
local search_string="${1}"
|
|
if [[ -z "${search_string}" ]]
|
|
then
|
|
${_ME} help disable
|
|
exit 1
|
|
else
|
|
_debug printf "disable() \${search_string}: %s\n" "${search_string}"
|
|
|
|
target_regex_ip="^\(${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
target_regex_commented_hostname="^\([^#]..*[${_TAB_SPACE_}]${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
target_regex_hostname="^\([^#]..*[${_TAB_SPACE_}]${search_string}\)$"
|
|
|
|
# Regular Expression Notes
|
|
#
|
|
# - Note double periods in regular expression in order to emulate /.+/,
|
|
# which apparently doesn't work properly with all versions of sed.
|
|
local targets
|
|
targets=$(
|
|
sed -n \
|
|
-e "s/${target_regex_ip}/\1/p" \
|
|
-e "s/${target_regex_commented_hostname}/\1/p" \
|
|
-e "s/${target_regex_hostname}/\1/p" \
|
|
"${HOSTS_PATH}"
|
|
)
|
|
_debug printf "disable() \${targets}: %s\n" "${targets}"
|
|
if [[ -z "${targets}" ]]
|
|
then
|
|
_die printf "Not found: %s\n" "${search_string}"
|
|
fi
|
|
|
|
printf "Disabling:\n%s\n" "${targets}"
|
|
|
|
# -i '' - in place edit. BSD sed requires extension argument, for GNU
|
|
# it's optional. More info: http://stackoverflow.com/a/16746032
|
|
sed -i '' \
|
|
-e "s/${target_regex_ip}/\#disabled: \1/g" \
|
|
-e "s/${target_regex_commented_hostname}/\#disabled: \1/g" \
|
|
-e "s/${target_regex_hostname}/\#disabled: \1/g" \
|
|
"${HOSTS_PATH}"
|
|
fi
|
|
}
|
|
|
|
# -------------------------------------------------------------------- disabled
|
|
|
|
desc "disabled" <<EOM
|
|
Usage:
|
|
${_ME} disabled
|
|
|
|
Description:
|
|
List all disabled records. This is an alias for \`hosts list disabled\`.
|
|
EOM
|
|
disabled() {
|
|
${_ME} list disabled
|
|
}
|
|
|
|
# ------------------------------------------------------------------------ edit
|
|
|
|
desc "edit" <<EOM
|
|
Usage:
|
|
${_ME} edit
|
|
|
|
Description:
|
|
Open the ${HOSTS_PATH} file in your \$EDITOR.
|
|
EOM
|
|
edit() {
|
|
_verify_write_permissions
|
|
if [[ -z "${EDITOR}" ]]
|
|
then
|
|
_die printf "\$EDITOR not set.\n"
|
|
else
|
|
"${EDITOR}" "${HOSTS_PATH}"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------- enable
|
|
|
|
desc "enable" <<EOM
|
|
Usage:
|
|
${_ME} enable (<ip> | <hostname> | <search string>)
|
|
|
|
Description:
|
|
Enable one or more disabled records based on a given ip address, hostname,
|
|
or search string.
|
|
EOM
|
|
enable() {
|
|
_verify_write_permissions
|
|
local search_string="${1}"
|
|
if [[ -z "${search_string}" ]]
|
|
then
|
|
${_ME} help enable
|
|
exit 1
|
|
else
|
|
_debug printf "enable() \${search_string}: %s\n" "${search_string}"
|
|
|
|
target_regex_ip="^\#disabled: \(${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
target_regex_commented_hostname="^\#disabled: \(..*[${_TAB_SPACE_}]${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
target_regex_hostname="^\#disabled: \(..*[${_TAB_SPACE_}]${search_string}\)$"
|
|
|
|
# Regular Expression Notes
|
|
#
|
|
# - Note double periods in regular expression in order to emulate /.+/,
|
|
# which apparently doesn't work properly with all versions of sed.
|
|
local targets
|
|
targets=$(
|
|
sed -n \
|
|
-e "s/${target_regex_ip}/\1/p" \
|
|
-e "s/${target_regex_commented_hostname}/\1/p" \
|
|
-e "s/${target_regex_hostname}/\1/p" \
|
|
"${HOSTS_PATH}"
|
|
)
|
|
_debug printf "enable() \${targets}: %s\n" "${targets}"
|
|
if [[ -z "${targets}" ]]
|
|
then
|
|
_die printf "Not found: %s\n" "${search_string}"
|
|
fi
|
|
|
|
printf "Enabling:\n%s\n" "${targets}"
|
|
|
|
# -i '' - in place edit. BSD sed requires extension argument, for GNU
|
|
# it's optional. More info: http://stackoverflow.com/a/16746032
|
|
sed -i '' \
|
|
-e "s/${target_regex_ip}/\1/g" \
|
|
-e "s/${target_regex_commented_hostname}/\1/g" \
|
|
-e "s/${target_regex_hostname}/\1/g" \
|
|
"${HOSTS_PATH}"
|
|
fi
|
|
}
|
|
|
|
# --------------------------------------------------------------------- enabled
|
|
|
|
desc "enabled" <<EOM
|
|
Usage:
|
|
${_ME} enabled
|
|
|
|
Description:
|
|
List all enabled records. This is an alias for \`hosts list enabled\`.
|
|
EOM
|
|
enabled() {
|
|
${_ME} list enabled
|
|
}
|
|
|
|
# ------------------------------------------------------------------------ file
|
|
|
|
desc "file" <<EOM
|
|
Usage:
|
|
${_ME} file
|
|
|
|
Description:
|
|
Print the entire contents of the ${HOSTS_PATH} file.
|
|
EOM
|
|
file() {
|
|
cat "${HOSTS_PATH}"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------ list
|
|
|
|
desc "list" <<EOM
|
|
Usage:
|
|
${_ME} list [enabled | disabled | <search string>]
|
|
|
|
Description:
|
|
List the existing IP / hostname pairs, optionally limited to a specified
|
|
state. When provided with a seach string, all matching enabled records will
|
|
be printed.
|
|
EOM
|
|
list() {
|
|
# Get the disabled records up front for the two cases where they are needed.
|
|
local disabled_records
|
|
disabled_records=$(
|
|
sed -n "s/^\#disabled: \(.*\)$/\1/p" "${HOSTS_PATH}"
|
|
)
|
|
|
|
if [[ -n "${1}" ]]
|
|
then
|
|
if [[ "${1}" == "disabled" ]]
|
|
then
|
|
printf "%s\n" "${disabled_records}"
|
|
elif [[ "${1}" == "enabled" ]]
|
|
then
|
|
grep -v -e '^$' -e '^\s*\#' "${HOSTS_PATH}"
|
|
else
|
|
${_ME} show "${1}"
|
|
fi
|
|
else
|
|
# NOTE: use separate expressions since using a | for the or results in
|
|
# inconsistent behavior.
|
|
grep -v -e '^$' -e '^\s*\#' "${HOSTS_PATH}"
|
|
if [[ -n "${disabled_records}" ]]
|
|
then
|
|
printf "\nDisabled:\n%s\n" "${disabled_records}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------- remove
|
|
|
|
desc "remove" <<EOM
|
|
Usage:
|
|
${_ME} remove (<ip> | <hostname> | <search string>) [--force]
|
|
${_ME} remove <ip> <hostname>
|
|
|
|
Options:
|
|
--force Skip the confirmation prompt.
|
|
|
|
Description:
|
|
Remove one or more records based on a given IP address, hostname, or search
|
|
string. If an IP and hostname are both provided, only records matching the
|
|
IP and hostname pair will be removed.
|
|
EOM
|
|
remove() {
|
|
_verify_write_permissions
|
|
local is_search_pair=0
|
|
local force_skip_prompt=0
|
|
local arguments=()
|
|
local search_ip=""
|
|
local search_hostname=""
|
|
local search_string=""
|
|
|
|
_debug printf "remove() \${1}: %s\n" "${1:-}"
|
|
_debug printf "remove() \${2}: %s\n" "${2:-}"
|
|
|
|
for arg in "${_COMMAND_ARGV[@]:-}"
|
|
do
|
|
case ${arg} in
|
|
--force)
|
|
force_skip_prompt=1
|
|
;;
|
|
*)
|
|
arguments+=(${arg})
|
|
;;
|
|
esac
|
|
done
|
|
|
|
_debug printf "remove() \${arguments[0]}: %s\n" "${arguments[0]:-}"
|
|
_debug printf "remove() \${arguments[1]}: %s\n" "${arguments[1]:-}"
|
|
_debug printf "remove() \${arguments[2]}: %s\n" "${arguments[2]:-}"
|
|
|
|
if [[ -z "${arguments[1]:-}" ]]
|
|
then
|
|
${_ME} help remove
|
|
exit 1
|
|
elif [[ -n "${arguments[2]:-}" ]]
|
|
then
|
|
search_ip="${arguments[1]}"
|
|
search_hostname="${arguments[2]}"
|
|
is_search_pair=1
|
|
_debug printf "remove() \${is_search_pair}: %s\n" "${is_search_pair}"
|
|
else
|
|
search_string="${arguments[1]:-}"
|
|
_debug printf "remove() \${search_string}: %s\n" "${search_string}"
|
|
fi
|
|
|
|
# Regular Expression Notes
|
|
#
|
|
# Note double periods in regular expression in order to emulate /.+/,
|
|
# which apparently doesn't work properly with all versions of sed.
|
|
#
|
|
# IP / Hostname pair regular expressions:
|
|
local target_regex_ip_hostname_commented="^\(${search_ip}[${_TAB_SPACE_}]*${search_hostname}[${_TAB_SPACE_}]..*\)$"
|
|
local target_regex_ip_hostname="^\(${search_ip}[${_TAB_SPACE_}]*${search_hostname}\)$"
|
|
# Search string regular expressions:
|
|
local target_regex_ip="^\(${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
local target_regex_commented_hostname="^\(..*[${_TAB_SPACE_}]${search_string}[${_TAB_SPACE_}]..*\)$"
|
|
local target_regex_hostname="^\(..*[${_TAB_SPACE_}]${search_string}\)$"
|
|
|
|
local target_records
|
|
|
|
if ((is_search_pair))
|
|
then
|
|
target_records=$(
|
|
sed -n \
|
|
-e "s/${target_regex_ip_hostname_commented}/\1/p" \
|
|
-e "s/${target_regex_ip_hostname}/\1/p" \
|
|
"${HOSTS_PATH}"
|
|
)
|
|
else
|
|
target_records=$(
|
|
sed -n \
|
|
-e "s/${target_regex_ip}/\1/p" \
|
|
-e "s/${target_regex_commented_hostname}/\1/p" \
|
|
-e "s/${target_regex_hostname}/\1/p" \
|
|
"${HOSTS_PATH}"
|
|
)
|
|
fi
|
|
|
|
if [[ -z ${target_records:-} ]]
|
|
then
|
|
printf "No matching records found.\n"
|
|
exit 1
|
|
fi
|
|
|
|
if ! ((force_skip_prompt))
|
|
then
|
|
printf "Removing the following records:\n%s\n" "${target_records}"
|
|
while true
|
|
do
|
|
read -p "Are you sure you want to proceed? [y/N] " yn
|
|
case ${yn} in
|
|
[Yy]* )
|
|
break
|
|
;;
|
|
* )
|
|
printf "Exiting...\n"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
# Regular Expression Notes
|
|
#
|
|
# -i '' - in place edit. BSD sed requires extension argument, for GNU
|
|
# it's optional. More info: http://stackoverflow.com/a/16746032
|
|
if ((is_search_pair))
|
|
then
|
|
sed -i '' \
|
|
-e "/${target_regex_ip_hostname_commented}/d" \
|
|
-e "/${target_regex_ip_hostname}/d" \
|
|
"${HOSTS_PATH}"
|
|
else
|
|
sed -i '' \
|
|
-e "/${target_regex_ip}/d" \
|
|
-e "/${target_regex_commented_hostname}/d" \
|
|
-e "/${target_regex_hostname}/d" \
|
|
"${HOSTS_PATH}"
|
|
fi
|
|
printf "Removed:\n%s\n" "${target_records}"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------ show
|
|
|
|
desc "show" <<EOM
|
|
Usage:
|
|
${_ME} show (<ip> | <hostname> | <search string>)
|
|
|
|
Description:
|
|
Print entries matching a given IP address, hostname, or search string.
|
|
EOM
|
|
show() {
|
|
if [[ -n "${1}" ]]
|
|
then
|
|
# Run `sed` before `grep` to avoid conflict that supress `sed` output.
|
|
local disabled_records
|
|
disabled_records=$(
|
|
sed -n "s/^\#\(disabled: .*${1}.*\)$/\1/p" "${HOSTS_PATH}"
|
|
)
|
|
local enabled_records
|
|
enabled_records=$(
|
|
grep "^[^#]*${1}" "${HOSTS_PATH}"
|
|
)
|
|
# Output disabled records secondly for better organization.
|
|
if [[ -n "${enabled_records}" ]]
|
|
then
|
|
printf "%s\n" "${enabled_records}"
|
|
fi
|
|
if [[ -n "${disabled_records}" ]]
|
|
then
|
|
printf "%s\n" "${disabled_records}"
|
|
fi
|
|
else
|
|
${_ME} help show
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
###############################################################################
|
|
# Run Program
|
|
###############################################################################
|
|
|
|
# Calling the _main function after everything has been defined.
|
|
_main
|