Search code examples
sedawkdecimalhex

Converting hex to decimal in awk or sed


I have a list of numbers, comma-separated:

123711184642,02,3583090366663629,639f02012437d4
123715942138,01,3538710295145500,639f02afd6c643
123711616258,02,3548370476972758,639f0200485732

I need to split the 3rd column into three as below:

123711184642,02,3583090366663629,639f02,0124,37d4
123715942138,01,3538710295145500,639f02,afd6,c643
123711616258,02,3548370476972758,639f02,0048,5732

And convert the digits in the last two columns into decimal:

123711184642,02,3583090366663629,639f02,292,14292
123715942138,01,3538710295145500,639f02,45014,50755
123711616258,02,3548370476972758,639f02,72,22322

Solution

  • Here's a variation on Jonathan's answer:

    awk $([[ $(awk --version) = GNU* ]] && echo --non-decimal-data) -F, '
        BEGIN {OFS = FS}
        {
            $6 = sprintf("%d", "0x" substr($4, 11, 4))
            $5 = sprintf("%d", "0x" substr($4,  7, 4))
            $4 = substr($4,  1, 6)
            print
        }'
    

    I included a rather contorted way of adding the --non-decimal-data option if it's needed.

    Edit

    Just for the heck of it, here's the pure-Bash equivalent:

    saveIFS=$IFS
    IFS=,
    while read -r -a line
    do
        printf '%s,%s,%d,%d\n' "${line[*]:0:3}" "${line[3]:0:6}" "0x${line[3]:6:4}" "0x${line[3]:10:4}"
    done
    IFS=$saveIFS
    

    The "${line[*]:0:3}" (quoted *) works similarly to AWK's OFS in that it causes Bash's IFS (here a comma) to be inserted between array elements on output. We can take further advantage of that feature by inserting array elements as follows which more closely parallels my AWK version above.

    saveIFS=$IFS
    IFS=,
    while read -r -a line
    do
        line[6]=$(printf '%d' "0x${line[3]:10:4}")
        line[5]=$(printf '%d' "0x${line[3]:6:4}")
        line[4]=$(printf '%s' "${line[3]:0:6}")
        printf '%s\n' "${line[*]}"
    done
    IFS=$saveIFS
    

    Unfortunately, Bash doesn't allow printf -v (which is similar to sprintf()) to make assignments to array elements, so printf -v "line[6]" ... doesn't work.

    Edit: As of Bash 4.1, printf -v can now make assignments to array elements. Example:

    printf -v 'line[6]' '%d' "0x${line[3]:10:4}"
    

    The quotes around the array reference are needed to prevent possible filename matching. If a file named "line6" existed in the current directory and the reference wasn't quoted, then a variable named line6 would be created (or updated) containing the printf output. Nothing else about the file, such as its contents, would come into play. Only the name - and only tangentially.