Simple awk script for positional replacement in text?

I have a string of letters. (They happen to be DNA, not that it's relevant to the question.) For analysis purposes, I need to replace the information at some of the sites. I need to do this based on their position, not the information in that position.

I also need to ignore differences at other sites, so I can't do a simple "wild card search and replace." I remember using awk scripts to do something like this about a decade ago, but I don't remember how.

For example, if you wanted to take the following strings,

AAAAATAAAGAAAA
and
AAAATTAAAGAAAA
and
AAAAATAAACAAAA

and in each case, turn the 10th letter into an "N," without changing any of the other letters, how would you do that? (The actual files are about 500 characters long, really only one "field" and will have about 30 position-specific replacements. I'll probably deal with each line separately, come to think of it.)

I'm also willing to use a web tool or text editor....MacOS or Unix terminal on a Mac. (Last time I had to do something like this was 1998.)

I know the forum rules say to use standard notation for programs, but if I could read standard notation with no further explanation, I probably would not need to be posting this question. So text-y replies would be most welcome. Thanks!

To keep the forums high quality for all users, please take the time to format your posts correctly.

First of all, use Code Tags when you post any code or data samples so others can easily read your code. You can easily do this by highlighting your code and then clicking on the # in the editing menu. (You can also type code tags

```text
 and 
```

by hand.)

Second, avoid adding color or different fonts and font size to your posts. Selective use of color to highlight a single word or phrase can be useful at times, but using color, in general, makes the forums harder to read, especially bright colors like red.

Third, be careful when you cut-and-paste, edit any odd characters and make sure all links are working property.

Thank You.

The UNIX and Linux Forums

**************************************************************

If I got it right - changing the 10th character from G to N for example:

echo "AAAAATAAAGAAAA"| awk '{substr((sub(/G/,"N")),10,1); print}'

That 10th position that's being replaced need not be a G. Strictly speaking, it could be anything. That's why I show it as being either a G or a C in the three examples I put up. Unfortunately, G's and C's look similar. Maybe I should have made that a "C or T" or a "G or A" (those are the actual options for what I'm doing).

Also, the data at OTHER points in each string is ALSO not identical.

If I remember correctly, I can put two options in brackets at that location, and otherwise follow what you're suggesting. Or whatever the symbol for a single character wild card is, which I should be able to look up. I remember that it's not just an asterisk (*).

Would this work?

echo "AAAA[AT]TAAA[ACGT]AAAA"| awk '{substr((sub(/[ACGT]/,"N")),10,1); print}'

Thanks!

NB, I also don't understand the comment about code tags, because so far as I know, I have not included any code, and my example is not "data," it's an explanation of my terminology, in case I'm using my terms incorrectly. Perhaps "data" means something different to a computer scientist than it does to a biologist. Apologies, if so.

echo "AAAAATAAAGAAAA"| awk '{sub((substr($0,10,1)),"N"); print}'

This up there should work whatever the 10th char is.

CODE tags are not only useful for code. They are also very useful to display anything but normal writings. It enhances readability a lot. If there is a lot text above and a lot text below describing things and inbetween there is a snippet of an example of data etc. then it will be much easier to be distinguished from the other text. This is a recommendation for all users on the board no matter if it is code, data or logs. I am and I bet other Mods too are very tired (not because of you :slight_smile: ) to always write a lengthy comment when I edit posts adding CODE tags etc. so Neo provided for us Mods this canned reply up there.

Thanks! That looks like the truly position-only answer I was looking for. Much nicer than having to specify a boatload of sequence alternatives.

I guess you answered while I was still adding text - read it! :slight_smile: :wink:

I got the single replacement to work. But when I try to chain up multiple commands, it's hit or miss.

Here's my input file, creatively named testfile1

2011AAAATAACAAAAAT
2012AAATTAACAAAAAT
2013AAATTAAGAAAAAT

here's the script

awk '{sub((substr($0,5,1)),"X");sub((substr($0,7,1)),"x");sub((substr($0,8,1)),"Y");sub((substr($0,10,1)),"z");print}' testfile1 > testfile6

and here's the output I get

2011XxYzTAACAAAAAT
2012XxzYTAACAAAAAT
2013XxzYTAAGAAAAAT

as opposed to what I hoped to get (and thought I asked for), which is

2011XAxyTzACAAAAAT
2012XAxyTzACAAAAAT
2013XAxyTzAGAAAAAT

is this a "first instance of a character" issue? I'm using the "terminal" function on a Mac, which I somehow believe is a BASH shell.

I switched to sed back references or "grouping" since I think this is easier than in awk:

$> cat infile
2011AAAATAACAAAAAT
2012AAATTAACAAAAAT
2013AAATTAAGAAAAAT
$> sed 's/\(....\).\(.\)..\(.\).\(.*\)/\1X\2xy\3z\4/' infile
2011XAxyTzACAAAAAT
2012XAxyTzACAAAAAT
2013XAxyTzAGAAAAAT
$> cat want
2011XAxyTzACAAAAAT
2012XAxyTzACAAAAAT
2013XAxyTzAGAAAAAT

I group those parts I want to keep unchanged inside escaped brackets \( and \). I wrote every character as dot. I could have written four characters as \{4\} for example but it's not worth compared to type 4 dots.
I made 4 groups that stay unchanged and so are just printed. Between them I inserted the characters you wanted. This is still position related but you have to type a number of dots or sum them up with \{6\} for 6 dots for example.

Thanks! I was getting the sense I might need to do a sort of patchwork using SED or PERL (neither of which I have ever used, but between your suggestion and a cheat sheet, it ought to be doable).

However, each of my "lines" is actually a DNA sequence several hundred characters long. (As opposed to the short version used in the example--I should have mentioned that up-front, I suppose.)

Will it work to stick 40 of those long lines in the command? Or can I use the command to do the process to "each line in the file"? Or will having ~550 character lines be a problem, regardless?

I have a sort of offer from the spouse to write something in FORTRAN sooner or later if SED, AWK etc can't handle the task, but I'm hoping I can make this work myself, and from what I can remember, this should be possible.

awk and sed will work every line of your given input file even if it's several thousands or more; you can just try it out.
You can write a very long line that has all your needed changes in it for every position. It is just more work for you but awk or sed would not mind I think. Have to try it out.
If you want to get deeper there is a really good book about awk and sed if your interessted on using them:
sed & awk | O'Reilly Media

I'm back at this after some bench work, and it's not working for me yet.

here's a file named infile

>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTATTTGGTTTAYGAGTCAGAGGTGTTTTTTCACGGAGAGATGGCTCTAAGA
>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTATTTGGTTTAYGAGYTAGAGGYGTCTTTTTACGGAGAGACGGTTTTAAGA 

I also tried a version without the initial ">" symbol, in case that had an inappropriate meaning.

here are three main versions of the sed script I'm trying to run, along with variants. I'm using the g command rather than the individual replacements because the replacement value will always be the same. Two of the versions are an attempt to figure out what sort of brackets to use when using numbers rather than dots. The final, clunky version uses the dots.

version 1

$> sed 's/\{36\}.\{12\}.\{9\}.\(.\).\(..\).\{12\}.\(.\).\(.*\)/x/g' infile1

version 1a

$> sed -e 's/\{36\}.\{12\}.\{9\}.\(.\).\(..\).\{12\}.\(.\).\(.*\)/x/g' infile1

version 1b

$> sed 's/\{36\}.\{12\}.\{9\}.\(.\).\(..\).\{12\}.\(.\).\(.*\)/x/g' infile >zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTATTTGGTTTAYGAGTCAGAGGTGTTTTTTCACGGAGAGATGGCTCTAAGA
>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTATTTGGTTTAYGAGYTAGAGGYGTCTTTTTACGGAGAGACGGTTTTAAGA 

version 2

$> sed 's/\(36\).\(12\).\(9\).\(.\).\(..\).\(12\).\(.\).\(.*\)/x/g' infile1

(same variants as with version 1)

version 3

$> sed 's/\(....................................\).\(............\).\(.........\).\(.\).\(..\).\(............\).\(.\).(.*\)/x/g' 

(same variants as with version 1)

the resulting error is sometimes along the lines of command not found, other times the error says unbalanced parentheses.

desired output would be

>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTAxTTGGTTTAYGAGxCAGAGGTGTxTxTTxACGGAGAGATGGxTxTAAGA
>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTATTTGGTTTAYGAGYTAGAGGYGTxTxTTxACGGAGAGACGGxTxTAAGA 

to give you a sense of the magnitude of the real program, here's the proposed script for the entire piece of DNA (which would be applied to about 60 lines of that length, rather than the two lines in the infile example.

$> sed 's/\(36\).\(12\).\(9\).\(.\).\(..\).\(12\).\(.\).\(13\).\(6\).....\(5\).\(.\)..\(7\).....\(.\).\(6\).\(..\).\(6\).\(...\)...\(25\).\(18\).\(..\)..\(8\).\(...\).\(...\).\(...\).\(5\).\(6\).\(9\).\(10\)..\(21\).\(6\)..\(8\).\(10\).\(4\).\(10\).\(24\).\(26\).\(.*\)/x/g' infile

I'm using a MacOS X default (Bash shell) terminal.

Is there anything I am doing right?
Is there something dead obvious that I'm doing wrong?
I need to write, oh, 18 scripts of this length, and get them working.

---------- Post updated at 03:16 PM ---------- Previous update was at 03:14 PM ----------

I missed two replacements in the sample output file

>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTAxTTGGTTTAYGAGxCAGAGGTGTxTxTTxACGGAGAGATGGxTxTAAGA
>zzzzzzzzzzzzzGGGAAGTGAGGCGYTGTTGTTAxTTGGTTTAYGAGxTAGAGGYGTxTxTTxACGGAGAGACGGxTxTAAGA