Outputting sequences based on length with sed

I have this file:

>ID1
AA
>ID2
TTTTTT
>ID-3
AAAAAAAAA
>ID4
TTTTTTGGAGATCAGTAGCAGATGACAG-GGGGG-TGCACCCC

Add I am trying to use this script to output sequences longer than 15 characters:

sed -r '/^>/N;{/^.{,15}$/d}'

The desire output would be this:

>ID4
TTTTTTGGAGATCAGTAGCAGATGACAG-GGGGG-TGCACCCC

I am also trying to output the sequences that are shorter than 15 characters using the following script:

sed -rn '/^>/N;{/^.{,15}$/p}' 

The desire output is:

>ID1
AA
>ID2
TTTTTT
>ID-3
AAAAAAAAA

How can I modify my scripts so I can generate the desire outputs?
Thanks in advance

Why do you require that only sed be used?

If we were allowed to use awk I would find it much easier (assuming that the input file always contains pairs of lines and the line length you want to check is always just the second line in each set). (If my assumption is wrong, you need to much more clearly state what you are trying to do!)

Let us first examine why your script doesn't do what you want it to do. As a scientist you surely know that finding out why something doesn't work is as well a publishable result as is finding out why it does work. ;-))

sed -r '/^>/N;{/^.{,15}$/d}'

My first suggestion is to get into the habit of writing sed-scripts line by line and not crammed into one line. It is easier to understand what is done when this way. I will only repeat the script itself, minus the not necessary curly braces:

/^>/ {
     N
}
/^.{,15}$/ {
     d
}

So there is a rule for lines starting with ">" and it reads the next line. Since every second line starts with ">" we always end up with two lines - the ">"-line and its successor - in the pattern space. Then there is a rule for a pattern space with up to 15 characters and it says: delete.

First, the curly braces in "{,15}" need to be escaped: \{,15\} . Furthermore i am not sure if every (or, more specifically: your) sed -version understands "\{,15\}", maybe it needs to be "\{1,15\}". But this means "one to fifteen"! If you want to test if a string is longer than 15 you need to make that unconditional: "\{15\}".

The meaning of the multiplicators is:

\{n\}      # exactly n occurrences of the last expression
\{n,\}     # n or more occurrences of the last expression
\{m,n\}    # between m and n occurrences of the last expression
\{,n\}     # at most n occurrences of the last expression (basically the same as \{1,n\})

There is also another logical problem: when the first line starts with ">ID.." then after adding the next line to the patternspace the content is like this:

>ID1\nAA

Each of these characters ("\n" is a character too here) counts towards the fifteen threshold, in the example the pattern space is 7 characters long, not 2. I am not sure if this is what you want.

Since we "know" that the structure of your input is to have a ">"-line every second line (otherwise your automatic loading of exactly one line would be wrong too) we can drop the (always complicated and tricky) multiline-patternspaces and use the hold space:

  • In case of a ">"-line we move it to the hold space and do nothing else.
  • In case of a not-">"-line we test it and if it is long we output the hold space contents, then the line.

Remove the comments before trying it:

/^>/ {           # all lines starting with a ">"
     h                # put it in the hold space
     d                # and delete it (starts over automatically)
}
/^.\{15\}.*$/ {     # all lines with 15 or more characters
     x                # exchange hold space and pattern space (">"-line is in pattern space now)
     p                # print the pattern space
     x                # exchange hold space and pattern space (second line is in pattern space now)
     p
}

You can do that by simply reversing the second rule:

/^>/ {
     h
     d
}
/^.\{15\}.*$/ ! {
     x
     p
     x
     p
}

I hope this helps.

bakunin

4 Likes

Note that the following awk code produces both output files you want in one invocation (with pairs with 2nd lines longer than 15 characters in a file name long and pairs with 2nd lines shorter than 16 characters in a file named short ):

awk '
/^>/ {	hdr = $0
	next
}
{	printf("%s\n%s\n", hdr, $0) > (length($0) > 15 ? "long" : "short")
}' file

If you want to try this on a Solaris/SunOS system, change awk to nawk or /usr/xpg4/bin/awk .

3 Likes
grep -x -B1 '^[^>]\{,15\}'
grep -x -B1 '^[^>]\{15,\}'
2 Likes

Yes, true - but this works only with GNU- grep , not with a standard-(POSIX-) grep because "-B" is not a POSIX-switch.

It is a hard lesson one learns in administrating very diverse large datacenters that in order to avoid "bad surprises" one sticks as close to the standards as possible. Otherwise you have to develop not "a script" but one for AIX this version, one for AIX that version, one completely different for HP-UX, another one for Solaris, several dozens for the various Linuxes....

Not that following the standards rules out bad surprises anyway, mind you, which is another hard lesson....

I hope this helps.

bakunin

2 Likes

It might be worthwhile to mention that this code should be run with

  • the -r flag removed
  • the -n flag set
  • the -fscriptname option added:
sed -nfscriptname datafile
1 Like

Guys
Thank you so very much! Bakunin, it did not even cross my mind to use the Hold, Get and Exchange commands for this task. Thanks a TON for a very detailed explanation. Don, thank you so very much for your solution too. Nezabudka, I do use GNU so your solution fits my app perfectly -appreciate it! Rudy, thank you so much! I spent 5 mins getting a wrong output because I wasn't using the -n flag.
Once again, thanks to all of you!

Thanks for the great analysis of the submitted code, explanation of what was wrong, and the suggested work around.

As you said, the standards only define the 1st three forms of interval expressions shown above. The fourth form is not in the standards and is not provided by all RE implementations. (One that does not support this form is the BSD RE parser that is used on many BSD, OSX, and macOS implementations.)

In the \{m,n\} form, m is required to be an integer greater than or equal to zero. So on systems that do accept the 4th form, I would expect \{,n\} to be equivalent to \{0,n\} instead of \{1,n\} (but I don't currently have a system handy where I can verify this case).

Nice suggestion.

As noted above, however, even for systems that do support the -B option, you still need to supply a lower bound on the interval expression to be portable:

grep -x -B1 '^[^>]\{0,15\}'
1 Like