Skip to content

Instantly share code, notes, and snippets.

@gbot
Last active December 15, 2025 03:25
Show Gist options
  • Select an option

  • Save gbot/53c9d5b4d79f75790443db5f0b665c91 to your computer and use it in GitHub Desktop.

Select an option

Save gbot/53c9d5b4d79f75790443db5f0b665c91 to your computer and use it in GitHub Desktop.
Use 'nslookup' to query one or more domains, using a pre-defined list of nameservers
#!/bin/bash
# what's this all about then?
description="Use 'nslookup' to query one or more domains, using a pre-defined list of nameservers"
# set nameservers as associative arrays
declare -A primary_nameservers
primary_nameservers['Google']='8.8.8.8'
primary_nameservers['CloudFlare']='1.1.1.1'
primary_nameservers['Quad9']='9.9.9.9'
primary_nameservers['Control D']='76.76.2.0'
primary_nameservers['OpenDNS']='208.67.222.222'
primary_nameservers['Yandex Basic DNS']='77.88.8.8'
declare -A additional_nameservers
additional_nameservers['AdGuard DNS']='94.140.14.14'
additional_nameservers['Digital Ocean SG']='139.59.219.245'
additional_nameservers['Orcon NZ']='121.98.0.1'
additional_nameservers['SiteHost NZ']='223.165.64.97'
additional_nameservers['Telstra AU']='139.130.4.4'
additional_nameservers['Yandex Safe DNS']='77.88.8.88'
# final list used in the loop (populated after parsing options)
declare -A nameservers
# misc. vars / settings / default
lookup_type="A"
this_script=$(basename "${BASH_SOURCE[0]}")
use_all_nameservers=false
watch_seconds=""
# colors, cos we like colours!
clr_default="\e[0m"
clr_white="\e[1m"
clr_dim="\e[2m"
clr_inverse="\e[7m"
clr_red="\e[31m"
clr_green="\e[32m"
clr_yellow="\e[33m"
clr_cyan="\e[36m"
# help text
help() {
echo "$description"
echo -e "${clr_yellow}Either pass in a list of domains: \n\t${clr_default}${this_script} domain.com\n\t${this_script} 'domain.com domain2.com domain3.com'\n\t${this_script} -d domain.com \n\t${this_script} --domains='domain.com domain2.com'\e[0m"
echo -e "${clr_yellow}Or export a ENV variable 'domains': \n\t${clr_default}export domains=\"domain.com domain2.com\"\e[0m"
echo -e "${clr_yellow}OPTIONS:${clr_default}"
echo -e "\t -d | --domains \tDomain(s) to query"
echo -e "\t -t | --type \t\tSet query type [ A = default | AAAA | NS | MX ]"
echo -e "\t -e | --expected \tHighlight result RED if doesn't match 'expected'"
echo -e "\t -a | --all \t\tQuery additional nameservers as well"
echo -e "\t -w | --watch=SECONDS \tRun repeatedly, sleeping SECONDS between runs"
echo -e "\t -h | --help \t\tDisplay this help"
echo -e "${clr_yellow}EXAMPLES:${clr_default}"
echo -e "\t${this_script} -t NS domain.com"
echo -e "\t${this_script} -d domain.com -e 123.123.123.123"
echo -e "\t${this_script} --domains='domain.com domain2.com' --type=NS"
echo -e "${clr_yellow}NOTES:${clr_default} \n\tWhen passing domains without a preceding option (-d | --domains), it must be the last argument."
echo
exit
}
# write error to STDERR and exit
die() {
echo -e "${clr_red}ERROR:${clr_default} $1 ${clr_dim}${2}${clr_default}" >&2; exit 2;
}
# getopts required arg fallback
option_needs_arg() {
if [ -z "$OPTARG" ] ; then
die "Missing argument for --$OPT" "[ERR1:long-option-missing-arg]"
fi
}
while getopts :d:ht:e:aw:-: OPT; do
# support long options: https://stackoverflow.com/a/28466267/519360
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning "="
fi
case "$OPT" in
h | help )
help;;
e | expected )
option_needs_arg
expected_result="$OPTARG";;
t | type )
option_needs_arg
lookup_type="${OPTARG^^}";; # convert to uppercase "^^"
d | domains )
option_needs_arg
domains="$OPTARG";;
a | all )
use_all_nameservers=true;;
w | watch )
option_needs_arg
watch_seconds="$OPTARG";;
: )
die "Missing argument for -$OPTARG" "[ERR2:missing-arg]";;
??* )
die "Unrecognised option --$OPT" "[ERR3:bad-long-option]";;
? )
die "Unrecognised option -$OPTARG" "[ERR4:bad-short-option]";;
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
# if there is a remaining argument, consider it as a list of domains to query
if [[ $1 != '' ]]; then
domains="${1}"
elif [[ $domains == '' ]]; then
echo -e "\e[31mNo domains to query!\e[0m"
help
fi
# check 'lookup_type' for accepted values
case "$lookup_type" in
NS | A | AAAA | MX ) ;;
* ) die "Unsupported lookup type";;
esac
# validate watch interval (if provided)
if [[ -n $watch_seconds ]] && [[ ! $watch_seconds =~ ^[0-9]+$ ]]; then
die "Watch value must be a positive integer number of seconds" "[ERR5:bad-watch-value]"
fi
build_nameserver_list() {
# start with primary nameservers
nameservers=()
for key in "${!primary_nameservers[@]}"; do
nameservers[$key]="${primary_nameservers[$key]}"
done
# optionally include additional nameservers
if [[ $use_all_nameservers == true ]]; then
for key in "${!additional_nameservers[@]}"; do
nameservers[$key]="${additional_nameservers[$key]}"
done
fi
}
do_nslookup_loop() {
for this_domain in $domains; do
echo -e "${clr_cyan}+ $this_domain ...${clr_default}"
echo -e " ${clr_yellow}Nameserver:\t\t\t\t\tResult:${clr_default}"
for key in "${!nameservers[@]}"; do
# run nslookup
local nslookup_result=$( nslookup -type="${lookup_type}" $this_domain ${nameservers[$key]} )
# check for failure
local failed="$( echo "$nslookup_result" | egrep "NXDOMAIN|No answer" )"
if [[ $failed != '' ]]; then
local result_output="NOT FOUND!"
else
# check if this is a canonical lookup
local canonical="$( echo "$nslookup_result" | grep "canonical name" | cut -f2 )"
# format result_output according to lookup type, i.e. NS, A
if [[ $lookup_type == "NS" ]]; then
local result_output=$( echo "${nslookup_result}" | grep "nameserver =" | cut -f2 -d'=' | sed 's/^ //' | sed 's/.$//' | sort )
elif [[ $lookup_type == "MX" ]]; then
local result_output="$( echo "$nslookup_result" | tail -4 | grep "mail exchanger =" | cut -f2 -d'=' | cut -f3 -d' ' | sed 's/.$//' | sort )"
else # default for 'A' or 'AAAA' lookup
local result_output="$( echo "$nslookup_result" | tail -3 | grep "Address:" | cut -f2 -d' ' )"
fi
fi
# output
echo -e -n "${clr_white} ${key} ${clr_dim}[${nameservers[$key]}]${clr_default}"
# hacky method faking column layout (pad spaces based on length of col 1 )
loops="$(( 42 - (( ${#key} + ${#nameservers[$key]} )) ))"
for i in $( seq 0 $loops ); do echo -n " "; done
if [[ $result_output == *"$expected_result"* ]] || [[ $expected_result == '' ]] && [[ $failed == '' ]]; then
echo -en "${clr_green}"
else
echo -en "${clr_red}"
fi
echo -en ${result_output}${clr_default}
if [[ $canonical != '' ]] && [[ $failed == '' ]] ; then
canonical="CNAME: $( echo $canonical | cut -f2 -d '=' | sed 's/^ //' )"
echo -en " ${clr_dim}${canonical}${clr_default}"
fi
echo # newline
done
done
}
build_nameserver_list
while true; do
clear
# run the nslookup loop
do_nslookup_loop
# if not watching, exit after first run
if [[ -z $watch_seconds ]]; then
break
fi
sleep "$watch_seconds"
done
# tidy up
unset domains
unset nameservers
exit
@gbot
Copy link
Author

gbot commented Oct 10, 2022

Added support for A, AAAA, NS and MX query types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment