I've written a grep loop to iteratively count DNA trinucleotides within a gzipped DNA fasta file containing DNA sequences e.g.

declare -a tri=(AAA AAC AAG AAT CAA .. etc)

for i in ${tri[@]}
   gzip -cd gencode.v18.pc_transcripts.fa.gz | grep -v "^>" | grep -o $i | wc -l

Where the fasta file is in this format (though much much bigger)

head test.fa

Whilst this works (i.e. counts occurrences of each trinucleotide) it is to my mind quite inefficient as it has to pass through the data 64 times (once for each possible trinucleotide).

My question is how using bash or grep is there a way I can count each trinucleotide in a single pass through the file (as the files are quite large)?


  • you could refer to superuser.com/questions/529821/grep-count-multiple-occurrences – Kiwy Feb 11 '14 at 12:40
  • 1
    Fasta files are usually folded at 60 characters per line, this means that your grep will not report the counts accurately, since you are not taking into account line breaks. Also, do you need to take frames into account? – terdon Feb 11 '14 at 13:35
  • these fasta are not folded, perhaps to avoid that sort of problem. I think the behaviour of 3rd awk solution below is what I want as I'm calculating the fraction of potential different mutation sites (hmmm probably) not looking at the actual codon code. – Stephen Henderson Feb 11 '14 at 13:53
  • Why would you be reading triplets to check mutation sites? All it takes is a single nt. Reading triplets implies codons and, if this is raw DNA data, you have 6 possible frames you need to check and you're only checking one. You might want to post a question on Biology or biostars.org or here (if you make it about the *nix side of things). I am not completely clear on what you are attempting but it sounds strange. – terdon Feb 11 '14 at 18:28
  • @terdon Yes but different types of mutagen cause point mutations at quite different DNA contexts triplets e.g UV, smoking, age, mismatch repair, not at all randomly across single sites. To deconvolute the relative effect size of mutagen signals you also need to (OK, I need to) estimate the empirical fractions of these different sites (amongst other things). A similar approach is explained here: go.nature.com/PRdkuZ – Stephen Henderson Feb 11 '14 at 19:50
gzip -dc file.gz | grep -v '^>' | grep -Foe "${tri[*]}" | sort | uniq -c

But by the way, AAAC matches both AAA and AAC, but grep -o will output only one of them. Is that what you want? Also, how many occurrences of AAA in AAAAAA? 2 or 4 ([AAA]AAA, A[AAA]AA, AA[AAA]A, AAA[AAA])?

Maybe you want instead:

gzip -dc file.gz | grep -v '^>' | fold -w3 | grep -Fxe "${tri[*]}" | sort | uniq -c

That is split the lines in groups of 3 characters and count the occurrences as full lines (would find 0 occurrence of AAA in ACAAATTCG (as that's ACA AAT TCG)).

Or on the other hand:

gzip -dc file.gz | awk '
  !/^>/ {l = length - 2; for (i = 1; i <= l; i++) a[substr($0,i,3)]++}
  END{for (i=1;i<n;i++) printf "%s: %d\n", ARGV[i], a[ARGV[i]]}' "${tri[@]}"

(would find 4 occurrences of AAA in AAAAAA).

  • thx, I will test this now. Those are good points my previous loop would count AAA and AAC but was intended to count 2 AAA not 4 in AAAAAA. But now you mention it these seem incompatible to me. I will try and test your suggestions to get the behaviour I need. Many thx again. – Stephen Henderson Feb 11 '14 at 13:18
  • Thx again that's actually more than answered my first question and pushed on to all sorts of different approaches. I'd up vote more if I could :) – Stephen Henderson Feb 11 '14 at 13:38

@stéphane-chazelas 's second example is great, but the sort command could really slow this down as the data gets larger.

Can we assume that the characters on non-header rows are valid nucleotides? That would remove the tri matching.

gzip -dc file.gz | grep -v '^>' | fold -w3 | awk '{a[$0]++} END{for(codon in a) {printf "%s: %d\n", codon, a[codon]}'

On a related note, converting the 3-nucleotide tokens to 2-byte octal (or binary from there) would reduce the size of the data stream by 2/3 or 1/3.

gzip -dc file.gz | grep -v '^>' | tr ACGT 0123 | fold -w3 | cat <(echo "obase=8; ibase=4;" ) - | bc | xargs printf "%02d\n" | tee foo.octal | xxd -r -p > foo.bin

Back to nucleotides

cat foo.bin | xxd -p | fold -w2 | cat <(echo "obase=4; ibase=8;") - | bc | xargs printf "%03d" | tr 0123 ACGT

xargs is probably the limiting step here, perhaps parsing N lines at a time would fix that or using gnu parallel.

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.