---
canonical: https://solariz.de/posts/25/macos_ip_command_fish_ipnfo/index.md
---
# 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:

```bash
brew install iproute2mac
```

You can also install via **MacPorts** or manually from the [GitHub repository](https://github.com/brona/iproute2mac), 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:

```bash
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:





![Screenshot macos terminal ip2route fish optimized output](https://solariz.de/posts/25/macos_ip_command_fish_ipnfo/iproute2mac-fish-wrapper.png)






### 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.

```fish
    # 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
```

