I have to sort all of the words from a file given as parameter in a shell script. Here is the one-liner:

tr  [:space:] '\n' <$1  | sort -nrk2,2 | uniq -c |sed 's/^ \+//g'

Basically, if I have something like this in my file:

bla bla bla bla hu hu hu

It will output

4 bla
3 hu

I want them to be something like

bla 4
hu 3
  • Also I believe you should replace sort -nrk2,2 with just sort -nr. In this case k2,2 just doesn't do anything since you trimmed the whitespace beforehand (i.e. there is just one field), but normally that wouldn't necessarily produce a file sorted the way uniq -c requires. – kos Jan 16 '16 at 17:02

You could replace your sed command with a simple awk command that swaps the field order

... | awk '{print $2,$1}'
  • What about if i have, let's say ,2 words with exactly the same number of occurences in a text , but i want to print only the first in alphabetically order ( let's say my output in this case is steel 4 and driver 4 ,but i will print only driver 4)? – gigiman Jan 16 '16 at 16:32
  • 1
    @gigiman you could either add an additional sort -uk1,1 in the pipeline, or modify the awk to build an array of the first result for each count, and output the array at the end of processing e.g. awk '!a[$1]{a[$1]=$2} END{for (i in a) print a[i],i}' – steeldriver Jan 16 '16 at 16:44
  • Wow , thanks so much steeldriver you're really a life saver ! – gigiman Jan 16 '16 at 16:50

There are many, many ways of doing this. Steeldriver already gave you the classic awk approach. Here are some other choices:

  1. Use sed to capture two groups of non-whitespace (\S) characters and then switch them around:

    ... | sed -E 's/\s*(\S+)\s+(\S+)/\2 \1/'
  2. Use perl. Its -a switch makes it work like awk. It will automatically split each input line on whitespace and save each field as an element of the array @F. Therefore, the 1st field will be $F[0], the second $F[1] etc.:

    ... | perl -lane 'print "$F[1] $F[0]"'
  3. Use Perl for the whole thing:

    perl -lane '$k{$_}++ for @F; }{ print "$_ $k{$_}" for keys(%k)' "$1"

    Here, perl reads the input file line by line and applies the script to each line. $k{$_}++ for @F saves each word (each field from @F) as a key in the hash %k and increments the associated value by one each time the word is seen. Then, after the file has been processed (that's what the }{ means), it will print the word ($_) and the number of times it was seen ($k{$_}) for each key stored in the hash.

  4. Use awk for the whole thing:

    awk '{for(i=1;i<=NF;i++){a[$i]++}}END{for(i in a){print i,a[i]}}' "$1"

    The first for loop iretares over each field and adds one to the value associated with that field in the array a. Then, at the end of the file, it loops over each element in a and prints the element (the word) and the associated value (the number of times the word was seen).

  5. Use the shell with your original pipeline:

    ... | while read a b; do echo "$b $a"; done
  • +1 nice roundup! these kinds of questions always make me want to quote the barometer question – steeldriver Jan 16 '16 at 17:05
  • @steeldriver heh, indeed. – terdon Jan 16 '16 at 17:11
  • @terdon thanks alot , now i have a much more ample vision about formatting in bash :) – gigiman Jan 16 '16 at 18:03
  • Come on Guru..i want some free points too ;) – heemayl Jan 17 '16 at 4:11

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.