I want to count the A's T's C's G's N's and "-" characters in a file, or every letter if needed, is there a quick Unix command to do this?
| show 8 more comments | |
We're looking for long answers that provide some explanation and context. Don't just give a one-line answer: please explain why you're recommending it as a solution. Answers that don't explain anything will be deleted. See Good Subjective, Bad Subjective for more information. |
|
|
If you want some real speed:
Is an incredibly fast pseudo-one-liner. A simple test shows that on my Core i7 CPU 870 @ 2.93GHz it counts at just over 600MB/s:
Unlike solutions involving sorting, this one runs in constant (4K) memory, which is very useful, if your file is far larger than your ram. And, of course with a little bit of elbow grease, we can shave off 0.7 seconds:
Nets just over 1.1GB/s finishing in:
For comparison, I tested some of the other solutions on this page which seemed to have some kind of speed promise. The
The perl method seemed promising as well, but I gave up after running it for 7 minutes
|
|||||||||||||||||||||
|
|
Will do the trick as a one liner. A little explanation is needed though.
If foo.txt contained the string
|
|||||||||||||||||||||
|
|
Try this one, inspired by @Journeyman's answer.
The key is knowing about the -o option for grep. This splits the match up, so that each output line corresponds to a single instance of the pattern, rather than the entire line for any line that matches. Given this knowledge, all we need is a pattern to use, and a way to count the lines. Using a regex, we can create a disjunctive pattern that will match any of the characters you mention:
This means "match A or T or C or G or N or -". The manual describes various regular expression syntax you can use. Now we have output that looks something like this:
Our last step is to merge and count all the similar lines, which can simply be accomplished with a
Which, when piped through
Addendum: If you want to total the number of A, C, G, N, T, and - characters in a file, you can pipe the grep output through |
|||||||||
|
|
One liner counting all letters using Python:
...producing a YAML friendly output like this:
It's interesting to see how most of the times Python can easily beat even bash in terms of clarity of code. |
||||
|
|
|
After using UNIX for a couple of years, you get very proficient at linking together a number of small operations to accomplish various filtering and counting tasks. Everyone has their own style-- some like To process a particular filename:
or as a filter:
It works like this:
I fed it "Hello, world!" followed by a newline and got this:
|
||||
|
|
|
Similar to Guru's
|
||||
|
|
|
The
|
|||||||||
|
|
You can combine
To do this for a number of characters at once, put the characters in an array and loop over it:
Example: for a file containing the string
For more information, see The downside of this approach, as user Journeyman Geek notes below in a comment, is that |
|||||
|
|
Using the sequence lines from 22hgp10a.txt the timing difference between grep and awk on my system make using awk the way to go... [Edit]: After having seen Dave's compiled solution forget awk too, as his completed in ~ 0.1 seconds on this file for full case sensitive counting.
The case insensitive version of ghostdog's completed in ~ 14 seconds. The sed is explained in the accepted answer to this question. |
|||||
|
|
I think any decent implementation avoids sort. But because it's also bad idea to read everything 4 times, I think one could somehow generate a stream that goes through 4 filters, one for each character, which is filtered out and where the stream lengths are also somehow calculated.
The cumulative sums are then in tmp[0-6].txt .. so work is still in progress There are merely 13 pipes in this approach, which converts to less than 1 Mb of memory.
|
||||
|
|
|
I didn't knew about If you know there is only "good" characters (those you want to count) in your file, you can go for
If only some characters must be counted and others not (i.e. separators)
The first one uses the regular expression wildcard Note that "sort" also has a |
||||
|
|
|
A silly one:
That's an alternative to the non-standard (GNU) |
||||
|
|
The output format is not the best...
Theory of operation:
Speed seems to be 60MBps + |
||||
|
|
|
Sample file:
Command:
|
||||
|
|
|
Short answer: If circumstances permit, compare file sizes of low character sets to one with no characters to get an offset and just count bytes. Ah, but the tangled details: Those are all Ascii characters. One byte per. Files of course have extra metadata prepended for a variety of stuff used by the OS and the app that created it. In most cases I would expect these to take up the same amount of space regardless of metadata but I would try to maintain identical circumstances when you first test the approach and then verify that you have a constant offset before not worrying about it. The other gotcha is that line-breaks typically involve two ascii white space characters and any tabs or spaces would be one each. If you can be certain these will be present and there's no way to know how many beforehand, I'd stop reading now. It might seem like a lot of constraints but if you can easily establish them, this strikes me as the easiest/best performing approach if you have a ton of these to look at (which seems likely if that's DNA). Checking a ton of files for length and subtracting a constant would be gobs faster than running grep (or similar) on every one. If:
And Two Things That Might Not Matter But I Would Test With First
Try Finding The Offset By Doing the Following: Compare an empty file to one with a few easily-human-counted characters to one with a few more characters. If subtracting the empty file from both of the other two files gives you byte counts that match character count, you're done. Check file lengths and subtract that empty amount. If you want to try to figure out multi-line files, most editors attach two special one-byte characters for line breaks since one tends to be ignored by Microsoft but you'd have to at least grep for white-space chars in which case you might as well do it all with grep. |
||||
|
|
|
Haskell way:
it works like this:
compiling and using:
not good for huge files maybe. |
||||
|
|
|
Combining a few others
Add |
||||
|
|
|
Quick perl hack:
Characters which don't occur at all won't be included in the result. |
||||
|
|
[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending– Guillaume86 Oct 10 '12 at 14:53Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending– Guillaume86 Oct 10 '12 at 16:33