这是一个sed
特定的问题;我很清楚这可以使用其他工具来完成,但我正在努力扩展我的知识sed
。
如何使用sed
全局引用(实际上是反引号)脚本中未指定的单词?该词被保存在保留空间中。
我想要的是这样的:
s/word/`&`/g
但诀窍是,word
将不包含在 sed 脚本中,而是包含在保留空间中。所以它看起来更像是:
H
g
s/^\(.*\)\n\(.*\)\1\(.*\)$/\2`\1`\3/
这将引用一保留空间中保留的单词的出现。我想引用全部其中,但我不能只添加一个g
标志,因为它使用反向引用而不是静态正则表达式。
H
g
s/^\(.*\)\n\(.*\)\1\(.*\)\1\(.*\)$/\2`\1`\3`\1`\4/
这会处理该单词的两次出现,但一次会失败,并且会忽略多次出现。
我想我可以使用一些干净简单的东西,例如:
s//`&`/g
但这会重用上次使用的正则表达式,不是它匹配的内容。 (这是有道理的。)
有什么办法sed
可以做我想做的事情吗? (其实我会有兴趣看看这在 中有多容易perl
,但我仍然想看看如何在 中做到这一点sed
。)
更新
并不是说它是需要的对于这个问题,但我想我应该提供更多背景信息来说明当我提出这个问题时我到底在做什么:
我有一个很大的文档文本文件,其中的某些部分需要压缩并汇总到一个asciidoc
表格中。由于Description:
和Prototype:
行等原因,这非常容易,所以我实际上编写了一个快速sed
脚本来为我完成所有解析。它工作得很好,但它缺少的一件事是我想对行中与Description
该行中列出的参数相匹配的单词进行反引号Prototype
。原型线看起来像这样:
Prototype: some_words_here(and, arg, list,here)
我输出的表中有超过 200 个不同的条目(源文档包含的文本比这多得多),每个 arglist 仅需要用于反引号引用匹配的单词单身的线。更棘手的是,有些参数不在描述行中,有些参数多次出现,有些参数列表为空()。
然而,考虑到有时 arg 会匹配部分一个单词的名称,我不想反引号,有时 arg 名称是一个常见的单词(例如from
),我只想在解释该函数(一个自动的)的使用的上下文中使用它时反引号。解决方案实际上根本不适合,我改为vim
在一些棘手的宏的帮助下半手动地完成这项工作。 :)
答案1
那是一件很难的事情。假设你有file
这样的:
$ cat file
word
line with a word and words and wording wordy words.
在哪里:
- 第 1 行:是应保存在保留空间中并引用到 的搜索模式
`word`
。 - 第2行:是全局搜索和替换的行。
命令sed
:
sed -n '1h; 2{x;G;:l;s/^\([^\n]\+\)\n\(.*[^`]\)\1\([^`]\)/\1\n\2`\1`\3/;tl;p}' file
解释:
1h;
将第一行保存到保留空间(这是我们要搜索的等待)。- 容纳空间包含:
word
- 容纳空间包含:
2{...}
适用于第二行。x;
交换模式空间和保持空间。G;
将保留空间附加到模式空间。在模式空间中我们现在有:
word # I will call this line the "pattern line" from now on
line with a word and words and wording wordy words.
:l;
设置一个名为 as point 的标签l
以供稍后使用。s///
在上面提到的模式空间中进行实际的搜索/替换:^\([^\n]\+\)\n
^
在“模式行”中搜索所有不是换行符的字符(从行的开头开始)[^\n]
(一次或多次\+
),直到换行符\n
。现在它存储在反向引用中\1
。它包含“图案线”。(.*[^`])
搜索.*
后跟一个字符(不是反引号)的任何字符[^`]
。这存储在\2
.\2
现在包含:line with a word and words and wording wordy
,直到最后一次出现word
,因为...\1
是下一个搜索项(反向引用\1
,word
),因此“模式行”包含的内容。([^`])
后面跟着另一个不是反引号的字符;保存到参考\3
。如果我们不这样做(以及上面的部分\2
),我们将在无限循环中结束word
,一遍又一遍地引用相同的 ->````word````
,因为s///
总是会成功并tl;
跳回到:l
(请参阅tl;
下面)。\1\n\2
以上所有内容都被反向引用所取代。第二个\1
\3\1
是我们应该引用的(注意第一个引用是“图案线”)。
tl;
如果s///
成功(我们替换了某些内容),则跳转到名为的标签l
并重新开始,直到没有更多内容可供搜索和替换。当所有出现的单词都被替换/引用时就是这种情况。p;
完成所有操作后,打印更改后的行(图案空间)。
输出:
$ sed -n '1h; 2{x;G;:l;s/^\([^\n]\+\)\n\(.*[^`]\)\1\([^`]\)/\1\n\2`\1`\3/;tl;p}' file
word
line with a `word` and `word`s and `word`ing `word`y `word`s.
答案2
查找表可能很困难 -而且很贵- 因为你必须同时搜索模式空间的两端。不过,它至少可以或多或少地直接实施。你必须考虑到,无论你做什么,你一次只能可靠地处理一场比赛,所以你最好放弃任何获得g
全球结果的希望。无论如何,它只会让事情变得混乱 - 你不是在使用编译的表达式,你实际上是在处理副作用并且两个都两侧启动。
printf %s\\n some words to match \
'and some words and some more words to match them against' |
sed -ne'$!{H;d;}' -e'G;s/\(\n\).*/\1&\1/;tm' -e:m \
-e 's/\(.\)\(.*\)\(.*\n\n.*\n\1\2\(\n\)\)/`\1\4\2`\3/;tm'
这就是主循环。它实际上还没有起作用,因为我还没有清理它,但它解决了根本问题。因为您必须重复循环相同的模式空间,所以您如何确定您的匹配不会匹配两次,对吧?如果你用一些分隔符来结束它,你仍然会再次匹配,并且你只会无限地堆叠书尾。
我在这里使用的解决方案是破坏比赛。我在匹配的第一个字符后插入一个换行符,当然,我仍然需要清理它,我会处理一下。但是,如果您的查找表可以包含其他成员的子集,或者您正在使用单个字符集,那么这仍然不起作用。有很多方法可以做到这一点 - 以及更好的方法 - 如果您需要的话,我会为您提供一些替代方案。
这里还有更多内容:
printf %s\\n some words to match \
'and some words and some more words to match them against' |
sed -ne'$!{H;d;}' -e'G;s/\(\n\).*/\1&\1/;tm' -e:m \
-e 's/\(.\)\(.*\)\(.*\n\n.*\n\1\2\(\n\)\)/`\1\4\2`\3/;tm' \
-e l
and `s\nome` `w\nords` and `s\nome` more `w\nords` `t\no` `m\natch` \
them against\n\n\nsome\nwords\nto\nmatch\n$
当然,清理也很容易:
printf %s\\n some words to match \
'and some words and some more words to match them against' |
sed -ne'$!{H;d;}' -e'G;s/\(\n\).*/\1&\1/;tm' -e:m \
-e 's/\(.\)\(.*\)\(.*\n\n.*\n\1\2\(\n\)\)/`\1\4\2`\3/;tm' \
-e 's/\(`.\)\n/\1/g;P'
and `some` `words` and `some` more `words` `to` `match` them against
至少,你可以g
在全球范围内做到这一点。
我做这类事情的首选方法是实际为其构建一个脚本。
printf %s\\n some words to match \
'and some words and some more words to match them against' |
{ sed -e"$(
sed -ne'$w /dev/fd/3' -e$\q \
-e 's/[]\^$/.*[]/\\&/g' \
-e 's|..*|s/&/`\&`/g|p'
)" <&3
} 3<<"" 3<>/dev/fd/3
and `some` `words` and `some` more `words` `to` `match` them against
sed
命令内替换会在注意转义任何输入行(但最后一个可能包含)的任何元字符后写出替换sed
s///
语句。最后一行w
按字面意思写入共享的here-doc 文件描述符,以便外部sed
将其作为输入读取。内部sed
打印一个脚本,其工作原理如下:
sed -e's/some/`&`/g' \
-e's/words/`&`/g' \
-e's/to/`&`/g' \
-e's/match/`&`/g'
...然后将最后一行交给另一个人sed
来处理。