<p>The Python script in my first response is heavy on regex matching, which is pretty clunky in Python. I like Python much better than Perl overall, but a throwaway script like this will be clearer and more concise in Perl.</p>

<pre class="lang-perl prettyprint-override"><code>#!/usr/bin/env perl
use strict;

while(&lt;STDIN&gt;) {
    if (m/(\S+):(\d+)_(\d+)del([ACGT]+)ins([ACGT]+)/) {
        print("<span class="math-container">$1\t$</span>2\t<span class="math-container">$3\t$</span>5\t<span class="math-container">$4\n");
    }
    elsif (m/(\S+):(\d+)(del|dup)([ACGT]+)/) {
        if ($3 eq "del") {
            print("$1\t$2\t$2\t$4\t-\n");
        }
        else {
            print("$1\t$2\t$2\t-\t$4\n");
        }
    }
    elsif (m/(\S+):(\d+)([ACGT]+)&gt;([ACGT]+)/) {
        my $coord2 = $2;
        my $coord1 = $2 - 1;
        print("$1\t$coord1\t$coord2\t$3\t$</span>4\n")
    }
}
</code></pre>

<p>It's been several years since I wrote Perl on a regular basis, so there are probably ways to make the script even more clear and concise. (Although I have a love/hate relationship with the default/hidden variables, which can make a script more concise but also harder for a newcomer to understand.)</p>

<p>Invoked like so on the command line.</p>

<pre><code>[standage@lappy ~] $ ./transform &lt; f1
chr7 140453144 140453145 A T
chr7 140453135 140453136 A T
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -
</code></pre>

CC BY-SA 4.0
2019-09-23T15:13:03.327Z
2019-09-23T15:13:03.327Z
Daniel Standage
<p>Quick Perl solution (following on from Daniel's answer saying that this would be "clearer and more concise in Perl"). I was a bit confused with your BED output because you've treated the start/end locations of a single base deletion and insertion different to a SNP. In my example here, the start and end locations for those cases are the same to distinguish from the two-base change for TT->CA [this can be changed]:</p>

<pre><code>cat 10454.txt | \
perl -ne 'if(/^(.*?):([0-9]+)(_([0-9]+))?(del|dup)?([ACGT]+)((ins|&gt;)([ACGT]+))?/){
   print(join("\t",
         <span class="math-container">$1,
         $</span>2,
         <span class="math-container">$4?$</span>4:(<span class="math-container">$2),
         ($</span>8 eq "ins")?<span class="math-container">$9:($</span>5 eq "dup")?"-":<span class="math-container">$6,
         (($</span>5 eq "dup") || (<span class="math-container">$8 eq "ins"))?$</span>6:(<span class="math-container">$9)?$</span>9:"-")."\n")
}';
chr7    140453145       140453145       A       T
chr7    140453136       140453136       A       T
chr7    140453135       140453136       TT      CA
chr20   31022287        31022287        -       A
chr19   13054614        13054614        G       -
</code></pre>

<p><strong>Regular Expression</strong></p>

<p>This uses enclosing groups to describe the important bits of the sequence. The groups are numbered from 1 upwards, with each number corresponding to a left bracket (in order), capturing the entirety of the sub-expression until the paired right bracket. I'll describe the groups, which are referenced in the subsequent part of the script as <code>$&lt;group_number&gt;</code>:</p>

<ol>
<li>Any number of characters, up to (but not including) the first ':'</li>
<li>Any number of the characters '0' to '9' [i.e. numbers]</li>
<li>Optionally, a "_", followed by any number of the characters '0' to '9'</li>
<li>The numbers from Group <strong>[3]</strong>, i.e. excluding the "_"</li>
<li>Optionally, a "dup" or a "del"</li>
<li>Any number of characters selected from "A", "C", "G", "T" (i.e. bases)</li>
<li>Optionally, an "ins" or ">", followed by any number of bases</li>
<li>The "ins" or ">" from Group <strong>[7]</strong></li>
<li>The bases from Group <strong>[7]</strong></li>
</ol>

