Reputation: 368
I'm interested whether I could use a single grep command for the following situation.
I have a dhcpd.conf file in which DHCP hosts are defined. Given the hostname, I need to find its MAC address in the dhcpd.conf file. I need to use it to disable its PXE boot config, but that's not part of this question.
The file's syntax is uniform, but I still want to make it a little fool-proof. Here is how the hosts are defined:
host client1 { hardware ethernet 12:23:34:56:78:89; fixed-address 192.168.1.11; filename "pxelinux.0"; }
host client2 { hardware ethernet 23:34:45:56:67:78; fixed-address 192.168.1.12; filename "pxelinux.0"; }
host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; }
host client4 { hardware ethernet C1:CA:88:FA:F4:90; fixed-address 192.168.1.14; filename "pxelinux.0"; }
We assume that all configurations take only one line, even though the dhcpd.conf syntax would allow to break options to several lines. We assume that the order of options may differ, however.
I came up with the following grep command:
grep -o "^[^#]*host.*${DHCP_HOSTNAME}.*hardware ethernet.*..:..:..:..:..:..;" /etc/dhcp/dhcpd-hosts.conf
It is supposed to ignore lines those are commented, allow arbitrary whitespace between tokens, and match until the end of the MAC address. When I run it, I get lines like this:
host client1 { hardware ethernet 12:23:34:56:78:89;
This is great! But the point is that I only need a MAC address, without the preceding trash. Now I know that using another grep, or cut, or awk to cut only the MAC address from this output would be trivial. But I wonder, is there a way to use a single grep command to get the end result, without having to pipe this output into another filter? Obviously I can't leave out the beginning of the pattern, because I want to get a specific hostname, thus matching for "..:..:..:..:..:..
" would give me all the MAC addresses.
Once again, I want a single command (not necessarily grep) which cuts out only the proper MAC address from the file. Thus I am not interested in any solutions those say "grep ... | grep ..." or "grep ... | cut ...", etc..
Of course, in practice, nothing bad happens if I use multiple filters and pipe them, I am just curious whether it is possible to solve with one filter.
I would assign the output to a variable.
Upvotes: 6
Views: 5484
Reputation: 30709
I'd choose sed for this, because you can use a regexp for line addressing:
sed -e "/host *${DHCP_HOSTNAME}/!d" -e "s/*.\(hardware [^;]*\).*/\1/g"
The first expression deletes all lines not matching ${DHCP_HOSTNAME}
(you might want to massage this in the shell if you might have any regexp metacharacters in your hostnames, but I'll assume you don't).
The second expression matches the hardware address portion, and deletes the rest of the line.
Upvotes: 1
Reputation: 11220
Since people also answer with different tools, I think awk
might be a good alternative as well.
$ cat so
host client1 { hardware ethernet 12:23:34:56:78:89; fixed-address 192.168.1.11; filename "pxelinux.0"; }
host client2 { hardware ethernet 23:34:45:56:67:78; fixed-address 192.168.1.12; filename "pxelinux.0"; }
#host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; }
host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxelinux.0"; }
host client4 { hardware ethernet C1:CA:88:FA:F4:90; fixed-address 192.168.1.14; filename "pxelinux.0"; }
$ awk '/^[^#]/ && /client3/ { printf ("%s: %s\n", $2, $6); }' so
client3: AB:CD:EF:01:23:45;
I use a double match to exclude commented lines, and simply use the fields index to print out wanted informations. That way, it should also be easy to remove the PXE part. For instance, remove the filename
directive for host3 could be done as follow:
$ awk '/^[^#]/ && /client3/ { gsub(/filename[^;]+;/, ""); print; }' so
host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; }
Specifying a custom image (pxecustom.0):
$ awk '/^[^#]/ && /client3/ { gsub(/filename[^;]+;/, "filename \"pxecustom.0\";"); print; }' so
host client3 { hardware ethernet AB:CD:EF:01:23:45; fixed-address 192.168.1.13; filename "pxecustom.0"; }
Upvotes: 0
Reputation: 35314
You can use a Perl one-liner to match each line of the file against a single regex with an appropriate capture group, and for each line that matches you can print the submatch.
There are several ways to use Perl for this task. I suggest going with the perl -ne {program}
idiom, which implicitly loops over the lines of stdin and executes the one-liner {program}
once for each line, with the current line made available as the $_
special variable. (Note: The -n
option does not cause the final value of $_
to be automatically printed at the end of each iteration of the implicit loop, which is what the -p
option would do; that is, perl -pe {program}
.)
Below is the solution. Note that I decided to pass the target hostname using the obscure -s
option, which enables parsing of variable assignment specifications after the {program}
argument, similar to awk's -v
option. (It is not possible to pass normal command-line arguments with the -n
option because the implicit while (<>) { ... }
loop gobbles up all such arguments for file names, but the -s
mechanism provides an excellent solution. See Is it possible to pass command-line arguments to @ARGV when using the -n or -p options?.) This design prevents the need to embed the $DHCP_HOSTNAME
variable in the {program}
string itself, which allows us to single-quote it and save a few (actually 8) backslashes.
DHCP_HOSTNAME='client3';
perl -nse 'print($1) if m(^\s*host\s*$host\s*\{.*\bhardware\s*ethernet\s*(..:..:..:..:..:..));' -- -host="$DHCP_HOSTNAME" <dhcpd.cfg;
## AB:CD:EF:01:23:45
I often prefer Perl to sed
for the following reasons:
sed
is more limited.-M{module}
option. sed
is not extensible.sed
due to its two-pass process and highly optimized opcode implementation. See http://rc3.org/2014/08/28/surprisingly-perl-outperforms-sed-and-awk/ for example.sed
, since sed
has a more primitive set of commands for manipulating the underlying text.Upvotes: 2
Reputation: 95
You can Try Grep -o with this expression:
grep -o "[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}:[0-9A-F]\{2\}"
Output:
12:23:34:56:78:89
23:34:45:56:67:78
AB:CD:EF:01:23:45
C1:CA:88:FA:F4:90
The Expression above will return only the MAC Address from the dhcp config file.
Upvotes: -1