#!/usr/bin/env bash netstat_parse_linux() { sed -r -n 's/tcp.*\.[0-9]+:([0-9]+).*/\1/p' <<< "$1" | sort -u } netstat_parse_macos() { sed -r -n 's/.*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)).*/\2/p;s/.*\*\.([0-9]+).*/\1/p' <<< "$1" | sort -u } netstat_parse() { if [[ $(uname -s) == Darwin ]]; then netstat_parse_macos "$1" else netstat_parse_linux "$1" fi } netstat_args="-a -t -n" if [[ $(uname -s) == Darwin ]]; then netstat_args="-p tcp -a -n" fi # Return a listing of all TCP ports in use get_ports_tcp() { local data=$(netstat $netstat_args) local ports=$(netstat_parse "$data") while read port; do if ! [[ $port =~ ^[0-9]+$ ]]; then continue fi echo "$port" done <<< "$ports" } # Determine whether a port is in use # Return # 0 if yes # >0 if no get_port_status() { local port="$1" grep "$port" <<< $(get_ports_tcp) &>/dev/null # returns exit code from grep } usage() { printf \ "usage: %s [-h] [range_low] [range_high] Positional arguments: range_low (default: $PORT_MIN) range_high (default: $PORT_MAX) Arguments: -h show this usage statement " $(basename $0) } # main() PORT_MIN=1024 PORT_MAX=65535 # Parse arguments argv=($*) argc="${#argv[@]}" args=() i=0 while [[ $i < $argc ]]; do key="${argv[$i]}" if [[ $key =~ ^- ]]; then key="${key#-*}" case "$key" in h) usage exit 0 ;; *) echo "error: unknown argument" >&2 usage exit 1 ;; esac fi args+=("$key") (( i++ )) done available=-1 range_low=${args[0]:-$PORT_MIN} range_high=${args[1]:-$PORT_MAX} # Check input boundaries if (( range_low > range_high )); then echo "Invalid port range: $range_low > $range_high" >&2 usage exit 1 elif (( range_high < 0 )) || (( range_low < 0 )); then echo "Range value must be a positive integer" >&2 usage exit 1 elif (( range_high > PORT_MAX )) || (( range_low > PORT_MAX )); then echo "Range value must be less than $PORT_MAX" >&2 usage exit 1 fi # Print the next available port or -1 if unable ports=($(seq $range_low $range_high)) for port in "${ports[@]}"; do if ! $(get_port_status $port); then available=$port break fi done echo $available