原来的字符串是这样的:
str-str001-002_01
str-str005-006_05
我想在数字之前和下划线之后提取字符串,所以它会是这样的:
str-str_01
str-str_05
我记得 sed 可以将模式分成这样的组:
sed -n 's/\(^.*\)\([0-9]-[0-9]\)\(.*$\)/\1\3/p'
但它打印:
str-str0002_01
然后我记得[0-9]只是一个数字,所以我用+号或*号尝试了它。然后它给出空结果。
ps:通过使用
echo 'str-str001-002_01' | sed -n 's/\(^.*\)\([0-9]-[0-9]\)\(.*$\)/\2/p'
我可以看到它匹配1-0
。
然后我尝试了:
echo 'str-str001-002_01' | sed -n 's/\(^.*\)\([0-9]\+-[0-9]\+\)\(.*$\)/\2/p'
它留下了前 2 个数字,并且只匹配
1-002
那么如何使其匹配001-002
答案1
这提供了所需的输出:
sed -nE 's/^([^0-9]*).*_([^_]+)$/\1_\2/p'
您的示例的输出
str-str_01
str-str_05
解释
sed -nE 's/…/…/p'
- 使用 ERE,除非它们匹配,否则不要打印行^
- 锚定到行的开头([^0-9]*)
- 匹配尽可能长的模式,即至少一个非数字字符.*_
- 尽可能匹配(包括什么都不匹配),后面跟“_
”([^_]+)
- 匹配尽可能长的不是下划线的模式(至少一个字符)$
- 锚定到行尾\1_\2
- 将整行替换为第一个(…)
匹配项“_
”和第二个(…)
匹配项
您的尝试未按预期工作的原因是因为*
(and +
) 是贪婪的 - 它将消耗尽可能多的与前面的原子匹配的字符。因此,对于(.*)([0-9]+)
应用于类似的ERE abc123
,.*
将消耗abc12
,留下[0-9]+
匹配3
。你需要一个“不是数字" 限制第一个匹配:([^0-9]*)([0-9]+)
得到abc
和123
。
答案2
$ cat file
str-str001-002_01
str-str005-006_05
$ sed 's/[0-9]\{3\}-[0-9]\{3\}//' file
str-str_01
str-str_05
这里的替换命令是匹配并删除NNN-NNN
其中NNN
的三位数。
匹配最后一个数字,用于1,
代替3
:
$ sed 's/[0-9]\{1,\}-[0-9]\{1,\}//' file
str-str_01
str-str_05
这对应于+
在扩展正则表达式中的使用。默认情况下使用的正则表达式sed
是“基本”正则表达式,并且+
会匹配文字加字符。大多数sed
实现还支持扩展表达式-E
:
$ sed -E 's/[0-9]+-[0-9]+//' file
str-str_01
str-str_05
使用*
,如[0-9]*-[0-9]*
,将不起作用,因为它会匹配破折号str-str
(其周围有零位数字)。
如果您觉得确实必须匹配整条线并捕获您想要保留的位,那么您也可以这样做。以下命令捕获初始非数字和最后一位,包括下划线:
$ sed 's/\([^0-9]*\).*\(_.*\)/\1\2/' file
str-str_01
str-str_05
然而,恕我直言,这有点难以破译,并对您在问题中从未提到的字符串的开头和结尾做出假设。例如,开头不能包含要删除的数字之前的数字,并且字符串的结尾将在最后的下划线,如果字符串的该部分中有多个下划线,则不必在要删除的数字之后。
您始终可以进一步添加此表达式以确保仅NNN-NNN
不捕获该位,但这会使该表达式更难以理解。