1
0
mirror of https://github.com/octoleo/hosts.git synced 2024-11-26 06:46:43 +00:00
hosts/hosts
William Melody 1242d0b4bc Replace 'domain' with 'hostname' in regular expression variable names.
'hostname' is more accurate and consistent for this context.
2015-09-12 16:14:36 -07:00

1077 lines
28 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.0.0"
# 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.
_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]:1}")
_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 remove ( <ip> | <hostname> | <search string> ) [--force]
$_ME list [enabled | disabled | <search string>]
$_ME show ( <ip> | <hostname> | <search string> )
$_ME disable ( <ip> | <hostname> | <search string> )
$_ME disabled
$_ME enable ( <ip> | <hostname> | <search string> )
$_ME enabled
$_ME edit
$_ME file
$_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() {
_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_term=$1
if [[ -z "${search_term}" ]]
then
$_ME help disable
exit 1
else
_debug printf "disable() \$search_term: %s\n" "$search_term"
target_regex_ip="^\(${search_term}[${_TAB_SPACE_}]..*\)$"
target_regex_commented_hostname="^\([^#]..*[${_TAB_SPACE_}]${search_term}[${_TAB_SPACE_}]..*\)$"
target_regex_hostname="^\([^#]..*[${_TAB_SPACE_}]${search_term}\)$"
# 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_term}"
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_term=$1
if [[ -z "${search_term}" ]]
then
$_ME help enable
exit 1
else
_debug printf "enable() \$search_term: %s\n" "$search_term"
target_regex_ip="^\#disabled: \(${search_term}[${_TAB_SPACE_}]..*\)$"
target_regex_commented_hostname="^\#disabled: \(..*[${_TAB_SPACE_}]${search_term}[${_TAB_SPACE_}]..*\)$"
target_regex_hostname="^\#disabled: \(..*[${_TAB_SPACE_}]${search_term}\)$"
# 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_term}"
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 search_ip=""
local search_hostname=""
local search_string=""
_debug printf "remove() \$1: %s\n" "${1:-}"
_debug printf "remove() \$2: %s\n" "${2:-}"
if [[ -z "${1:-}" ]]
then
$_ME help remove
exit 1
elif [[ -n "${2:-}" ]] && [[ ! "${2}" =~ ^-\* ]]
then
search_ip="${1}"
search_hostname="${2}"
is_search_pair=1
_debug printf "remove() \$is_search_pair: %s\n" "$is_search_pair"
else
search_string=${1:-}
_debug printf "remove() \$search_string: %s\n" "$search_string"
fi
# 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}\)$"
# 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 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 ! _command_argv_includes "--force"
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
;;
[Nn]* )
printf "Exiting...\n"
exit 0
;;
* )
printf "Please answer yes or no.\n"
;;
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
#
# - Note double periods in regular expression in order to emulate /.+/,
# which apparently doesn't work properly with all versions of sed.
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.
[[ -n "$enabled_records" ]] && printf "%s\n" "$enabled_records"
[[ -n "$disabled_records" ]] && printf "%s\n" "$disabled_records"
else
$_ME help show
exit 1
fi
}
###############################################################################
# Run Program
###############################################################################
# Calling the _main function after everything has been defined.
_main