Determine an SSIDs Index Value in the Preferred Networks List

bashNetworkscriptwifizsh

Im looking for a way to use the macOS networksetup command (and some string/line filtering mojo) to output all SSIDs from the Preferred Networks, and then determine what index my corp SSID is located at (and then take action depending on what index value it has).

Example:

admin@mac1 ~ % networksetup -listpreferredwirelessnetworks en0
Preferred networks on en0:
    Denny's
    Hipster Cafe
    Hilton Hotels
    My_Mom's_Basement
    Airport
    Marriot - Public-5G
    HOME-OFFICE

You will see each line shows an SSID. 'Denny's' is at index 0 (top) and, therefore, is the prioritized SSID.

Likewise, you will see my Corporate network ('HOME-OFFICE') is at the bottom (index 7 of 7).

How can I get the index value using a shell script? I have tried parsing the output and then building/filtering/appending a list using tools such as wc, grep, etc but haven't gotten the logic right thus far.

I already have the logic to move my corporate SSID up to index 0, but I only want to run that logic if the SSID 'HOME-OFFICE' is NOT already at index 0 – which I can't seem to figure out.

Best Answer

Formulating the problem in terms of “which index does my corporate network have?” makes the problem more complicated. Since you only want to know whether it's at the top of the list, just check whether the top of the list is your corporate network.

Zsh

Here's a way to put the network names into an array in zsh. This uses three nested parameter expansion constructs around a command substitution:

  • ${(f)$(…)} splits the output of the command substitution into lines (f flag).
  • ${(@)…##[[:space:]]##} strips off the prefix matching the pattern [[:space:]]##, meaning one or more whitespace characters. ([[:space:]]#, meaning zero or more whitespace characters, would be equivalent, since removing an empty string has no effect.) The goal is to strip the leading tab. ${(@)…#$'\t'} would work too, but would be fragile in case a different version of macOS uses a different amount of spacing. The pattern needs setopt extended_glob, which is off by default. The @ flag ensures that the data is processed as a list of strings (list of lines from the previous step) and not as a single string.
  • "${(@)…[2,-1]}" selects the elements of the array from the second one (zsh arrays start at 1) to the last one (-1). This removes the “Preferred networks on …” line. The @ flag ensures that each line ends up in a separate array element. The double quotes ensure that spaces inside a network name do not cause each word of a name to end up in a separate array element.
setopt extended_glob
networks=("${(@)${(@)${(f)$(networksetup -listpreferredwirelessnetworks en0)}##[[:space:]]##}[2,-1]}")

Now you can check whether the first element is what you want:

if [[ $networks[1] != "HOME-OFFICE" ]]; then
  …
fi

If you do want the (1-based) index of a network name, you can use the i array subscript flag.

echo $networks[(i)"HOME-OFFICE"]

Sed

You can use sed to extract a specific line by number. Line numbers start at 1. Use the -n command line flag to suppress printing and the p command to print the desired line, or use the d command to delete all lines except the desired one. Use the s command to strip off leading whitespace; note that there is a literal tab character and a space inside the brackets. You can add the suffix p to the s command to print its output without a separate p command.

preferred_network=$(networksetup -listpreferredwirelessnetworks en0 | sed -n '2s/^[  ]*//p')

or

preferred_network=$(networksetup -listpreferredwirelessnetworks en0 | sed -n '2!d; s/^[  ]*//')

You can get the 2-based index of an element by name with sed by applying the = command to the line(s) matched by a regular expression.

line_number=$(networksetup -listpreferredwirelessnetworks en0 | sed -n 's/^[     ]*//; /^HOME-OFFICE$/=')
zero_based_index=$((line_number - 2))

Awk

You can use awk to extract a specific line by number. Line numbers start at 1, and the number of the current line is in the NR variable. After matching the desired line, remove the leading whitespace with the sub command, then print the line. Awk recognizes \t to mean a tab character.

preferred_network=$(networksetup -listpreferredwirelessnetworks en0 | awk 'NR==2 {sub(/^[\t ]*/, ""); print}')

If you want the number of a line with a specific content, you can match the line with a regular expression or by doing a string comparison ($0 contains the whole line without a terminating newline character), then print NR.

home_office_zero_based_index=$(networksetup -listpreferredwirelessnetworks en0 | awk '{sub(/^[\t ]*/, "")} $0 == "HOME-OFFICE" {print NR - 2})'

or

home_office_zero_based_index=$(networksetup -listpreferredwirelessnetworks en0 | awk '/^[\t ]HOME-OFFICE$/ {print NR - 2})'