-1

I want to print a colon after every 2 characters of alphanumeric Value and I found something like below. But I am not sure how to remove initial 2 characters from the output and what is the meaning of 1 in last of ask command.. I am sure there must be better approach.I don’t want to put colon at the end also ..

echo 0x100070b7e4323096 | sed 's/../&:/g; s/:&//' echo 0x100070b7e4323096 | awk '{gsub(/../,"&:",$1)}1' 

Desired output:

10:00:70:b7:e4:32:30:96 
1
  • 1
    You've re-added ‘smart quotes’, which will fail when you try to use them. I've fixed them for you. Please take care as this is the second time... Commented Aug 28 at 16:52

7 Answers 7

5

This might be what you're trying to do but without showing us the expected output it's just a guess:

$ echo 0x100070b7e4323096 | sed 's/..//; s/../&:/g' 10:00:70:b7:e4:32:30:96: 

or maybe:

$ echo 0x100070b7e4323096 | sed 's/../:&/g; s/....//' 10:00:70:b7:e4:32:30:96 

You can use a very similar syntax in awk, e.g.:

$ echo 0x100070b7e4323096 | awk '{sub(/../,""); gsub(/../,"&:")} 1' 10:00:70:b7:e4:32:30:96: 

or:

$ echo 0x100070b7e4323096 | awk '{gsub(/../,":&"); sub(/..../,"")} 1' 10:00:70:b7:e4:32:30:96 

The 1 is a true condition which causes awk to execute its default action of printing the current record. You could always replace 1 with {print} if you prefer.

1
  • I wanted to print out all the characters except the beginning 2 characters.. but print a colon in between 2 characters.. your command works flawlessly Commented Aug 28 at 16:31
4

With AWK:

awk '{ for(i=3;i<=length();i=i+2) printf "%.2s%s", substr($0,i) , i>=length()-1 ? ORS : ":" }' file 

Using gawk:

awk '{ n=patsplit($0,a,/../,sep); for(i=2;i<=n;i++) printf "%s", a[i] (i==n ? ORS : ":") # for(i=2;i<n;i++) printf "%s:", a[i]; print a[n] }' file 

This prints an array a from second index using gawk's patsplit() function.


Using fold and paste in a pipe:

This approach doesn't use regular expressions (As suggested by @Kusalananda):

echo 0x100070b7e4323096 | fold -w 2 | tail -n +2 | paste -d: -s - echo 0x100070b7e4323096 | cut -c 3- | fold -w 2 | paste -d: -s - 
  • folds output to a width of 2 characters.

  • tail removes lines before 2nd line (+2).

  • paste command squeezes lines from - standard input or stdin into one line (serial option) using delimiter :.

  • cutting from 3rd character may be used.

7
  • 1
    Or gawk -vFPAT=.. -vOFS=: '{$1="";print substr($0,2)}' -- or perl -ple '@a=m/../g; shift @a; $_=join ":",@a' but that wasn't asked for Commented Aug 29 at 0:41
  • Not at all, that's how Stack works. If I'd had the time to present it nicely, I'd have editted myself. Commented Aug 30 at 0:33
  • or: gawk -vFIELDWIDTHS="2 2 2 2 2 2 2 2 2" -vOFS=":" '{ $1=""; print substr($0,2) }' Commented Aug 30 at 9:20
  • Another way to output is perl -pe 's/..(?!$)/"$&:" if my $counter++ > 0/ge'. Commented Aug 30 at 16:46
  • 1
    I think we should stop spamming other ways. (like: echo 0x100070b7e4323096 | gawk '{x=substr($0,3); while (x!=""){ printf "%s"(length(x)==2?"":":"), substr(x,1,2); x=substr(x,3)}}' 😉) Commented Aug 30 at 17:15
3

You can add a separate operation to remove the initial two characters:

echo 0x100070b7e4323096 | sed 's/^0x//; s/../&:/g; s/:$//' 

(fixing the quotes, and the trailing colon replacement), or extract the substring of interest after inserting all the colons:

echo 0x100070b7e4323096 | sed -E 's/../&:/g; s/^0x:(.*):$/\1/' 

With AWK:

