复制一列的第一个字符,用它替换现有的一列,同时根据需要保持间距

3 人关注

我有一个长文件,看起来像这样。

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

我需要取第三列的第一个字母,在这里是C(但它会随行变化),并用这个字符替换我的最后一列LIGA。我需要在确保第12列和第13列之间的间距为11的情况下做这件事,如下图所示。我需要它与下面这行相同,以便我的程序能够理解它。

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

我设法将第三栏的第一个字母复制到一个不同的文件中,然后从原文件中删除第13栏,并将不同的文件粘贴到原文件中,下面是几行。然而,我可以找到一种方法来解决间距问题。

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

我知道awk在这里可能有用。我还没能实现它。我很感谢你的帮助!

2 个评论
我认为你显示的输出应该是 ATOM 55 CE1 LIG X 1 -2.921 4.159 -10.046 1.00 0.00 C ?这是否正确。
关于【替换代码0- 你下面这一行的一些字段的值与输入的不同(例如,第7个字段从-2.921变成了-4.950),最后一个字段的间距也不同(看起来 LIGA 前有6个空格,但在 C 前变成了11个空格),但你说你想保持间距。所以--请 edit 你的问题是为了更好地解释你发布的输入如何映射到你发布的输出,因为不清楚从输入到输出的哪些变化是故意的,哪些是错误的(如果有的话)。
linux
unix
awk
text
sed
user19619903
user19619903
发布于 2022-08-25
7 个回答
RavinderSingh13
RavinderSingh13
发布于 2022-08-26
0 人赞同

1st solution: 请用你显示的样本和尝试尝试以下 awk 代码。用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:在这里使用sed及其-E选项来启用ERE。

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

Here is the Online demo为了便于理解,请使用所显示的词条((^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+))(注意:网站中使用的词条有点不同(为了满足网站的要求),请使用这里的代码中显示的词条)。

Bonus solution:在此添加一个perl的单行本解决方案。

perl -pe 's/(^[^[:space:]]+[[:space:]]+[^[:space:]]+[[:space:]]+)(.)([^[:space:]]*.*[[:space:]]+)[^[:space:]]+$/\1\2\3\2/' Input_file
    
谢谢!这两个解决方案似乎对大多数行都有效,但不是全部。他们确实删除了最后一列,而对于大多数来说,第一个字符取代了它。我注意到,当我的第三列只有一个字母时,例如C,或N,或O,最后一列是空白。我将发表另一篇评论,为那些不工作的地方提供一个例子。因为这是一个评论,所以间距会不正确。我仍然需要如图所示的间距。谢谢!!!。
atom 1488 o lig x 52 5.636 -11.834 10.151 1.00 0.00 liga
@user19619903,好的,我现在已经编辑了我的答案,现在对第三栏的单字母也应该可以了,让我知道情况如何。
谢谢你的帮助。前两个方案在增加由第三列第一个字符组成的新列时起了作用,但它没有删除第11列(LIGA)。我担心如果我删除它,又会把间距搞乱。
@user19619903,请你给我举个例子,我可以在这里提供更多帮助🙏。
HatLess
HatLess
发布于 2022-08-26
0 人赞同

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

使用GNU sed来保留循环中的间距

$ 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
    
stevesliva
stevesliva
发布于 2022-08-26
0 人赞同
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
  • 这应该能让你接近。 我不能完全按照列数和空间数来计算。

    但它只是指望第12个col是一个独特的字符串,可以用 $lc来替换。

    这也取决于第12列 "LIGA "始终是4个字符。如果该字段是可变宽度的,你可以用空格替换其中的所有字符,然后再替换最后的字符。

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

    ......同样,$F[11]必须是一个唯一的字符串,否则任何其他较早出现的字符都会被替换。 但根据这一点,意味着你要保持原版的字符间距。

    谢谢你!"。这与我的需求非常接近。LIGA "栏确实总是4个字符。你的回答对我帮助很大。当我使用你的解决方案时,第11列(0.00列)和新创建的第3列中的第一个字符之间的间距是9。这应该是11。这是否可以从解决方案中改变?我还是个使用awk的新手!谢谢
    我能够从你的解释中解决间距问题!非常感谢您!这非常有帮助!
    anubhava
    anubhava
    发布于 2022-08-26
    0 人赞同

    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
        
    谢谢!但间距不对。我需要第11和第12列之间的间距应该是11,而不是6。这个问题可以从这行代码中解决吗?谢谢!
    你是说你总是想在第11和第12列之间有 11 的空格,即使在原始输入中没有。
    你可以试试我现在更新的答案,确保在最后一列前总是插入 11 的空格。
    The fourth bird
    The fourth bird
    发布于 2022-08-26
    0 人赞同

    通过 gnu awk ,你可以在字段分隔符上进行分割,并对分隔符进行跟踪。

    然后将最后一列设置为第3列的第一个字符,并将最后一项的分隔符设置为11个空格。

    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"
    }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
    

    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
        
    Ed Morton
    Ed Morton
    发布于 2022-08-26
    0 人赞同

    使用任何POSIX awk,无论第三个和/或最后一个字段中的字符是什么。

    a) 保持你在主题栏中所说的间距。

    $ 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) 或者像你的例子中显示的那样,将最后两个字段之间的间距改为11个字符。

    $ 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
        
    RARE Kpop Manifesto
    RARE Kpop Manifesto
    发布于 2022-08-26
    0 人赞同

    这里有一个更通用的 awk 解决方案,不需要 gawk 的自定义功能。

    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
    
    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