9

I have a couple of commands in an awk script I'm writing:

print "Here are some players and their numbers, sorted by last name"
if(sum[x] > 500) {print x, $2}

Which outputs:

Here are some players and their numbers, sorted by last name
Lebron James 23
Kevin Durant 35
Kobe Bryant 24
Blake Griffin 32
Dikembe Mutumbo 55

How can I use the sort command in my awk script to sort the players and their numbers ONLY?

muru
  • 193,181
  • 53
  • 473
  • 722
Anonymous
  • 405
  • 2
  • 6
  • 12
  • 3
    Given your comments on the answers, you seem to be confusing **awk** and **shell** script in your question. It seems that you wish to do the sorting _within your awk script_, not in the _shell_ script invoking it. If that is correct, then please edit your question and replace the two occurrences of 'shell' by 'awk'. On a separate note: yes, awk has a sorting facility, but it is quite involved: you must store all lines in an array, keyed on their second field, which you'll need to extract from `x`, then set `PROCINFO["sorted_in"]` to a cryptic value, then output the array. I wouldn't go there. – zwets Nov 01 '16 at 09:30
  • 1
    I mean: I wouldn't go there given the simplicity of `... | sort -k2,2`. – zwets Nov 01 '16 at 09:31
  • @zwets How would I implement `...| sort -k2,2` if there are other lines that need to be printed? Check edited question. – Anonymous Nov 02 '16 at 00:25
  • By `echo`-ing the header line from the shell, then run the `awk | sort` pipeline. – zwets Nov 02 '16 at 05:03

7 Answers7

12

you can add | sort -k2 to your command. This will sort alphabetically based on the second column.

Example:

$ echo "Lebron James 23
Kevin Durant 35
Kobe Bryant 24
Blake Griffin 32
Dikembe Mutumbo 55" | sort -k2

results in

Kobe Bryant 24
Kevin Durant 35
Blake Griffin 32
Lebron James 23
Dikembe Mutumbo 55
Wayne_Yux
  • 4,873
  • 3
  • 27
  • 35
  • Unfortunately I'm using a script, and the sort command will be mashed up with many other outputs. Is there a way to sort the output of `{print x, $2}` directly in the script code? I'm getting an error when piping `if(sum[x] > 500) {print x, $2} | sort -k2`. – Anonymous Nov 01 '16 at 07:38
  • 3
    @KMoy: `if(sum[x] > 500) {print x, $2}` is Awk code while `| sort -k2` is a shell command. Obviously you can't mix the two like that because they're different languages. Instead you need to apply the `sort` command to the output of the Awk interpreter that runs your Awk code snippet. If you don't know what I mean please expand your question to give us the full picture. – David Foerster Nov 01 '16 at 07:48
  • 1
    You are writing a shell script, right? Then you have two option: 1. run `./my-script.sh | sort -k2`. 2. add ` | sort -k2` to the line of your script that produces the output given in your question. – Wayne_Yux Nov 01 '16 at 08:43
  • @Wayne_Yux Please check edits made to original question. – Anonymous Nov 01 '16 at 20:07
  • Then you probably need the answer from @steeldriver – Wayne_Yux Nov 01 '16 at 20:21
9

Although I wouldn't recommend it (given the relative simplicity of piping the result through an external sort command) you can do this at least with recent versions of GNU awk (at least 4.0 IIRC), as described at Sorting Array Values and Indices with gawk

Here's how you could implement it, assuming you have the data in an associative array in which the index is Firstname Lastname. First you need to define a custom comparison function that splits the index, compares first on Lastname then (as a tie breaker) on Firstname e.g.

function mycmp(ia, va, ib, vb, sa, sb) {
  if(split(toupper(ia), sa) && split(toupper(ib), sb)) {
    if(sa[2] < sb[2]) return -1;
    else if (sa[2] > sb[2]) return 1;
    else {
      # compare first names
      if(sa[1] < sb[1]) return -1;
      else if (sa[1] > sb[1]) return 1;
      else return 0;
    }
  }
  else return 0;
}

Now you can use the PROCINFO["sorted_in"] array sorting method mentioned in comments by @zwets

