1

I need to transform some data into assignments. I'm pretty sure it looks like a pretty straightforward job for awk, but I am far from confortable with it.

Each data element (and columns) are tab-separated. Data elements may contain spaces and special characters, but no TABs.

example input :

column1 column2 column3
rowA1   rowA2   rowA3
rowB1   rowB2   rowB3

expected output :

column1 = rowA1
column2 = rowA2
column3 = rowA3

column1 = rowB1
column2 = rowB2
column3 = rowB3

(with arbitrary number of rows, not exceeding hundreds)

Any clue how to do this ? (with awk or any standard command-line tool on a linux)

5

For example:

{
    if (NR==1){
        for (i=1; i<=NF; ++i){
            arr[i] = $i
        }
    }else{
        for (i=1; i<=NF; ++i){
            print(arr[i]," = ",$i)
        }
    }
    print("")
}

To run:

awk -f script.awk input
  • I needed to insert this first line into your script: BEGIN { FS = "[\t]+" } ; , otherwise the fields containing spaces were shifted to wrong columns. It works nicely now. Thank you for helping me with my first awk task! – Pac0 Jul 25 '17 at 8:44
  • @Pac0 Right, I forgot you mentioned your file is separated by tabs. – pfnuesel Jul 25 '17 at 9:02
  • good script. i'd upvote if you took the time to explain how it works and turn it into a detailed answer. teaching is better than cargo-culting. Pac0 seems to understand it but anyone else who stumbles across this may not. Also, you seem to have a habit of writing awk script fragments and then saying "run with awk -f script.awk input" - that works, but why not just make it a #!/usr/bin/awk script? – cas Jul 25 '17 at 11:42
3
cat data |
while IFS=$'\t' read -r -a a; do
   case ${flag+'set'} in
      "set" )
         set -- "${a[@]}"
         for c in "${C[@]}"; do echo "$c = $1"; shift; done
         echo ;;

      * ) C=( "${a[@]}" ); flag= ;;
   esac
done

sed -Ee '
   1h;1N
   /^\n$/{
      $d;P;g;N
   }
   s/^(\S+)\s*((\S.*)?)\n(\S+)\s*((\S.*)?)/\1 = \4\n\2\n\5/
   P;D
' data

perl -F'\t+' -lane '
   @C or @C = @F,next;
   print "$C[$_] = $F[$_]" for 0 .. $#C;
   eof or print q[];
' data

Result

column1 = rowA1
column2 = rowA2
column3 = rowA3

column1 = rowB1
column2 = rowB2
column3 = rowB3

Explanation

  1. bash

    *) Store the first line in an array C during the time the flag is unset. Then promptly set it so that next time onwards we don't arrive here. *) The array a is split up into arguments using the set command. *) Then we cycle through the columns, accessed via, "${c[@]}" in a for loop and printed alongwith $1 ( which is then shifted out) *) Note IFS is set to tab via the $'\t' construct. Since it is one of special characters hence a run of these would be collapsed into one and so we won't be seeing empty fields.

  2. perl

    *) Set the FS to one or more TABs: -F'\t+' and turn on autosplit mode. *) Same logic as with bash based solution, wherein we store the columns data found in 1st line in the @C array. The arrays @C and current record fields data in @F are printed taking one from each.

  3. sed

    *) Here we first convert all TABs to spaces. *) Store the first line columns data in the hold space. *) For all the other lines, append the columns to the current row. *) Then we keep picking first elements from current row/column and shrinking the pattern space by taking away these printed stuff. *) Stop condition happens when no spaces are left.

  • I prefer pfnuesel's simple awk answer, but I appreciate the try anyway. and +1 for detailed explanations. (Well, i would have given +1 but I haven't enough reputation to upvote yet, sorry!) – Pac0 Jul 25 '17 at 12:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.