2

I have a file that looks like the following (the columns are separated by tabs and might contain spaces):

    0637    apple    8528    1392    orange    1390    8528    unknown fruit    7537    1039    banana    1892    0989    pineapple    7537    8528    melon    7537    8528    grape    7537    8528    (null)    (null)

And I need to split it each 3 columns, grepping the ones that will result in [0-9]$ (I'm using awk because is much faster than grep on large files):

0637    apple    8528
1392    orange    1390
8528    unknown fruit    7537
1039    banana    1892
0989    pineapple    7537
8528    melon    7537
8528    grape    7537

Now, I have this beautiful command that does it:

awk -F\\t '{print $2 "\t" $3 "\t" $4 "\n" $5 "\t" $6 "\t" $7 "\n" $8 "\t" $9 "\t" $10 "\n" $11 "\t" $12 "\t" $13 "\n" $14 "\t" $15 "\t" $16 "\n" $17 "\t" $18 "\t" $19 "\n" $20 "\t" $21 "\t" $22 "\n" $23 "\t" $24 "\t" $25}' filename | awk '/[0-9]$/'

I have also an ugly cut command that does the same. I'm still learning awk, so I believe there is a much wiser way to do this. Also, I might get into trouble if there is more than $25. Can you help me?

0
2

This works:

$ cat splitnum.awk
#!/bin/awk -f
BEGIN {
    FS = OFS = "\t"
}
{
    for ( i = 1; i < NF; i = i + 3) {
        if ( $(i+2) ~ /[0-9]+/ ) {
            print $i, $(i+1), $(i+2)
        }
    }
}
$ awk -f splitnum.awk filename

Or all on one line:

awk 'BEGIN{FS=OFS="\t"}{for (i=1;i<NF;i=i+3){if ($(i+2) ~ /[0-9]+/) {print $i, $(i+1), $(i+2)}}}' filename

Basically cycling over the data fields, three at a time, checking that the third is made up of numerals.

4
  • It doesn't work with the example above (replacing four spaces with tabs). Feb 4 '12 at 7:31
  • Oh, I see! It works if I remove the first tab (every line starts with a tab before data). Feb 4 '12 at 7:36
  • for (i=2; i<NF; i=i+3) to ignore first tab Feb 4 '12 at 15:42
  • 1
    @Arcege nit: $ awk -f whatever.awk is not necessary if the shebang #! has already specified /bin/awk -f. In your case, $ ./splitnum.awk filename will work a treat. The gain is: less typing effort. Dec 15 '14 at 19:21
2

Supposing the fields are separated by one or more tabs, and that each field can contain spaces but not tabs, then the following should work

( IFS=$'\t'; printf '%s %s %s\n' $(<input_file) )

(the subshell is convenient to not change IFS for the calling shell).

Alternatively, if fields are separated by one or more spaces and tabs, and that each field do not contain spaces nor tabs, then the following should work

printf '%s %s %s\n' $(<input_file)
3
  • Man, this is awesome :D Could you please explain what $ means in IFS? Feb 4 '12 at 14:35
  • IFS=$'\t' means to use a literal tab character as an Input Fields Separator.
    – enzotib
    Feb 4 '12 at 14:49
  • Unfortunately it fails if fruit is empty! Feb 4 '12 at 15:44
1
awk -F'\t' '{for(i=1;i<=NF;i++)if(!(i%3))$i=$i "\n"}1' file
1
  • Almost! The resulting columns should be separated by tabs too to avoid confusion. And blank spaces and some empty lines are being printed. Feb 4 '12 at 7:42
1
$ tr '\t' '\n' <file | paste - - -
0637    apple   8528
1392    orange  1390
8528    unknown fruit   7537
1039    banana  1892
0989    pineapple       7537
8528    melon   7537
8528    grape   7537
8528    (null)  (null)

This replaces the tabs by newlines, and then lets paste format it as three columns (filling the columns row-wise).

The output is tab-delimited.

If the file starts with an initial tab:

cut -f 2- <file | tr '\t' '\n' | paste - - - 
0

For awk haters:

perl -pe 's/(\d+)\s+(\d+)/$1\n$2/g' | 
perl -lnpe 's/^\s*(\d+)\s+([^\d]+?)\s+(\d+).*/$1\t$2\t$3/' |
egrep '[0-9]$'
0

An even worse perl answer:

perl -pnle 's/\s+/\t/g;s/(\d)\t(\d)/$1\n$2/g;s/^\s+|\s+$//;s/.*[^\d]$//'|grep .
1
  • Thank you, but both perl commands don't give the desired output. Feb 4 '12 at 14:33

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.