MacOS Better ip info in terminal
One particular painful point, coming from linux, was the ip command in the Terminal. The Default MacOS ifconfig command is very - well let’s say “old fashioned” :)
Coming from Linux you mostly use ip for everyting ip a to display your interfaces ip r your routes, and so on.
Luckily you can install the iproute2 Package on MacOS which give you the ip command.
Installing iproute2 on MacOS
The easiest way is using homebrew, if you not using it now you probably should:
brew install iproute2mac
You can also install via MacPorts or manually from the GitHub repository, though Homebrew is the most straightforward approach.
After installation you should be able to use the ip command simple by typing ip addr as a test.
As a final Note; The iproute2mac package is a wrapper around macOS native tools like ifconfig, route, and networksetup. It is not a full port of Linux iproute2. Some functionality is limited compared to Linux and command output isn’t fully compatible. Nevertheless I find it way better then relying on the ifconfig MacOS provides.
Cleaning up the Output
Unfortunately the output of a ip a on MacOS is everything but not something I would consider clean and minimal. There are alot of internal interfaces and you might be at first sight a little lost.
One way is to only show active interfaces via:
ip link | grep -B1 "state UP"
My ip alias / fish function
I love clean output which give me needed information about my interfaces at the first sight. As I prefer fish shell over the MacOS default zsh this only works if you also using fish. I will write something about my fish setup in another post.
But if you are on fish shell for MacOS you can copy the function below to your fish config file. The Function is actually a wrapper for the ip command. After this wrapper is active if you run ip without any argument it detects the default interface and deduplicate others finally trying to get IP Addr and some stats for each active interface. The display looks like this:

