Skip to content

Instantly share code, notes, and snippets.

@codeinthehole
Last active August 6, 2023 22:02
Show Gist options
  • Save codeinthehole/5064150 to your computer and use it in GitHub Desktop.
Save codeinthehole/5064150 to your computer and use it in GitHub Desktop.
Bash helper function for easier port forwarding - should be added to your ~/.bashrc
# Easier port forwarding - usage:
#
# $ portforward project-app1 5432
#
# where 'project-app1' is an alias from ~/.ssh/config and 5432 is the remote
# port that you want to forward to.
function portforward() {
HOST=$1
REMOTE_PORT=$2
# Pick a random port and check it is free
LOCAL_PORT=$((RANDOM+1000))
if ! [[ `lsof -i :$LOCAL_PORT | grep COMMAND` ]]
then
# Port is free - woop!
echo "Forwarding to port $REMOTE_PORT on $HOST from http://localhost:$LOCAL_PORT"
ssh -L $LOCAL_PORT:localhost:$REMOTE_PORT $HOST -N 2> /dev/null
else
# Recursion ftw
portforward $HOST $REMOTE_PORT
fi
}
@kura
Copy link

kura commented Mar 1, 2013

Edit: The version I am now using - https://gist.github.com/kura/5065819

function portforward() {
  if [[ $# -ne 2 ]]
  then
    echo "Usage: portforward HOST PORT";
    exit;
  fi
  HOST=$1
  REMOTE_PORT=$2
  # Pick a random port and check it is free
  LOCAL_PORT=$((RANDOM+1000))
  if ! [[ `lsof -i :$LOCAL_PORT | grep COMMAND` ]]
  then
    # Port is free - woop!
    echo "Forwarding to port $REMOTE_PORT on $HOST from http://localhost:$LOCAL_PORT"
    ssh -L $LOCAL_PORT:localhost:$REMOTE_PORT $HOST -N 2> /dev/null
  else
    # Recursion ftw
    portforward $HOST $REMOTE_PORT
  fi
}

function _portforward() {
  if [[ ${COMP_WORDS[COMP_CWORD-1]} == "portforward" ]]
  then
    cur="${COMP_WORDS[COMP_CWORD]}"
    hosts=$(grep "Host " ~/.ssh/config | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | grep -v "#" | grep -v "*" | awk '{print $2}')
    COMPREPLY=($(compgen -W "${hosts}" -- ${cur}))
    return 0
  fi
}

complete -F _portforward portforward

@kura
Copy link

kura commented Mar 1, 2013

You can make it autocomplete some known ports

function portforward() {
  service_ports[postgres]=5432
  service_ports[mysql]=3306
  service_ports[rabbitmq2]=55672
  service_ports[rabbitmq3]=15672
  service_ports[httpalt]=8080

  # echo out the service port name, you'd naturally have do the port lookup here
  # or treat as manually specified port if not inside the array
  echo ${service_ports[${2}]}
  if [[ $# -ne 2 ]]
  then
    echo "Usage: portforward HOST PORT";
    exit;
  fi
  HOST=$1
  REMOTE_PORT=$2
  # Pick a random port and check it is free
  LOCAL_PORT=$((RANDOM+1000))
  if ! [[ `lsof -i :$LOCAL_PORT | grep COMMAND` ]]
  then
    # Port is free - woop!
    echo "Forwarding to port $REMOTE_PORT on $HOST from http://localhost:$LOCAL_PORT"
    ssh -L $LOCAL_PORT:localhost:$REMOTE_PORT $HOST -N 2> /dev/null
  else
    # Recursion ftw
    portforward $HOST $REMOTE_PORT
  fi
}

function _portforward() {
  cur="${COMP_WORDS[COMP_CWORD]}"
  # COMP_CWORD-1 = portforward, i.e. the name of this function, so autocomplete SSH host
  if [[ ${COMP_WORDS[COMP_CWORD-1]} == "portforward" ]]
  then
    hosts=$(grep "Host " ~/.ssh/config | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | grep -v "#" | grep -v "*" | awk '{print $2}')
    COMPREPLY=($(compgen -W "${hosts}" -- ${cur}))
  else
    # otherwise assume on second arg, so autocomplete service name
    services="postgres mysql rabbitmq2 rabbitmq3 httpalt"
    COMPREPLY=($(compgen -W "${services}" -- ${cur}))
  fi
  return 0
}

complete -F _portforward portforward

Sadly, bash associative arrays suck, so there is no real way of generating the services variable on-the-fly, I tried below using:

services=""; for i in "${!service_ports[@]}"; do $services="${services} ${i}"; done

Or you can simply auto-complete the port number itself

function portforward() {
  if [[ $# -ne 2 ]]
  then
    echo "Usage: portforward HOST PORT";
    exit;
  fi
  HOST=$1
  REMOTE_PORT=$2
  # Pick a random port and check it is free
  LOCAL_PORT=$((RANDOM+1000))
  if ! [[ `lsof -i :$LOCAL_PORT | grep COMMAND` ]]
  then
    # Port is free - woop!
    echo "Forwarding to port $REMOTE_PORT on $HOST from http://localhost:$LOCAL_PORT"
    ssh -L $LOCAL_PORT:localhost:$REMOTE_PORT $HOST -N 2> /dev/null
  else
    # Recursion ftw
    portforward $HOST $REMOTE_PORT
  fi
}

function _portforward() {
  cur="${COMP_WORDS[COMP_CWORD]}"
  # COMP_CWORD-1 = portforward, i.e. the name of this function, so autocomplete SSH host
  if [[ ${COMP_WORDS[COMP_CWORD-1]} == "portforward" ]]
  then
    hosts=$(grep "Host " ~/.ssh/config | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | grep -v "#" | grep -v "*" | awk '{print $2}')
    COMPREPLY=($(compgen -W "${hosts}" -- ${cur}))
  else
    # otherwise assume on second arg, so autocomplete service name
    ports="5432 3306 55672 15672 8080"
    COMPREPLY=($(compgen -W "${ports}" -- ${cur}))
  fi
  return 0
}

complete -F _portforward portforward

@codeinthehole
Copy link
Author

Just for info, here's my latest version:

# Easier port forwarding - usage:
#
# $ portforward project-app1 5432 [5588]
#
# where 'project-app1' is an alias from ~/.ssh/config and 5432 is the remote
# port that you want to forward to. An optional local port can be specified to
# - it omitted, then a random available port will be chosen.
function portforward() {
    if [[ $# -le 1 ]]
    then
        echo "Usage: portforward HOST PORT [LOCALPORT]";
        return
    fi
    HOST=$1
    REMOTE_PORT=$2
    if [[ $# -eq 3 ]]
    then
        LOCAL_PORT=$3
    else
        # Pick a random port and check it is free
        LOCAL_PORT=$((RANDOM+1000))
    fi
    if ! [[ `lsof -i :$LOCAL_PORT | grep COMMAND` ]]
    then
        # Port is free - woop!
        echo "Forwarding to port $REMOTE_PORT on $HOST from http://localhost:$LOCAL_PORT"
        ssh -L $LOCAL_PORT:localhost:$REMOTE_PORT $HOST -N 2> /dev/null
        [[ $? -ne 0 ]] && echo "Unable to connect to $HOST"
    else
        # Recursion ftw
        portforward $HOST $REMOTE_PORT
    fi
}

function _portforward() {
    # Only autocomplete on the first arg
    if [[ ${COMP_WORDS[COMP_CWORD-1]} == "portforward" ]]
    then
        # Extract host aliases from ~/.ssh/config
        HOSTS=$(grep --color=never "^Host " ~/.ssh/config | awk '{if ($2 != "*") print $2}')
        CURRENT_WORD="${COMP_WORDS[COMP_CWORD]}"
        COMPREPLY=($(compgen -W "$HOSTS" -- $CURRENT_WORD))
        return 0
    fi
}

complete -F _portforward portforward

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