<p><strong>Print Code</strong></p>

<p>The <code>print</code> statement prints out a tab-separated list, finishing off with a line break. I'll describe each list element:</p>

<ol>
<li>Group <strong>[1]</strong></li>
<li>Group <strong>[2]</strong></li>
<li>If Group <strong>[4]</strong> exists, then Group <strong>[4]</strong>, otherwise Group <strong>[2]</strong></li>
<li>If Group <strong>[8]</strong> is "ins", then Group <strong>[9]</strong>; [otherwise]<br>
If Group <strong>[5]</strong> is "dup", then "-", otherwise Group <strong>[6]</strong></li>
<li>If Group <strong>[5]</strong> is "dup", or group <strong>[8]</strong> is "ins", then Group <strong>[6]</strong>[otherwise]<br>
If Group <strong>[9]</strong> exists, then Group <strong>[9]</strong>, otherwise "-"</li>
</ol>

CC BY-SA 4.0
2019-09-24T16:17:02.343Z
+1, although I'm tempted to quote Jurassic Park here: "You were so preoccupied with whether or not you could, you didn't stop to think if you should." 
Daniel Standage
CC BY-SA 4.0
2019-09-23T22:42:47.663Z
2019-09-24T20:03:03.917Z
2019-09-24T20:03:03.917Z
gringer
<p>Here is an <code>R</code> solution, would probably be slower than the suggested <code>Python</code> solution though:</p>

<pre><code>bed &lt;- readLines("bed.txt")

res &lt;- data.frame()

for(i in bed) {

  temp &lt;- strsplit(i, ":") %&gt;% unlist()

  # case N&gt;N

  if(grepl("&gt;", temp[[2]])) {

    begin &lt;- str_extract(temp[[2]], pattern = "^[:digit:]+") %&gt;% as.integer() - 1
    end &lt;- begin + 1
    ref &lt;- str_extract(temp[[2]], pattern = "[A-Z]")
    alt &lt;- str_extract(temp[[2]], pattern = "[A-Z]$")

    temp_res &lt;- data.frame(temp[[1]], begin, end, ref, alt)
    res &lt;- rbind(res, temp_res)
  }

  # case del_ins

  if(grepl("ins", temp[[2]])) {

    begin &lt;- str_extract(temp[[2]], pattern = "^[:digit:]+") %&gt;% as.integer()
    end &lt;- str_extract(temp[[2]], pattern = "_[:digit:]+")
    end &lt;- substring(end, 2, nchar(end)) %&gt;% as.integer()
    ref &lt;- str_extract(temp[[2]], pattern = "[A-Z]+$")
    alt &lt;- str_extract(temp[[2]], pattern = "[A-Z]+")

    temp_res &lt;- data.frame(temp[[1]], begin, end, ref, alt)
    res &lt;- rbind(res, temp_res)
  }


  # case dup

  if(grepl("dup", temp[[2]])) {

    begin &lt;- str_extract(temp[[2]], pattern = "^[:digit:]+") %&gt;% as.integer()
    end &lt;- begin
    ref &lt;- "-"
    alt &lt;- str_extract(temp[[2]], pattern = "[A-Z]$")

    temp_res &lt;- data.frame(temp[[1]], begin, end, ref, alt)
    res &lt;- rbind(res, temp_res)
  }

  # case del only

  if(grepl("del", temp[[2]]) &amp; !grepl("ins", temp[[2]])) {

    begin &lt;- str_extract(temp[[2]], pattern = "^[:digit:]+") %&gt;% as.integer()
    end &lt;- begin
    ref &lt;- str_extract(temp[[2]], pattern = "[A-Z]$")
    alt &lt;- "-"

    temp_res &lt;- data.frame(temp[[1]], begin, end, ref, alt)
    res &lt;- rbind(res, temp_res)
  }

}