pretty ip wrapper for fish config
As said, just add this function either directly to your fish config file or in the custom fish function directory.
A user-friendly fish wrapper around the ip tool that prints aligned, colorized IPv4 interface info (including active utun tunnels), plus a filtered view of routes and extra subnets.
# prettify the ip Command (simple, aligned, colored)
function ip --wraps ip --description 'ip information (aligned IPv4 column; includes active utun tunnels; dedup; dynamic name padding)'
if test -n "$argv"
grc /opt/homebrew/bin/ip -p -4 $argv
return
end
set -l IP /opt/homebrew/bin/ip
set -l defif (route -n get default 2>/dev/null | string match -r --groups-only 'interface:\s*([^\s]+)')
# Collect and deduplicate interfaces (include en* and utun*)
set -l ifaces (ifconfig -l | tr ' ' '\n' | command grep -E '^(en[0-9]+|utun[0-9]+)$' | sort -u)
set_color brcyan
echo "Interfaces:"
set_color normal
# Pre-pass: compute max visible interface name length (incl. " *" for default)
set -l name_target 0
for n in $ifaces
set -l nv $n
if test "$n" = "$defif"
set nv "$nv *"
end
set -l nl (string length -- $nv)
if test $nl -gt $name_target
set name_target $nl
end
end
for name in $ifaces
set -l cfg (ifconfig $name)
# IP (prefer 'ip', fallback to ifconfig incl. tunnels)
set -l ipcidr ($IP -4 addr show dev $name 2>/dev/null | awk '/inet /{print $2; exit}')
if test -z "$ipcidr"
set -l ip (string match -r --groups-only 'inet\s+([0-9.]+)' -- $cfg | head -n 1)
set -l peer_cidr (string match -r --groups-only 'peer\s+([0-9.]+/[0-9]+)' -- $cfg | head -n 1)
if test -n "$ip"
if test -n "$peer_cidr"
set -l prefix (string replace -r '^[0-9.]+/' '' -- $peer_cidr)
set ipcidr "$ip/$prefix"
else
set ipcidr "$ip"
end
end
end
test -n "$ipcidr"; or continue
# State/flags and tunnel activity
set -l state (string upper -- (string match -r --groups-only 'status:\s*([A-Za-z]+)' -- $cfg))
set -l flags (string match -r --groups-only 'flags=[0-9]+<([^>]+)>' -- $cfg)
set -l is_tun (string match -rq '^utun[0-9]+$' -- $name; and echo 1; or echo 0)
set -l is_running 0
if test -n "$flags"
if string match -rq '(^|,)RUNNING(,|$)' -- $flags
set is_running 1
end
end
if test -z "$state"
if test $is_running -eq 1
set state ACTIVE
else
set state UNKNOWN
end
end
if test $is_tun -eq 1
if not string match -q "ACTIVE" "$state"
and test $is_running -ne 1
continue
end
end
set -l mtu (string match -r --groups-only 'mtu\s+([0-9]+)' -- $cfg)
set -l mac (string match -r --groups-only 'ether\s+([0-9a-f:]+)' -- $cfg)
if test -z "$mac"; set mac "-"; end
# Name column padding (dynamic)
set -l name_visible $name
if test "$name" = "$defif"
set name_visible "$name_visible *"
end
set -l nlen (string length -- $name_visible)
set -l pad (math "$name_target - $nlen"); test $pad -lt 0; and set pad 0
set_color green; printf "%s" $name
if test "$name" = "$defif"; set_color yellow; printf " *"; end
set_color normal; printf "%s" (string repeat -n (math "$pad + 2") " ")
set_color brblack; printf "State: "
if string match -q "ACTIVE" "$state"; set_color green; else; set_color red; end
printf "%s" "$state"
# IPv4 padded to fixed min width for aligned MTU column
set -l ip_target 18
set -l ip_len (string length -- $ipcidr)
set -l ip_pad (math "$ip_target - $ip_len"); test $ip_pad -lt 0; and set $ip_pad 0
set_color brblack; printf "\tIPv4: "; set_color brwhite; printf "%s" "$ipcidr"
set_color normal; printf "%s" (string repeat -n $ip_pad " ")
set_color brblack; printf "\tMTU: "; set_color brwhite; printf "%s" "$mtu"
set_color brblack; printf "\tMAC: "; set_color brwhite; printf "%s\n" "$mac"
set_color normal
end
# Routes: default interface only (old approach) + extra subnets not on default iface
set -l routes_all ($IP -4 route show)
if test -n "$defif"
echo
set_color brmagenta; echo "Routes ($defif):"; set_color normal
set -l def_prefixes
for line in (string split \n -- $routes_all)
string match -qr " dev $defif( |\$)" -- $line; or continue
# default on default iface
if string match -qr '^default( |\$)' -- $line
set_color yellow; echo " $line"; set_color normal
continue
end
# skip host /32, and noisy prefixes
string match -qr '/32( |\$)' -- $line; and continue
set -l prefix (string split ' ' -- $line)[1]
string match -qr '^(127\.|169\.254\.|255\.|224\.)' -- $prefix; and continue
set_color blue; echo " $line"; set_color normal
set -a def_prefixes $prefix
end
# Extra subnets from non-default ifaces, not already in def_prefixes
set -l extras
for line in (string split \n -- $routes_all)
string match -qr " dev $defif( |\$)" -- $line; and continue
string match -qr '^default( |\$)' -- $line; and continue
string match -qr '/32( |\$)' -- $line; and continue
set -l prefix (string split ' ' -- $line)[1]
string match -qr '^(127\.|169\.254\.|255\.|224\.)' -- $prefix; and continue
contains -- $prefix $def_prefixes; and continue
set -a extras $line
end
if test (count $extras) -gt 0
echo
set_color brmagenta; echo "Extra subnets:"; set_color normal
for line in $extras
set -l prefix (string split ' ' -- $line)[1]
set -l via (string match -r --groups-only ' via ([^ ]+)' -- $line)
set -l iface (string match -r --groups-only ' dev ([^ ]+)' -- $line)
set_color green
if test -n "$via"
echo " $prefix via $via on $iface"
else
echo " $prefix on $iface"
end
set_color normal
end
end
end
end