echo 0x100070b7e4323096 | awk '{gsub(/^0x/,"",$1); gsub(/../,"&:",$1); gsub(/:$/,"",$1)}1' 

The trailing 1 in AWK is a shortcut to invoke the default action, which is to print the current record. More explicitly, and having gsub operate on the full line (which it does by default):

echo 0x100070b7e4323096 | awk '{gsub(/^0x/,""); gsub(/../,"&:"); gsub(/:$/,""); print}' 
1
  • Thank you both the command works what I expected and also for the explanation about 1 .. I added few more additional conditions can that be done in a one liner ? Commented Aug 28 at 16:22
1

In pure bash:

echo '0x100070b7e4323096' | { IFS= read -r str # Read piped string into "$str" [[ $str =~ ${str//??/(..)} ]] # Match every 2 characters in a separate capture group IFS=':' # Set the field separator to : instead of space printf '%s\n' "${BASH_REMATCH[*]:2}" # Print the capture groups matches skipping the first one } 

Outputs:

10:00:70:b7:e4:32:30:96 

Notes:

  • ${str//??/(..)} is a parameter expansion and will replase every 2 characters with (..) to build the RegEx with capture groups (..)(..)(..)(..)(..)(..)(..)(..)(..)
  • BASH_REMATCH is a builtin array where index 0 is the whole RegEx match, 1 is the first capture group match, 2 is the second and so on ...
  • The expansion of ${BASH_REMATCH[*]:2} will start from index 2 i.e. skipping 0 (the whole match) and 1 (first capture group i.e 0x)
  • Due to the pipe | (unless used with lastpipe set), IFS=':' (and other variables) is set in a sub-shell not affecting the current working shell ... But, take care otherwise.
0
1

Mostly for the fun of it, in the zsh shell, to do it on a variable, using its string manipulation operators:

$ hex=0x100070b7e4323096 $ set -o extendedglob $ echo ${${hex//(#m)??/:$MATCH}[5,-1]} 10:00:70:b7:e4:32:30:96 

Where we replace every two characters ?? (with (#m) activating the capturing of the match in $MATCH), with : and the match, using the Korn-style ${param//pattern/replacement} operator, and then select the 5th to last (-1) characters from the result. Essentially the same as sed's s/../:&/g; s/....//.

$ printf "${(r[5*8-1][%s%s:])}\n" ${(s[])hex#0x} 10:00:70:b7:e4:32:30:96 

Where ${hex#0x} is the Korn/POSIX operator to strip a leading 0x, the s[] parameter expansion flag splits the result into the character constituents which we pass as separate arguments to printf.

The format argument for printf is %s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s:%s%s\n which we obtain using the r[length][pad] right padding parameter expansion flag applied to no parameter, here using %s%s: as the pad and 5*8-1 as the length so as to have 8 of them without the last :.

Or using csh-style modifiers:

$ set -o histsubstpattern -o extendedglob $ echo $hex:s/0x//:fs/(#m)[^:](#c4)/$MATCH[1,2]:$MATCH[3,4] 10:00:70:b7:e4:32:30:96 

Where we remove the 0x, then look for 4 non-: characters, insert a : in the middle, and repeat the process until there's no more change (the f prefix to the s modifier). Essentially like GNU sed's echo $hex | sed -E 's/0x//;:1;s/([^:][^:])([^:][^:])/\1:\2/;t1' or echo $hex | perl -lpe 's/0x//; while (s/([^:][^:])([^:][^:])/$1:$2/){}'

Or a translation of @Raffa's approach into zsh, using regexps (extended or PCRE if the rematchpcre option is enabled):

$ [[ $hex =~ ${hex//??/(..)} ]]; echo ${(j[:])match[2,-1]} 10:00:70:b7:e4:32:30:96 

Or globs:

$ set -o extendedglob $ [[ $hex = (#b)${~hex//??/(??)} ]]; echo ${(j[:])match[2,-1]} 10:00:70:b7:e4:32:30:96 

Or the lame (but certainly easier to follow) approach:

$ echo $hex[3,4]:$hex[5,6]:$hex[7,8]:$hex[9,10]:$hex[11,12]:$hex[13,14]:$hex[15,16]:$hex[17,18] 10:00:70:b7:e4:32:30:96 

Or the ksh93-style equivalent (also supported by bash, though you'd need to double-quote it in those shells):

$ echo ${hex:2:2}:${hex:4:2}:${hex:6:2}:${hex:8:2}:${hex:10:2}:${hex:12:2}:${hex:14:2}:${hex:16:2} 10:00:70:b7:e4:32:30:96 

You could pass that list of offsets as arguments to an anonymous function:

$ set -o extendedglob $ (){echo ${(j[:])@/(#m)*/${hex:$MATCH:2}}} {2..16..2} 10:00:70:b7:e4:32:30:96 

Where ${array/(#m)*/something-with-$MATCH} is a common way to do something similar to the map() of perl or other languages, the j[:] parameter expansion flag to join.

More fun ways:

$ echo $hex | grep -Eo '[[:xdigit:]]{2}' | paste -sd : - 10:00:70:b7:e4:32:30:96 

Abusing hexdump:

$ echo $hex | hexdump -e '/2 "%.0s" 7/2 "%.2s:" /2 "%s" /9 "%.0s"' 10:00:70:b7:e4:32:30:96 

The natural way with perl:

$ echo $hex | perl -lne 'print join ":", s/^0x//r =~ /../g' 10:00:70:b7:e4:32:30:96 
1
  • Indeed loads of fun😀 Commented Aug 30 at 7:55
1

Using perl with a negative look-ahead assertion,(?!$), in the regex to prevent it form appending a : at the end of the line.

echo 0x100070b7e4323096 | perl -lpe 's/^0x//; s/(..)(?!$)/$1:/g' 10:00:70:b7:e4:32:30:96 

See man perlre, search for the section header Lookaround Assertions.

1
  • Cool.. thank you for the code Commented Sep 3 at 7:15
0

If that hex number is meant to always be treated as a 64bit number and be output as 8 :-separated 0-padded hexadecimal numbers even if that number has fewer than 16 digits, for instance if for 0x1, you expect to get 00:00:00:00:00:00:00:01, you could do:

$ printf '%s\n' 0x100070b7e4323096 0x1 0xDeadBeef | perl -lpe ' $_ = join ":", unpack("(H2)*", pack("Q>", hex$_))' 10:00:70:b7:e4:32:30:96 00:00:00:00:00:00:00:01 00:00:00:00:de:ad:be:ef 

Where hex decodes the hex into an integer, pack packs it as a big-endian 64bit number, and unpack unpacks that into 2 digit hex nibbles which are joined with :.

Or in zsh, allowing the numbers to be expressed in any base, but limited to numbers up to 0x7fffffffffffffff (maximum signed 64 bit number):

$ for i (0x100070b7e4323096 0xDeadBeef 1234 0b1000000 '36#ZZZZZZ') printf "${(r[8*5-1][%02x:])}\n" 'i>>'{56..0..8}'&255' 10:00:70:b7:e4:32:30:96 00:00:00:00:de:ad:be:ef 00:00:00:00:00:00:04:d2 00:00:00:00:00:00:00:40 00:00:00:00:81:bf:0f:ff 

That is first expanded into:

printf '%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n' 'i>>56&255' 'i>>48&255' 'i>>40&255' 'i>>32&255' 'i>>24&255' 'i>>16&255' 'i>>8&255' 'i>>0&255' 

You'd write A POSIX equivalent by putting those arithmetic expressions inside "$((...))" and hardcode the list. POSIX arithmetics only support decimal, hexadecimal and octal (with leading 0s though several shells including zsh, mksh and ksh93 disable that by default as it gets in the way more often than it's useful; use 8#100 instead of 0100 in those):

for i in 0x100070b7e4323096 0xDeadBeef 1234; do printf '%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n' \ "$((i>>56&255))" "$((i>>48&255))" "$((i>>40&255))" "$((i>>32&255))" \ "$((i>>24&255))" "$((i>>16&255))" "$((i>>8&255))" "$((i>>0&255))" done 

POSIX doesn't give any guarantee that numbers above 2<sup>31</sup> - 1 will be supported though in practice on most systems and with most shell/printf implementations, signed 64bit integers are used by both, so you can expect it to work there with numbers up to 0x7fffffffffffffff like in zsh above.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.