print(res)

  temp..1..     begin       end ref alt
1      chr7 140453144 140453145   A   T
2      chr7 140453135 140453136   A   T
3      chr7 140453135 140453136  TT  CA
4     chr20  31022287  31022287   -   A
5     chr19  13054614  13054614   G   -
</code></pre>

CC BY-SA 4.0
2019-09-23T20:33:49.480Z
Thank you all very much :).
justaguy
CC BY-SA 4.0
2019-09-23T14:47:03.910Z
2019-09-23T14:47:03.910Z
haci
<p>The following Python script seems to do the job.</p>

<pre class="lang-py prettyprint-override"><code>#!/usr/bin/env python3

from __future__ import print_function
import re
import sys

for line in sys.stdin:
    if '_' in line:
        match = re.search(r'(\S+):(\d+)_(\d+)del([ACGT]+)ins([ACGT]+)', line)
        print(match.group(1), match.group(2), match.group(3), match.group(5), match.group(4), sep='\t')
    elif 'dup' in line or 'del' in line:
        match = re.search(r'(\S+):(\d+)(del|dup)([ACGT]+)', line)
        if match.group(3) == 'del':
            print(match.group(1), match.group(2), match.group(2), match.group(4), '-', sep='\t')
        else:
            print(match.group(1), match.group(2), match.group(2), '-', match.group(4), sep='\t')
    else:
        match = re.search(r'(\S+):(\d+)([ACGT]+)&gt;([ACGT]+)', line)
        coord = int(match.group(2))
        print(match.group(1), coord - 1, coord, match.group(3), match.group(4), sep='\t')
</code></pre>

<p>Invoked like so on the command line.</p>

<pre><code>[standage@lappy ~] $ ./transform &lt; f1
chr7 140453144 140453145 A T
chr7 140453135 140453136 A T
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -
</code></pre>

CC BY-SA 4.0
2019-09-23T14:43:53.453Z
Thank you very much: `print(match.group(1), match.group(2), match.group(3), match.group(5), match.group(4), sep='\t')`
                                                                                             ^
SyntaxError: invalid syntax
~$ python --version
Python 2.7.9  (not sure if this helps, sorry new to `python`. :)
justaguy
CC BY-SA 4.0
2019-09-23T14:51:15.623Z
It looks like you're running Python 2, which unfortunately is the default version on many operating systems even though it's at its [end-of-life](https://pythonclock.org/). I'll update the script so that it's compatible with Python 2.
Daniel Standage
CC BY-SA 4.0
2019-09-23T14:51:38.580Z
"print" was a statement in Python 2 but is a function in Python 3 hence the syntax error.
haci
CC BY-SA 4.0
2019-09-23T13:44:53.047Z
2019-09-23T14:53:14.390Z
2019-09-23T14:53:14.390Z
Daniel Standage
<p>In <code>f1</code> below I am trying to split <code>$1</code> based on each line and create a bed file. If the line is a snp then the <code>:</code> is spilt and the text is <code>$1</code> the last digit is <code>$2</code> -1 and <code>$3</code> and the letter to the left of the <code>&gt;</code> is <code>$4</code> and the letter to the right is <code>$5</code>. (lines 1 and 2)</p>

<p>If the line has <code>dup</code> then the <code>:</code> is spilt and the text is <code>$1</code> the last digit is <code>$2</code> and <code>$3</code> and the <code>$4</code> is <code>-</code> and the letter to the right is <code>$5</code>. (line 4)</p>

<p>If the line has <code>del</code> with nothing else then the <code>:</code> is spilt and the text is <code>$1</code> the last digit is <code>$2</code> and <code>$3</code> and the letter to the left of the <code>&gt;</code> is <code>$4</code> and <code>$5</code> is <code>-</code>. (line 5)</p>

<p>I am not sure how to format for line 3 (the complex indel)... maybe split <code>$1</code> on the <code>:</code> and the text is <code>$1</code> the last digit is <code>$2</code> and <code>$3</code> and the letters to the left of the <code>&gt;</code> is <code>$4</code> and the letters to the right is <code>$5</code>. Thank you :)</p>

