2

I need to find all active network interfaces on new macOS. That means the following one-liner with pcregrep will not work:

ifconfig | pcregrep -M -o '^[^\t:]+(?=:([^\n]|\n\t)*status: active)'

because pcregrep is no default install on macOS.

I tried to translate it into egrep to no avail, because a positive lookahead is not possible, right?

So I tried with a one-liner in perl. But the following command is not working, because the switch -pe is not gobbling up all lines together. I tried with -p0e too.

ifconfig | perl -pe 'while (<>) {if (/^[^\t:]+(?=:([^\n]|\n\t)*status: active)/){print "$1";};}'

If I search with a positive lookahead the same line, it is working; for example:

ifconfig | perl -pe 'while (<>) {if (/^([^\t:]+)(?=:([^\n]|\n\t)*mtu 1380)/){print "$1";};}'
utun0

A typical output of ifconfig:

en10: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=6467<RXCSUM,TXCSUM,VLAN_MTU,TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
    ether 00:e0:4c:68:01:20
    inet6 fe80::1470:31b9:a01c:6f5e%en10 prefixlen 64 secured scopeid 0xd
    inet 192.168.178.39 netmask 0xffffff00 broadcast 192.168.178.255
    inet6 2003:ee:4f1a:ce00:864:f90c:9a11:6ad9 prefixlen 64 autoconf secured
    inet6 2003:ee:4f1a:ce00:d89a:7e34:6dd4:1370 prefixlen 64 autoconf temporary
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (1000baseT <full-duplex>)
    status: active

The expected result would be:

en10

I am on macOS Monterey, zsh and perl 5.34

Thank you for your help

marek

3 Answers3

3

Since output of ifconfig normally has a multiline block of text for each interface, all separated by blank lines, it is convenient to read it in paragraphs (-00). Then the rest simplifies a lot

ifconfig -a | perl -00 -nE'say $1 if /^(.+?)\s*:.*?status:\s+active/s'

We still need the /s modifier, making . match a newline as well, as each paragraph itself is a multiline string and the pattern needs to match across multiple lines.


Except that it doesn't on the MacOS used for this question -- there are no blank lines separating blocks for interfaces. Then there is no point in seeking paragraphs (breaking on newline which isn't) and this answer doesn't work for that system.

Here is then a classic line-by-line approach that does -- set the interface name at the first line for that interface's output (no spaces at line beginning), then test for active status

perl -wnE'$ifn=$1, next if /^(\S[^:]+?)\s*:/; say $ifn if /status:\s+active/' file

This allows spaces inside an interface name, what is very unlikely (and perhaps not even allowed). For a more restrictive pattern, which doesn't allow spaces in the name, use /^(\S+?)\s*:/ (or the more efficient /^([^:\s]+)/). The \s* and the preceding ? are there only to make it not capture the trailing spaces (right before :), if any were possible.

This works in the case when there are empty lines between interface blocks as well.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Pretty close: I get the result of: `lo0` which is the first network interface of the `ifconfig` output. But it is not active. I need `en5` and `en10`. These are the active ones. – Marek Stepanek Mar 28 '22 at 05:40
  • @MarekStepanek Huh? What does it mean that it is "not active" -- how does it differ from the posted sample? (`status: inactive` or no `status:...` line or ...?) When I test on my system (which has no `active:...` field) it just doesn't match, as it should be -- so I don't see how it can match without that (and why it would stop on the first one). Are the interfaces blocks of text separated by (at least one) empty lines? – zdim Mar 28 '22 at 05:55
  • @MarekStepanek Note that `-00` are zeros, and it must be _two_ zeros, not one. (One way it can do what you say is if the whole `ifconfig` output is in one string (like with `-0`, which should really be `-0777`) --- then the pattern indeed finds `status:active`, in an active interface further down in the string, while the initial `^(.+?)` picks up the name of the first interface.) – zdim Mar 28 '22 at 08:46
  • thx for the detailed answers. Unfortunately on my macOS the output of `ifconfig` are without empty lines. That means the `lo0` ist found, because it is the first interface with colon `:` so a separator should be in grep `^([a-z]+\d): .*` or `^([^:]+):\s+.*` Where the captured pattern `\1` is the interface name, and not an empty line ... – Marek Stepanek Mar 28 '22 at 14:32
  • @MarekStepanek Indeed, that explains it. Then I've added another solution, a standard line-by-line approach: set the interface name at a line which has no spaces at the start, then test each line for active status. (Once found print the interface name.) – zdim Mar 29 '22 at 03:58
  • Thx! Working very clever concept: ifconfig | perl -wnE'$ifn=$1, next if /^(\S+?):/; say $ifn if /status:\s+active/' en5 en10 Pretty far from the example with pcregrep. – Marek Stepanek Mar 29 '22 at 04:47
  • @MarekStepanek Yes, different -- this is a very standard way to process a "stateful" problem, where there is a "state" of some sort that the behavior depends on, which changes in sections of program/input/some other controlling structure. So code checks whether that is set/changed, sets a variable (or flag or such) accordingly once it's detected, and then goes on and does whatever else it is supposed to be doing. Here that's the name of the interface, valid within each interface's block, which determines what to do when "active" is detected (well, just print that name :). – zdim Mar 29 '22 at 17:09
2

You can use

perl -0777 -nE 'say "$&" while /^[^\n\r\t:]+(?=:(?:.*\R\t)*status:\h+active)/gm'

See the regex test.

Here, -0777 slurps the file so that a regex could match multiline text spans (the M equivalent that exposes the newlines to the pattern in pcregrep), say "$&" prints all matched substrings (the o equivalent, also see g flag).

I edited the [^\t:]+ to match any one or more chars other than tabs, colons and also CR/LF chars. Also, I replaced ([^\n]|\n\t)* into a more efficient (?:.*\R\t)* that matches zero or more occurrences of any zero or more chars other than line break chars till the end of a line (.*), then a line break sequence (\R), and then a tab char (\t).

Also, note the m flag to make ^ anchor also match any line start position.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • Thx Wiktor! is working too! `ifconfig | perl -0777 -nE 'say "$&" while /^[^\n\r\t:]+(?=:(?:.*\R\t)*status:\h+active)/gm'` – Marek Stepanek Mar 29 '22 at 04:52
  • @MarekStepanek and it is much more efficient than using `s` modifier with `.*?` lazy dot pattern as it grabs the whole lines before the necessary match as a whole, without having to expand further. See [Can I improve performance of this regular expression further](https://stackoverflow.com/a/33869801/3832970). – Wiktor Stribiżew Mar 29 '22 at 07:27
1

perl's -n and -p command-line switches add an implicit while (<>) {...} block around the -e code, and in addition -p prints the line at the end of each iteration. So you need to change the -p to -n and only print out the lines which match; and remove the extra and unneeded while loop. So something like

ifconfig | perl -ne 'print if /...../'
Dave Mitchell
  • 2,193
  • 1
  • 6
  • 7
  • 1
    Thx for the quick reply Dave. I ended up with `ifconfig | perl -ne 'print if /^([^\t:]+)(?=:([^\n]|\n\t)*status: active)/'` But no result. – Marek Stepanek Mar 27 '22 at 18:53