0

I have the following to print a dashed line based on the longest line of a csv file:

awk -F ',' '
    BEGIN {
       longest_line=0
        for (i = 1; i <= NF; i++) {
           longest[i] = ""
        }
    }
    {
       for (i = 1; i <= NF; i++) {
           if (length($i) > length(longest[i])) {
               longest[i] = $i
           }
       }
    }
    
    END {
        for (i=1; i<=NF;i++) {
            longest_line += length(longest[i])
        }
        printf("%*s", longest_line, "=")
    }
'

Here's the entire script:

awk -F ',' -v smso="$smso" -v rmso="$rmso" 'BEGIN {
    count=1
    firstcol=0
    arraylen=1
    longest_line=0
   for (i = 1; i <= NF; i++) {
       longest[i] = ""
    }
}

{
    for (i=1;i<=NF;i++) {
            if (i==NF) {
                data[arraylen++]=$i
                data[arraylen++]="\n"
            } else {
                data[arraylen++]=$i
            }
    }
}


{
   for (i = 1; i <= NF; i++) {
       if (length($i) > length(longest[i])) {
           longest[i] = $i
       }
   }
}

END {
    for (i=1; i<=NF;i++) {
        longest_line += length(longest[i])
    }
    printf("%*s", longest_line, "b")
    for (i = 1; i <= length(data); i++) {
        if (data[i]=="\n") {
            firstcol++
           count=1  
            printf("%s", data[i])
        } else if (count==1 && i != 1) {
            printf("%s%s%s", "|", data[i], "|")
            count++
        } else {
            smso=$(tput smso)
            rmso=$(tput rmso)
            num_spaces=(length(longest[count])-length(data[i]))+1
            printf("%s%*s%s%s", (i==1?"|":""), (i==1?num_spaces-1:num_spaces), " ", firstcol==0?toupper(data[i]):data[i], "|")
            count++
        }
    }   
}'

input file is:

NUMBER,FNAME,LNAME,PHONE-TYPE:GROUPS
222-222-2222,Elizabeth,Taylor,office:beauty:
111-111-1111,Matt,Alex,personal:superhuman:cool:amazing:extra

and desired output is:

==============================================================
|      NUMBER|     FNAME|  LNAME|           PHONE-TYPE:GROUPS|
|222-222-2222| Elizabeth| Taylor|              office:beauty:|
|111-111-1111|      Matt|   Alex| personal:cool:amazing:extra|

but my output is:

                                                             =
|      NUMBER|     FNAME|  LNAME|           PHONE-TYPE:GROUPS|
|222-222-2222| Elizabeth| Taylor|              office:beauty:|
|111-111-1111|      Matt|   Alex| personal:cool:amazing:extra|
3
  • 1
    Unrelated to this question but smso=$(tput smso) is shell syntax. Awk is not shell. You can't directly call arbitrary Unix commands like tput from awk any more than you could call them directly from C. You should ask a new question about that if you can't figure out how to do whatever it is you're trying to do there (I personally have posted several answers on this forum for how to use tput with awk_shell so look in the archives).
    – Ed Morton
    Commented Oct 27, 2023 at 15:40
  • I was actually going to inquire about that as well haha. I was trying to bold the first row as thats where the column titles are. Any idea how I'd do that in awk?
    – Mathew
    Commented Oct 27, 2023 at 15:42
  • Regarding using tput or otherwise coloring output with awk, see these for some examples: stackoverflow.com/a/64046525/1745001, unix.stackexchange.com/a/669122/133219, unix.stackexchange.com/a/691810/133219.
    – Ed Morton
    Commented Oct 27, 2023 at 16:32

2 Answers 2

3

sprintf("%*s", n, string) left-pads the string to a length (in number of bytes or characters depending on the awk implementation) of n with spaces.

To repeat a string n times, you could write a helper function:

function repeat(n, string,  result) {
  while (n-- > 0) result = result string
  return result
}

Here, I'd suggest using mlr which does that (or something similar) out of the box (and processes CSVs correctly):

$ mlr --c2p --barred cat file.csv
+--------------+-----------+--------+----------------------------------------+
| NUMBER       | FNAME     | LNAME  | PHONE-TYPE:GROUPS                      |
+--------------+-----------+--------+----------------------------------------+
| 222-222-2222 | Elizabeth | Taylor | office:beauty:                         |
| 111-111-1111 | Matt      | Alex   | personal:superhuman:cool:amazing:extra |
+--------------+-----------+--------+----------------------------------------+

