select 只有一个单词是冒号的一部分

select only a word that is part of colon

我有一个使用标记语言的文本文件(类似于维基百科文章)

cat test.txt
This is a sample text having: colon in the text. and there is more [[in single or double: brackets]]. I need to select the first word only.
and second line with no [brackets] colon in it.

我只需要select“有:”这个词,因为它是常规文本的一部分。我试过了

grep -v '[*:*]' test.txt

这将正确避开标签,但不会 select 预期的词。

使用sedawk的组合解决方案:

sed 's/ /\n/g' test.txt | gawk 'i==0 && [=10=]~/:$/{ print [=10=] }/\[/{ i++} /\]/ {i--}'
  • sed 会将所有 space 更改为换行符
  • awk(或 gawk)将输出匹配 [=17=]~/:$/ 的所有行,只要 i 等于零
  • awk 的最后一部分记录了左括号和右括号的数量。

另一个使用sedgrep的解决方案:

sed -r -e 's/\[.*\]+//g' -e 's/ /\n/g' test.txt  | grep ':$'
  • 's/\[.*\]+//g' 将过滤括号内的内容
  • 's/ /\n/g' 将用换行符
  • 替换 space
  • grep 只会查找以 :
  • 结尾的行

第三次仅使用 awk:

gawk '{ for (t=1;t<=NF;t++){ 
            if(i==0 && $t~/:$/) print $t; 
            i=i+gsub(/\[/,"",$t)-gsub(/\]/,"",$t) }}' test.txt
  • gsubreturns替换次数
  • 变量i用来统计括号的层数。每 [ 它递增 1,每 ] 它递减 1。这样做是因为 gsub(/\[/,"",$t) returns 被替换字符的数量。当有像 [[][ 这样的标记时,计数增加 (3-1=) 2. 当标记有方括号和分号时,我的代码将失败,因为标记将匹配,如果它以 [=23 结尾=], 在括号计数之前。

方括号指定一个字符 class,因此您的正则表达式会查找字符 *:(或 *,但是我们已经说过了,不是吗?)

grep 有选项 -o 只打印匹配的文本,所以有些谎言

grep -ow '[^[:space:]]*:[^[:space:]]*' file.txt

将提取任何带有冒号的文本,每边被零个或多个 non-whitespace 字符包围。 -w 选项添加匹配需要在单词边界之间的条件。

但是,如果您想限制匹配文本的上下文,您可能需要切换到比普通工具更强大的工具 grep。例如,您可以使用 sed 预处理每一行以删除任何带括号的文本,然后 然后 在剩余文本中查找匹配项。

sed -e 's/\[.*]//g' -e 's/ [^: ]*$/ /' -e 's/[^: ]* //g' -e 's/ /\n/' file.txt

(假设您的 sed 将替换字符串中的 \n 识别为文字换行符。如果没有,可以使用简单的解决方法,但如果没有必要,我们不要去那里.)

简而言之,我们首先替换方括号之间的任何文本。 (如果您的输入可能在一行中包含多个方括号序列且方括号之间有普通文本,则需要改进这一点。您的示例仅显示嵌套的方括号,但我的方法对于任何一种情况都可能太简单了。)然后,我们删除任何不包含冒号的单词,对行中的最后一个单词有特殊规定,并进行一些后续清理。最后,我们将剩余的 space 替换为换行符,并(隐含地)打印剩余的内容。 (这仍然会导致多打印一个换行符,但这很容易在以后修复。)

或者,我们可以使用 sed 删除任何括号内的表达式,然后对剩余的标记使用 grep

sed -e :a -e 's/\[[^][]*\]//' -e ta file.txt |
grep -ow '[^[:space:]]*:[^[:space:]]*'

:a 创建一个标签 a 并且 ta 表示如果正则表达式匹配则跳回该标签并重试。这一个还演示了如何处理嵌套和重复的括号。 (我想它可以重构到之前的尝试中,这样我们就可以避免通往 grep 的管道。但我想,概述不同的解决方案模型在这里也很有用。)

如果你想确保至少有一个 non-colon 字符与冒号相邻,你可以这样做

... file.txt |
grep -owE '[^:[:space:]]+:[^[:space:]]*|[^[:space:]]*:[^: [:space:]]+'

其中 -E 选项 select 是一种稍微更现代的正则表达式方言,它允许我们在备选方案之间使用 |,并使用 + 进行一次或多次重复。 (1969 年的基本 grep 根本没有这些功能;很久以后,POSIX 标准用稍微古怪的语法嫁接了它们,需要你将它们反斜杠 remove 字面意思和 select 元字符行为......但我们不要去那里。)

还要注意 [^:[:space:]] 如何匹配不是冒号或白色的单个字符 space,其中 [:space:] 是(有点神秘的)特殊字符 POSIX命名字符 class 匹配任何白色 space 字符(常规 space、水平制表符、垂直制表符,可能是 Unicode 白色space 字符,具体取决于语言环境)。

Awk 可以轻松地让您遍历一行中的标记。忽略方括号内匹配的要求使事情变得有些复杂;你可以保留一个单独的变量来跟踪你是否在括号内。

awk '{ for(i=1; i<=NF; ++i) {
        if($i ~ /\]/) { brackets=0; next }
        if($i ~ /\[/) brackets=1;
        if(brackets) next;
        if($i ~ /:/) print $i }' file.txt

这又是 hard-codes 关于如何放置括号的一些可能不正确的假设。如果单个标记包含一个右方括号后跟一个左方括号,并且对嵌套括号的处理过于简单(一系列左括号之后的第一个右括号将有效地假设我们不再位于括号内),它将出现意外行为。