Copy the first character of a column and replace an existing column with it, while maintaining the spacing as needed

Viewed 128

I have a long file that looks like this:

ATOM 55 CE1 LIG X 1 -2.921 4.159 -10.046 1.00 0.00 LIGA

I need to take the first letter of the third column, in this case C (but it changes by line), and replace my last column, LIGA, with this character. I need to do this while ensuring the spacing between my 12th and 13th columns is 11, as shown below. I need it to be identical to the line below for my program to understand it.

ATOM 55 CE1 LIG X 1 -4.950 9.318 4.387 1.00 0.00 C

I managed to copy the first letter of the third column into a different file, then delete the 13th column from the original file, and paste the different file into the original file with the lines below. However, I can find a way to fix the spacing.

cut -c 14 original.pdb > different.pdb
perl -pi -e 's/LIGA//g' original.pdb
paste original.pdb different.pdb >> joint.pdb
mv joint.pdb original.pdb

I know awk may work here. I haven't been able to achieve it. I appreciate the help!

7 Answers

1st solution: With your shown samples and attempts please try following awk code. Written and tested in GNU awk.

awk '
match($0,/(^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+)/,arr){
  print arr[1] arr[2] arr[3] arr[2]
}
' Input_file

2nd solution: Using sed with its -E option to enable ERE here.

sed -E 's/(^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+).*/\1\2\3\2/'  Input_file

Here is the Online demo for shown regex((^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+)) for understanding purposes(NOTE: regex used in site is bit different(to satisfy site's requirement) use regex shown in code here only).

Bonus solution: Adding a perl one-liner solution here:

perl -pe 's/(^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+)[^[:space:]]+$/\1\2\3\2/' Input_file

Using sed

$ sed -E 's/(([^ ]* ){2})(([[:alpha:]]).* ).*/\1\3\4/' input_file
ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00      C

Using GNU sed to retain spacing within a loop

$ sed -E ':a;s/(([^ ]* +){11})[A-Z]/\1 /;ta;s/(([^ ]*( )+){2}([[:alpha:]]).*)/\1\3\4/' input_file
ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00           C
perl -ape '$lc = substr $F[2],0,1; s/$F[11]/   $lc/' original.pdb 
  • Use -a to autosplit into @F
  • Use -p to loop, -e to execute inline program
  • $lc = substr $F[2],0,1 - get first char of 3rd col as variable $lc
  • s/$F[11]/ $lc/ - replace 12th column with 3 spaces then $lc

This should get you close. I can't exactly follow the column counts and space counts.

But it's just counting on the 12th col being a unique string, that can be replaced with $lc

This also depends on 12th column 'LIGA' always being 4 chars. If that field is variable width, you could always replace all the chars in it with a space, and then replace the last char:

perl -ape '$lc = substr $F[2],0,1; ($new = $F[11]) =~ s/./ /g; $new =~ s/.$/$lc/; s/$F[11]/$new/' original.pdb

... again, $F[11] must be a unique string, or any other earlier occurrence will be replaced. But depending on that means you keep the char spacing of the original.

It is simpler using this gnu-awk solution with gensub:

awk '{
$0 = gensub(/^(\s*\S+(\s+\S+)+)\s+\S+\s*$/, "\\1" sprintf("%12s", substr($3,1,1)), "1")
} 1' file

ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00           C

With gnu awk you can split on the field separator and keep track of the separators.

Then set the last column to the first character of column 3, and set the separator for the last item to 11 spaces.

awk '{
  nr = split($0, a, FS, seps)
  if (nr > 4) {
    a[nr] = substr(a[3],1,1)
    seps[nr-1] = "           "
    for (i = 1; i <= nr; ++i) {
      printf "%s%s", a[i], seps[i]
    }
    printf "\n"
    next
  }
}1' file

If the contents of the file is:

ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00      LIGA
ATOM     55  CE1 LIG X
ATOM     55  CE1 LIG
ATOM     55  CE1
ATOM     55
ATOM

Output:

ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00           C
ATOM     55  CE1 LIG           C
ATOM     55  CE1 LIG
ATOM     55  CE1
ATOM     55
ATOM

Using any POSIX awk no matter which char(s) are in the 3rd and/or last fields:

a) maintaining the spacing as you said you wanted in the subject line:

$ awk '{sub(/[^[:space:]]+$/,""); print $0 substr($3,1,1)}' file
ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00      C

b) or changing the spacing between the last 2 fields to 11 chars as you show in your example:

$ awk '{sub(/[[:space:]]+[^[:space:]]+$/,""); printf "%s%12s\n", $0, substr($3,1,1)}' file
ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00           C

here's a more generic awk solution without needing gawk's custom features :

ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00      LIGA
ATOM     55  CE1 LIG X
ATOM     55  CE1 LIG
ATOM     55  CE1
ATOM     55
ATOM
 
mawk 'BEGIN { _____=_=(_=(_=(_=" ")_)(_)_)_

         FS = "^"(_="[[:space:]]+")"|"(_)"$" (OFS="")
        __ = index(_,"s")
                          sub("[[]:.+$",    "^&"_, _)
     ___ = substr((_)_, index(____="^"((_)_)".", "+"),
                       length(_) )  "$"
     __ = _ (_ = "")
     _+= _+=_ = sub(".",_,____) 

 } { NF=NF } gsub(__,"&")<_ || match($!_, ____) + \
              sub(___,(_____) substr($!_, RLENGTH,-_<_))'
ATOM     55  CE1 LIG X   1      -2.921   4.159 -10.046  1.00  0.00            C
ATOM     55  CE1 LIG            C
ATOM     55  CE1 LIG
ATOM     55  CE1
ATOM     55
ATOM
Related