Or for right-aligned fields:

$ mlr --c2p --barred --right cat file.csv
+--------------+-----------+--------+----------------------------------------+
|       NUMBER |     FNAME |  LNAME |                      PHONE-TYPE:GROUPS |
+--------------+-----------+--------+----------------------------------------+
| 222-222-2222 | Elizabeth | Taylor |                         office:beauty: |
| 111-111-1111 |      Matt |   Alex | personal:superhuman:cool:amazing:extra |
+--------------+-----------+--------+----------------------------------------+

If mlr is not available, rather than awk, I'd use perl which has largely superseded awk.

perl -MList::Util=zip -MTerm::ANSIColor -F, -lane '
  push @rows, [@F];
  $i = 0; for (@F) {
    $max[$i] = length if length > $max[$i];
    $i++;
  }
  
  END {
    $line = join("+", "", (map {"-" x ($_ + 2)} @max), "");
    print $line;
    print join("|", "", (map {colored(sprintf(" %*s ", @$_), "bold blue")} zip(\@max, shift @rows)), "");
    print $line;
    for (@rows) {
      print join("|", "", (map {sprintf " %*s ", @$_} zip(\@max, $_)), "");
    }
    print $line;
  }' file.csv

Which gives:

+--------------+-----------+--------+----------------------------------------+
|       NUMBER |     FNAME |  LNAME |                      PHONE-TYPE:GROUPS |
+--------------+-----------+--------+----------------------------------------+
| 222-222-2222 | Elizabeth | Taylor |                         office:beauty: |
| 111-111-1111 |      Matt |   Alex | personal:superhuman:cool:amazing:extra |
+--------------+-----------+--------+----------------------------------------+

With the headers in bold and blue.

In perl, repeating strings is done with the x repetition operator: "string" x 3 results in stringstringstring. See perldoc perlop for details.

Though in perl you rarely need to reinvent the wheel as in the 35 years it's been around, perl modules have been written for about anything. We've already used the List::Util module with some useful list manipulation functions and Term::ANSIColor to output coloured text above, but there's also a Text::CSV module to process CSVs and Text::ASCIITable to format tables:

$ perl -MText::CSV=csv -MText::ASCIITable -e '
  $c = csv(in => shift);
  $t = Text::ASCIITable->new;
  $t->setCols(shift @$c);
  $t->addRow($_) for @$c;
  print $t' file.csv
.----------------------------------------------------------------------------.
| NUMBER       | FNAME     | LNAME  | PHONE-TYPE:GROUPS                      |
+--------------+-----------+--------+----------------------------------------+
| 222-222-2222 | Elizabeth | Taylor | office:beauty:                         |
| 111-111-1111 | Matt      | Alex   | personal:superhuman:cool:amazing:extra |
'--------------+-----------+--------+----------------------------------------'
1
  • I wasnt aware of mlr but this was more of a personal challenge because I'm trying to get better with awk
    – Mathew
    Commented Oct 27, 2023 at 15:46
2
printf("%*s", longest_line, "=")

prints a = character indented by longest_line-1 blanks. You want this instead:

line = sprintf("%*s", longest_line, "")
gsub(/ /,"=",line)
print line

FWIW this is how I'd write the code to do what you appear to be trying to do, using any awk:

$ cat tst.awk
BEGIN { FS=","; OFS="|"; hdrChar="=" }
{
    for (colNr=1; colNr<=NF; colNr++) {
        val = $colNr
        vals[NR,colNr] = val
        wid = length(val)
        wids[colNr] = (wid > wids[colNr] ? wid : wids[colNr])
    }
}
END {
    for ( colNr=1; colNr<=NF; colNr++ ) {
        row = row hdrChar sprintf("%*s",wids[colNr],"")
    }
    gsub(/ /,hdrChar,row)
    print row hdrChar

    for ( rowNr=1; rowNr<=NR; rowNr++ ) {
        row = ""
        for ( colNr=1; colNr<=NF; colNr++ ) {
            row = row OFS sprintf("%*s",wids[colNr],vals[rowNr,colNr])
        }
        print row OFS
    }
}

$ awk -f tst.awk file
======================================================================
|      NUMBER|    FNAME| LNAME|                     PHONE-TYPE:GROUPS|
|222-222-2222|Elizabeth|Taylor|                        office:beauty:|
|111-111-1111|     Matt|  Alex|personal:superhuman:cool:amazing:extra|

Pad some fields with spaces as you see fit by changing val = $colNr to val = " " $colNr " " or similar.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .