【问题标题】:sed: replacing nth word with matched pattern?sed:用匹配的模式替换第 n 个单词?
【发布时间】:2012-03-07 10:24:49
【问题描述】:

我有一个具有以下特征的文本文件:

  1. 每行至少有三个“单词”,用空格隔开
  2. “单词”可以是任何字符或字符串

我已经在一些行中附加了一些注释,其中包含对原始单词进行更改的初步建议,现在想使用 sed 为我进行这些更改。因此,为了提供更清晰的图片,我的文件如下所示:

NO NO O
SIGNS NN O      #NNS
GIVEN VBD B-VP  #VBN
AT IN O
THIS NN O       
TIME NN O            ## B-NP
. PER O
...

带有 1 # 的注释用于替换一行中的第二个单词,带有 2 个 # 的注释用于替换一行中的第三个单词。任何人都可以建议一种使用 sed (或 awk 或其他任何东西)的方法吗?再次澄清(希望如此),我的目标是获取 # 或 ## 之后的模式,并将该行的第 n 个单词替换为匹配的模式。

谢谢。

【问题讨论】:

  • 是否可以规定您的笔记在# 之后有预定数量的空格?现在,您显示的注释在 # 之后有 0 和 1 个空格,这会使代码不必要地难以适应。如果你的笔记后面有零个空格会更容易
  • 另外,您希望注释出现在输出中吗?
  • @SiegeX:是的,我应该可以很容易地删除“## B-NP”中的多余空格,或者根据需要在“#NNS”中添加一个空格。
  • @SiegeX:不,我不想在输出中添加注释——我确实想在几个制表符之后保留一个像“#”这样的标记,以指定我已更改的行。

标签: regex perl bash sed awk


【解决方案1】:

Perl 可以处理这个问题。虽然我想我更愿意把它做成一个脚本。

粘贴版本:

perl -lnwe 's/#\K\s+//; my @a=/\S+/g; if (@a>3) { $c = $a[3] =~ tr/#//d; $a[$c] = $a[3]; } print join " ", @a[0..2]' file

此版本将打印到标准输出而不更改文件。添加-i.bak,例如perl -i.bak -lnwe '....' 进行就地编辑,并在file.bak 中进行备份。

可读版本:

$ perl -lnwe '       # -l: handle newlines, -n read file/stdin
    s/#\K\s+//;                    # strip optional spaces
    my @a = /\S+/g;                # extract the data
    if (@a > 3) {                  # when there are replacements..
        my $c = $a[3] =~ tr/#//d;  # count and remove #
        $a[$c] = $a[3];            # set element number $c to element 3
    } print join " ", @a[0..2]     # reassemble and print 3 first elements
' file

输出:

NO NO O
SIGNS NNS O
GIVEN VBN B-VP
AT IN O
THIS NN O
TIME NN B-NP
. PER O

【讨论】:

    【解决方案2】:

    这可能对你有用:

    sed 's/\S*\(\s*\S*\s*#\s*\)\([^#]*\)$/\2\1/;s/ *##*.*/\t\t#/' file
    NO NO O
    SIGNS NNS O             #
    GIVEN VBN B-VP          #
    AT IN O
    THIS NN O       
    TIME NN B-NP            #
    . PER O
    ...
    

    【讨论】:

    • 是的,这也有效。我在 sed 中尝试这样做的时间最长,而我想出的命令是噩梦般的。解释器一直告诉我我的 /1 和 /2 标识符无效。
    • 这里的诀窍是将正则表达式锚定到字符串的末尾($)并使用额外的# 将后面的引用拉近一个字段(参见\(\s*\S*\s*#\s*\))。
    【解决方案3】:

    这对你有用:

    awk '/#/{sub(/# +/,"#");n=gsub(/#/,"",$NF);$(n+1)=$NF;$NF="\t\t#"}1' file
    

    说明

    1. /#/{ ... }:搜索包含#的行并执行以下步骤...
    2. sub(/# +/,"#"):如有必要,删除注释和# 之间的所有空格
    3. n=gsub(/#/,"",$NF):从最后一个字段$NF中删除所有#,并将删除的#的数量设置为变量n
    4. $(n+1)=$NF:将 n+1 字段 $(n+1) 设置为新的最后一个字段 $NF,该字段已剥离所有 #
    5. $NF="\t\t#":将最后一个字段 $NF 设置为两个选项卡,后跟 #
    6. 1:告诉awk 打印更改的行的快捷方式
    7. file: 你的输入文件

    示例

    $ awk '/#/{sub(/# +/,"#");n=gsub(/#/,"",$NF);$(n+1)=$NF;$NF="\t\t#"}1' file
    NO NO O
    SIGNS NNS O             #
    GIVEN VBN B-VP          #
    AT IN O
    THIS NN O
    TIME NN B-NP            #
    . PER O
    ...
    

    注意:如果您这样做是为了让您的笔记始终跟在 # 之后,并且中间有零个空格,您可以删除命令的整个 sub(/# +/,"#"); 部分让它更短

    【讨论】:

    • 谢谢。我不熟悉 awk,所以我必须做一些学习才能了解这里发生了什么。不过,我真的无法将您的笔记翻译成不同的命令。会不会是: awk '/#/{;t=$NF;n=gsub(/#/,"",t);$(n+1)=t}1' notes ??这看起来有点奇怪......
    • 应该是/#/{n=gsub(/#/,"",$NF);$(n+1)=$NF;$NF="\t\t#"}1' file
    • 我刚刚在我的文件上运行了你的命令,它似乎运行良好。真是太感谢你了。
    • @wayeast 没问题,你可以先接受答案=)
    猜你喜欢
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    相关资源
    最近更新 更多