user1783253
user1783253

Reputation: 55

Awk or sed to prepend missing zeros in mac addresses

I've got a file consisting of IPs and MAC address pairs and I need to pad the MAC addresses with zeros in each octet, but I don't want to change the IP. So this...

10.5.96.41 0:0:e:4c:b7:42
10.5.96.42 c4:f7:0:13:ef:32
10.5.96.43 0:e8:4c:60:2b:42
10.5.96.44 0:6a:bf:b:35:f1

Should get changed to this...

10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

I tried sed 's/\b\(\w\)\b/0\1/g' but that produces:

10.05.96.41 00:00:0e:4c:b7:42
10.05.96.42 c4:f7:00:13:ef:32
10.05.96.43 00:e8:4c:60:2b:42
10.05.96.44 00:6a:bf:0b:35:f1

which is not desired because I only want to effect the MAC address portion.

Upvotes: 2

Views: 310

Answers (6)

RARE Kpop Manifesto
RARE Kpop Manifesto

Reputation: 2855

very circuitous and verbose solution to deal with mawk not having regex :: back-references - the approach is to prepad every slot with extra zeros, then trim out the excess :

nawk ' sub(".+","\5:&:\3", $NF)^_   + gsub(":", "&00") +  \
      gsub("[0-9A-Fa-f]{2}:","\6&") + gsub("[^:]*\6|\5:|:00\3$",_)'
mawk ' sub("^", "\5:", $NF)^_ + gsub(":", "&00") + \
      gsub("[^ :][^ :](:|$)", "\6&") + gsub("[^:]*\6|\5:",_)'
10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

To do it the proper gawk gensub() way -- needed 2 calls to gensub() - calling once ended up missing a few ::

gawk -be 'BEGIN { ___ *= __ = "([ :\t])([[:xdigit:]]?)(:|$)"

_="\\10\\2\\3" } $___ = gensub(__, _, "g", gensub(__, _, "g"))'  

Upvotes: 0

Jonathan Wheeler
Jonathan Wheeler

Reputation: 286

A succinct and precise solution for GNU sed:

sed -Ee 's/\b[0-9a-f](:|$)/0&/gi' file

(On macOS, I recommend installing gsed using brew install gnu-sed.)

Upvotes: 0

potong
potong

Reputation: 58473

This might work for you (GNU sed):

sed 's/\b.\(:\|$\)/0&/g' file

Prepend a 0 before any single character followed by a : or the end of line.

Other seds may use:

sed 's/\<.\(:\|$\)/0&/g' file

Upvotes: 2

hek2mgl
hek2mgl

Reputation: 158080

With GNU sed:

sed -E ':a;s/([ :])(.)(:|$)/\10\2\3/g;ta' file

with any sed:

sed ':a;s/\([ :]\)\(.\):/\10\2:/g;ta' file

Explanation (of the GNU version)

:a  # a label called 'a', used as a jump target
;   # command separator
s   # substitute command ...
/([ :])(.)(:|$)/ # search for any single char which is enclosed by
                 # either two colons, a whitespace and a colon or
                 # a colon and the end of the line ($)
                 # Content between () will be matched in a group
                 # which is used in the replacement pattern

\10\2\3          # replacement pattern: group1 \1, a zero, group2 and 
                 # group3 (see above)
/g               # replace as often as possible
;                # command separator
ta               # jump back to a if the previous s command replaced
                 # something (see below)

The loop using the label a and the ta command is needed because sed won't match a pattern again if input was already part of a replacement. This would happen in this case for example (first line):

0:0

When the above pattern is applied, sed would replace

<space>0: by <space>00: <- colon

The same colon would not match again as the beginning : of the second zero. Therefore the loop until everything is replaced.

Upvotes: 0

Ed Morton
Ed Morton

Reputation: 203985

With any sed that uses -E to support EREs, e.g. GNU sed or OSX/BSD (MacOS) sed:

$ sed -E 's/[ :]/&0/g; s/0([^:]{2}(:|$))/\1/g' file
10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

and with any sed:

$ sed 's/[ :]/&0/g; s/0\([^:][^:]:\)/\1/g; s/0\([^:][^:]$\)/\1/' file
10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

Upvotes: 2

Sundeep
Sundeep

Reputation: 23677

Since you've tagged macos, I'm not sure if this will work for you. I tested it on GNU awk

$ awk '{gsub(/\<[0-9a-f]\>/, "0&", $2)} 1' ip.txt
10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

awk is good for field processing, here you can simply perform substitution only for second field

But, I see \b and \w with your sed command, so you are using GNU sed? If so,

sed -E ':a s/( .*)(\b\w\b)/\10\2/; ta' ip.txt


With perl

$ perl -lane '$F[1] =~ s/\b\w\b/0$&/g; print join " ", @F' ip.txt
10.5.96.41 00:00:0e:4c:b7:42
10.5.96.42 c4:f7:00:13:ef:32
10.5.96.43 00:e8:4c:60:2b:42
10.5.96.44 00:6a:bf:0b:35:f1

If you want to get adventurous, specify that you want to avoid replacing first field:

perl -pe 's/^\H+(*SKIP)(*F)|\b\w\b/0$&/g' ip.txt

Upvotes: 4

Related Questions