<p><strong>f1</strong></p>

<pre><code>chr7:140453145A&gt;T
chr7:140453136A&gt;T 
chr7:140453135_140453136delCAinsTT
chr20:31022287dupA
chr19:13054614delG
</code></pre>

<p><strong>desired</strong></p>

<pre><code>chr7    140453144   140453145   A   T 
chr7    140453135   140453136   A   T 
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -
</code></pre>

<p><strong>awk</strong></p>

<pre><code>awk 'BEGIN{OFS="\t"} {sub(/[^0-9]+<span class="math-container">$/, "", $</span>1)}' f1

awk 'BEGIN { FS = "[ -]"; OFS="\t" } NF==3 { print <span class="math-container">$1, $</span>2 - 1, <span class="math-container">$2, $</span>3 } NF==4 { print <span class="math-container">$1, $</span>2, <span class="math-container">$3, $</span>4 }' f1
</code></pre>

CC BY-SA 4.0
2019-09-23T12:57:31.730Z
Do you have to do this in awk for some reason? Sure it's possible, but there are enough cases that'd it'd make more sense to use python that's easier to write longer scripts in.
Devon Ryan
CC BY-SA 4.0
2019-09-23T13:21:43.633Z
`python` is fine I just don't know it as well.... only the basics but I am interested to see what it would look like as I can learn from it. Thank you :).
justaguy
<awk>
split 1 column input into 5 column bed file
# split 1 column input into 5 column bed file

(Asked by: username_0 on 2019-09-23)

In f1 below I am trying to split $1 based on each line and create a bed file. If the line is a snp then the : is spilt and the text is $1 the last digit is $2 -1 and $3 and the letter to the left of the > is $4 and the letter to the right is $5. (lines 1 and 2)
If the line has dup then the : is spilt and the text is $1 the last digit is $2 and $3 and the $4 is - and the letter to the right is $5. (line 4)
If the line has del with nothing else then the : is spilt and the text is $1 the last digit is $2 and $3 and the letter to the left of the > is $4 and $5 is -. (line 5)
I am not sure how to format for line 3 (the complex indel)... maybe split $1 on the : and the text is $1 the last digit is $2 and $3 and the letters to the left of the > is $4 and the letters to the right is $5. Thank you :)
f1
chr7:140453145A>T
chr7:140453136A>T 
chr7:140453135_140453136delCAinsTT
chr20:31022287dupA
chr19:13054614delG

desired
chr7    140453144   140453145   A   T 
chr7    140453135   140453136   A   T 
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -

awk
awk 'BEGIN{OFS="\t"} {sub(/[^0-9]+$/, "", $1)}' f1

awk 'BEGIN { FS = "[ -]"; OFS="\t" } NF==3 { print $1, $2 - 1, $2, $3 } NF==4 { print $1, $2, $3, $4 }' f1

**Tags:** awk
> **Comments:**
> **username_1**: Do you have to do this in awk for some reason? Sure it's possible, but there are enough cases that'd it'd make more sense to use python that's easier to write longer scripts in.  
> **username_0**: `python` is fine I just don't know it as well.... only the basics but I am interested to see what it would look like as I can learn from it. Thank you :).  

---

## 4 answers

### Answer by username_2 on 2019-09-23. Score: 4 | Accepted answer

The following Python script seems to do the job.
#!/usr/bin/env python3

from __future__ import print_function
import re
import sys