PROCINFO["sorted_in"] = "mycmp";
for(i in a) print i, a[i];

Putting it together

#!/usr/bin/gawk -f

function mycmp(ia, va, ib, vb, sa, sb) {
  if(split(toupper(ia), sa) && split(toupper(ib), sb)) {
    if(sa[2] < sb[2]) return -1;
    else if (sa[2] > sb[2]) return 1;
    else {
      # compare first names
      if(sa[1] < sb[1]) return -1;
      else if (sa[1] > sb[1]) return 1;
      else return 0;
    }
  }
  else return 0;
}

{
  a[$1" "$2] = $3;
}

END {
  PROCINFO["sorted_in"] = "mycmp";
  for(i in a) print i, a[i];
}

Testing:

$ ./namesort.awk yourfile
Kobe Bryant 24
Kevin Durant 35
Blake Griffin 32
Lebron James 23
Dikembe Mutumbo 55

In lesser or older versions of awk, your best bet may be to store the data indexed by Lastname Firstname instead, sort with the conventional asorti, then split and swap the fields of the indices as you traverse the array to print it:

awk '
  {a[$2" "$1]=$3} 
  END {
    n=asorti(a,b); for (i=1;i<=n;i++) {split(b[i],s); print s[2], s[1], a[b[i]]}
}' yourfile
dessert
  • 39,392
  • 12
  • 115
  • 163
steeldriver
  • 131,985
  • 21
  • 239
  • 326
5

To sort only by the whitespace separated second field, use key -k2,2:

... | sort -k2,2

by default sort does the sorting lexicographically.

Note that, if you don't mention the last field for the sorting key i.e. if you just use -k2 then you might not get the desired result as this will sort according to all fields starting from second.

Also check man sort.

heemayl
  • 90,425
  • 20
  • 200
  • 267
1
print "Here are some players and their numbers, sorted by last name"
if(sum[x] > 500) {print x, $2 | "sort -k2,2"}

To sort the output to a file:

print "Here are some players and their numbers, sorted by last name"
if(sum[x] > 500) {print x, $2 | "sort -k2,2 > sortedFile"}
D.M.Bailey
  • 11
  • 1
1

Try

awk -f myscript.awk | sort -k2

Where myscript.awk contains purely awk commands.

If your actual script is a shell script, you have several options including

  • Pipe output through sort. ./myscript.bash | sort -k2
  • Rewrite code as a function inside the script
    Instead of

    $ cat t1
    #!/bin/bash
    for i in 2 4 3 1 5;
    do
      echo $i
    done
    
    $ ./t1
    2
    4
    3
    1
    5
    

    Do

    $ cat t2
    #!/bin/bash
    function foo {
      for i in 2 4 3 1 5;
      do
        echo $i
      done
    }
    foo | sort
    
    $ ./t2
    1
    2
    3
    4
    5
    

But note you can also apply the sort to the do...done structure instead of making a function.

    do
       echo $i
    done | sort
RedGrittyBrick
  • 270
  • 1
  • 2
  • 7
  • Why define the function? – zwets Nov 02 '16 at 05:11
  • @zwets, it makes it easier to feed the results of arbitrary code, including looping control structures, through a pipeline. There are cases where it is unnecessary but I find it a useful general pattern. I'll edit my answer to demonstrate this. – RedGrittyBrick Nov 02 '16 at 10:34
1

To sort your data to print:

  • Suppose you want to print 2nd field (whitespace separated) use this:

    awk '{print $2}' data.txt | sort
    

    e.g.:

    $cat>data.txt
    1 Kedar 20
    2 Amit 30
    3 Rahul 21
    ^C
    
    $awk '{print $2}' | sort
    Amit
    Kedar
    Rahul
    
  • If you want to print the whole of your data.txt but sorted on column 2, then:

    $awk '{print}'|sort -k2
    2 Amit 30
    1 Kedar 20
    3 Rahul 21
    

Use this logic(s) in your requirement.

You may use man sort for more interesting features of sort.

Melebius
  • 11,121
  • 8
  • 50
  • 77
0

what about below:

 awk 'BEGIN{str="1\n2\n3\n4"; system("echo -e \""str"\" | sort -r")}'

it work when i tested.