for line in sys.stdin:
    if '_' in line:
        match = re.search(r'(\S+):(\d+)_(\d+)del([ACGT]+)ins([ACGT]+)', line)
        print(match.group(1), match.group(2), match.group(3), match.group(5), match.group(4), sep='\t')
    elif 'dup' in line or 'del' in line:
        match = re.search(r'(\S+):(\d+)(del|dup)([ACGT]+)', line)
        if match.group(3) == 'del':
            print(match.group(1), match.group(2), match.group(2), match.group(4), '-', sep='\t')
        else:
            print(match.group(1), match.group(2), match.group(2), '-', match.group(4), sep='\t')
    else:
        match = re.search(r'(\S+):(\d+)([ACGT]+)>([ACGT]+)', line)
        coord = int(match.group(2))
        print(match.group(1), coord - 1, coord, match.group(3), match.group(4), sep='\t')

Invoked like so on the command line.
[standage@lappy ~] $ ./transform < f1
chr7 140453144 140453145 A T
chr7 140453135 140453136 A T
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -

> **Comments:**
> **username_0**: Thank you very much: `print(match.group(1), match.group(2), match.group(3), match.group(5), match.group(4), sep='\t')`                                                                                              ^ SyntaxError: invalid syntax ~$ python --version Python 2.7.9  (not sure if this helps, sorry new to `python`. :)  
> **username_2**: It looks like you're running Python 2, which unfortunately is the default version on many operating systems even though it's at its [end-of-life](https://pythonclock.org/). I'll update the script so that it's compatible with Python 2.  
> **username_4**: "print" was a statement in Python 2 but is a function in Python 3 hence the syntax error.  

---

### Answer by username_2 on 2019-09-23. Score: 3

The Python script in my first response is heavy on regex matching, which is pretty clunky in Python. I like Python much better than Perl overall, but a throwaway script like this will be clearer and more concise in Perl.
#!/usr/bin/env perl
use strict;

while() {
    if (m/(\S+):(\d+)_(\d+)del([ACGT]+)ins([ACGT]+)/) {
        print("$1\t$2\t$3\t$5\t$4\n");
    }
    elsif (m/(\S+):(\d+)(del|dup)([ACGT]+)/) {
        if ($3 eq "del") {
            print("$1\t$2\t$2\t$4\t-\n");
        }
        else {
            print("$1\t$2\t$2\t-\t$4\n");
        }
    }
    elsif (m/(\S+):(\d+)([ACGT]+)>([ACGT]+)/) {
        my $coord2 = $2;
        my $coord1 = $2 - 1;
        print("$1\t$coord1\t$coord2\t$3\t$4\n")
    }
}

It's been several years since I wrote Perl on a regular basis, so there are probably ways to make the script even more clear and concise. (Although I have a love/hate relationship with the default/hidden variables, which can make a script more concise but also harder for a newcomer to understand.)
Invoked like so on the command line.
[standage@lappy ~] $ ./transform < f1
chr7 140453144 140453145 A T
chr7 140453135 140453136 A T
chr7    140453135   140453136   TT  CA
chr20   31022287    31022287    -   A
chr19   13054614    13054614    G   -


---

### Answer by username_3 on 2019-09-23. Score: 3

Quick Perl solution (following on from Daniel's answer saying that this would be "clearer and more concise in Perl"). I was a bit confused with your BED output because you've treated the start/end locations of a single base deletion and insertion different to a SNP. In my example here, the start and end locations for those cases are the same to distinguish from the two-base change for TT->CA [this can be changed]:
cat 10454.txt | \
perl -ne 'if(/^(.*?):([0-9]+)(_([0-9]+))?(del|dup)?([ACGT]+)((ins|>)([ACGT]+))?/){
   print(join("\t",
         $1,
         $2,
         $4?$4:($2),
         ($8 eq "ins")?$9:($5 eq "dup")?"-":$6,
         (($5 eq "dup") || ($8 eq "ins"))?$6:($9)?$9:"-")."\n")
}';
chr7    140453145       140453145       A       T
chr7    140453136       140453136       A       T
chr7    140453135       140453136       TT      CA
chr20   31022287        31022287        -       A
chr19   13054614        13054614        G       -

Regular Expression
This uses enclosing groups to describe the important bits of the sequence. The groups are numbered from 1 upwards, with each number corresponding to a left bracket (in order), capturing the entirety of the sub-expression until the paired right bracket. I'll describe the groups, which are referenced in the subsequent part of the script as $:

Any number of characters, up to (but not including) the first ':'
Any number of the characters '0' to '9' [i.e. numbers]
Optionally, a "_", followed by any number of the characters '0' to '9'
The numbers from Group [3], i.e. excluding the "_"
Optionally, a "dup" or a "del"
Any number of characters selected from "A", "C", "G", "T" (i.e. bases)
Optionally, an "ins" or ">", followed by any number of bases
The "ins" or ">" from Group [7]
The bases from Group [7]

Print Code
The print statement prints out a tab-separated list, finishing off with a line break. I'll describe each list element:

Group [1]
Group [2]
If Group [4] exists, then Group [4], otherwise Group [2]
If Group [8] is "ins", then Group [9]; [otherwise]
If Group [5] is "dup", then "-", otherwise Group [6]
If Group [5] is "dup", or group [8] is "ins", then Group [6][otherwise]
If Group [9] exists, then Group [9], otherwise "-"

> **Comments:**
> **username_2**: +1, although I'm tempted to quote Jurassic Park here: "You were so preoccupied with whether or not you could, you didn't stop to think if you should."   

---

### Answer by username_4 on 2019-09-23. Score: 2

Here is an R solution, would probably be slower than the suggested Python solution though:
bed <- readLines("bed.txt")

res <- data.frame()

for(i in bed) {

  temp <- strsplit(i, ":") %>% unlist()

  # case N>N

  if(grepl(">", temp[[2]])) {

    begin <- str_extract(temp[[2]], pattern = "^[:digit:]+") %>% as.integer() - 1
    end <- begin + 1
    ref <- str_extract(temp[[2]], pattern = "[A-Z]")
    alt <- str_extract(temp[[2]], pattern = "[A-Z]$")

    temp_res <- data.frame(temp[[1]], begin, end, ref, alt)
    res <- rbind(res, temp_res)
  }

  # case del_ins

  if(grepl("ins", temp[[2]])) {

    begin <- str_extract(temp[[2]], pattern = "^[:digit:]+") %>% as.integer()
    end <- str_extract(temp[[2]], pattern = "_[:digit:]+")
    end <- substring(end, 2, nchar(end)) %>% as.integer()
    ref <- str_extract(temp[[2]], pattern = "[A-Z]+$")
    alt <- str_extract(temp[[2]], pattern = "[A-Z]+")

    temp_res <- data.frame(temp[[1]], begin, end, ref, alt)
    res <- rbind(res, temp_res)
  }


  # case dup

  if(grepl("dup", temp[[2]])) {

    begin <- str_extract(temp[[2]], pattern = "^[:digit:]+") %>% as.integer()
    end <- begin
    ref <- "-"
    alt <- str_extract(temp[[2]], pattern = "[A-Z]$")

    temp_res <- data.frame(temp[[1]], begin, end, ref, alt)
    res <- rbind(res, temp_res)
  }

  # case del only

  if(grepl("del", temp[[2]]) & !grepl("ins", temp[[2]])) {

    begin <- str_extract(temp[[2]], pattern = "^[:digit:]+") %>% as.integer()
    end <- begin
    ref <- str_extract(temp[[2]], pattern = "[A-Z]$")
    alt <- "-"

    temp_res <- data.frame(temp[[1]], begin, end, ref, alt)
    res <- rbind(res, temp_res)
  }

}

print(res)

  temp..1..     begin       end ref alt
1      chr7 140453144 140453145   A   T
2      chr7 140453135 140453136   A   T
3      chr7 140453135 140453136  TT  CA
4     chr20  31022287  31022287   -   A
5     chr19  13054614  13054614   G   -

> **Comments:**
> **username_0**: Thank you all very much :).  

---

