数字、括号和 sed
Numbers, parentheses and sed
我需要清理一些文本并尝试删除括号中出现的数字。如果还有更多,那么应该保留。
示例:
Foo 12 (bar, 13) -> Foo 12 (bar)
Foo 12 (13, bar, 14) -> Foo 12 (bar)
Foo (14, 13) -> Foo
我想我会首先分解字符串并删除数字(如果它们出现在括号之间),但似乎我遗漏了什么。
echo "Foo 12 (bar, 12)" | sed 's/\(.*\)\((\)\([^0-9,].*\)\([, ].*\)\([0-9].*\)\()\)//g'
结果 Foo 12 (bar,)
。
我想我的方法太原子化了。我能做什么?
如果你对 Perl 没有问题,你可以试试这个。
$ perl -pe 's/\s*,?\s*\b\d+\b\s*,?\s*(?=[^()]*\))//g;s/\h*\(\)$//' file
Foo 12 (bar)
Foo 12 (bar)
Foo
或
$ perl -pe 's/(?:(?<=\()\d+,\h*|,?\h*\d+\b)(?=[^()]*\))//g;s/\h*\(\)$//' file
Foo 12 (bar)
Foo 12 (bar)
Foo
这是解决此类问题的通用方法,您可以在其中隔离特定标记并对其进行处理,以适应您的问题:
#!/bin/sed -f
:loop # while the line has a matching token
/([^)]*[0-9]\+[^)])/ {
s//\n&\n/ # mark it -- \n is good as a marker because it is
# nowhere else in the line
h # hold the line!
s/.*\n\(.*\)\n.*// # isolate the token
s/[0-9]\+,\s*//g # work on the token. Here this removes all numbers
s/,\s*[0-9]\+//g # with or without commas in front or behind
s/\s*[0-9]\+\s*//g
s/\s*()// # and also empty parens if they exist after all that.
G # get the line back
# and replace the marked token with the result of the
# transformation
s/\(.*\)\n\(.*\)\n.*\n\(.*\)//
b loop # then loop to get all such tokens.
}
对于那些认为这超出了 sed 应该合理完成的范围的人,我要说:是的,但是......好吧,是的。但是,如果你看到的只是钉子,这是一种将 sed 变成大锤的方法。
这当然可以写成内联(尽管这不利于可读性):
echo 'Foo 12 (bar, 12)' | sed ':loop;/([^)]*[0-9]\+[^)])/{;s//\n&\n/;h;s/.*\n\(.*\)\n.*//;s/[0-9]\+,\s*//g;s/,\s*[0-9]\+//g;s/\s*[0-9]\+\s*//g;s/\s*()//;G;s/\(.*\)\n\(.*\)\n.*\n\(.*\)//;b loop}'
但我的建议是将其放入文件中 运行 echo 'Foo 12 (bar, 12)' | sed -f foo.sed
。或者,使用上面的 shebang,chmod +x foo.sed
和 echo 'Foo 12 (bar, 12)' | ./foo.sed
.
顺便说一句,我没有对此进行基准测试。我想这不是处理大量数据的最有效方法。
编辑:回应评论:我不确定 OP 在这种情况下想要什么,但为了完成,基本模式可以适应其他行为,如下所示:
#!/bin/sed -f
:loop
/(\s*[0-9]\+\s*)\|(\s*[0-9]\+\s*,[^)]*)\|([^)]*,\s*[0-9]\+\s*)\|([^)]*,\s*[0-9]\+\s*,[^)]*)/ {
s//\n&\n/
h
s/.*\n\(.*\)\n.*//
s/,\s*[0-9]\+\s*,/,/g
s/(\s*[0-9]\+\s*,\s*/(/
s/\s*,\s*[0-9]\+\s*)/)/
s/\s*(\s*[0-9]*\s*)//
G
s/\(.*\)\n\(.*\)\n.*\n\(.*\)//
b loop
}
顶部的正则表达式现在看起来更可怕了。知道它由四个子模式组成应该会有所帮助
(\s*[0-9]\+\s*)
(\s*[0-9]\+\s*,[^)]*)
([^)]*,\s*[0-9]\+\s*)
([^)]*,\s*[0-9]\+\s*,[^)]*)
与 \|
进行或运算。这应该涵盖所有情况,并且不匹配括号中的 foo12
、12bar
和 foo12bar
之类的内容(除非它们中也有独立的数字)。
这是一个 awk
版本:
awk -F' *\(|\)' '{for (i=2;i<=NF;i+=2) {n=split($i,a," *, *");f="";for (j=1;j<=n;j++) f=f (a[j]!~/[[:digit:]]/?a[j]",":""); $i=f?"("f")":"";sub(/,)/,")",$i)}}1' file
Foo 12 (bar)
Foo 12 (bar)
Foo
目录文件
Foo 12 (bar, 13, more)
Foo 12 (13, bar, 14) (434, tar ,56)
Foo (14, 13)
awk -F' *\(|\)' '{for (i=2;i<=NF;i+=2) {n=split($i,a," *, *");f="";for (j=1;j<=n;j++) f=f (a[j]!~/[[:digit:]]/?a[j]",":""); $i=f?"("f")":"";sub(/,)/,")",$i)}}1' file
Foo 12 (bar,more)
Foo 12 (bar) (tar)
Foo
更具可读性:
awk -F' *\(|\)' '
{
for (i=2;i<=NF;i+=2) {
n=split($i,a," *, *")
f=""
for (j=1;j<=n;j++)
f=f (a[j]!~/[[:digit:]]/?a[j]",":"")
$i=f?"("f")":""
sub(/,)/,")",$i)
}
}
1' file
sed ':retry
# remove "( number )"
s/( *[0-9]* *)//
# remove first ", number" (not at first place)
s/^\(\([^(]*([^(]*)\)*[^(]*([^)]*\), *[0-9]\{1,\} *\([,)]\)//
t retry
# remove " number" (first place)
s/^\(\([^(]*([^(]*)\)*[^(]*(\) *[0-9]\{1,\}\(,\{0,1\}\)\()\{0,1\}\)]*//
# case needed where only "( number)" or "()" are the result at this moment
t retry
' YourFile
- (posix 版本所以
--POSIX
在 GNU sed 上)
我需要清理一些文本并尝试删除括号中出现的数字。如果还有更多,那么应该保留。
示例:
Foo 12 (bar, 13) -> Foo 12 (bar)
Foo 12 (13, bar, 14) -> Foo 12 (bar)
Foo (14, 13) -> Foo
我想我会首先分解字符串并删除数字(如果它们出现在括号之间),但似乎我遗漏了什么。
echo "Foo 12 (bar, 12)" | sed 's/\(.*\)\((\)\([^0-9,].*\)\([, ].*\)\([0-9].*\)\()\)//g'
结果 Foo 12 (bar,)
。
我想我的方法太原子化了。我能做什么?
如果你对 Perl 没有问题,你可以试试这个。
$ perl -pe 's/\s*,?\s*\b\d+\b\s*,?\s*(?=[^()]*\))//g;s/\h*\(\)$//' file
Foo 12 (bar)
Foo 12 (bar)
Foo
或
$ perl -pe 's/(?:(?<=\()\d+,\h*|,?\h*\d+\b)(?=[^()]*\))//g;s/\h*\(\)$//' file
Foo 12 (bar)
Foo 12 (bar)
Foo
这是解决此类问题的通用方法,您可以在其中隔离特定标记并对其进行处理,以适应您的问题:
#!/bin/sed -f
:loop # while the line has a matching token
/([^)]*[0-9]\+[^)])/ {
s//\n&\n/ # mark it -- \n is good as a marker because it is
# nowhere else in the line
h # hold the line!
s/.*\n\(.*\)\n.*// # isolate the token
s/[0-9]\+,\s*//g # work on the token. Here this removes all numbers
s/,\s*[0-9]\+//g # with or without commas in front or behind
s/\s*[0-9]\+\s*//g
s/\s*()// # and also empty parens if they exist after all that.
G # get the line back
# and replace the marked token with the result of the
# transformation
s/\(.*\)\n\(.*\)\n.*\n\(.*\)//
b loop # then loop to get all such tokens.
}
对于那些认为这超出了 sed 应该合理完成的范围的人,我要说:是的,但是......好吧,是的。但是,如果你看到的只是钉子,这是一种将 sed 变成大锤的方法。
这当然可以写成内联(尽管这不利于可读性):
echo 'Foo 12 (bar, 12)' | sed ':loop;/([^)]*[0-9]\+[^)])/{;s//\n&\n/;h;s/.*\n\(.*\)\n.*//;s/[0-9]\+,\s*//g;s/,\s*[0-9]\+//g;s/\s*[0-9]\+\s*//g;s/\s*()//;G;s/\(.*\)\n\(.*\)\n.*\n\(.*\)//;b loop}'
但我的建议是将其放入文件中 运行 echo 'Foo 12 (bar, 12)' | sed -f foo.sed
。或者,使用上面的 shebang,chmod +x foo.sed
和 echo 'Foo 12 (bar, 12)' | ./foo.sed
.
顺便说一句,我没有对此进行基准测试。我想这不是处理大量数据的最有效方法。
编辑:回应评论:我不确定 OP 在这种情况下想要什么,但为了完成,基本模式可以适应其他行为,如下所示:
#!/bin/sed -f
:loop
/(\s*[0-9]\+\s*)\|(\s*[0-9]\+\s*,[^)]*)\|([^)]*,\s*[0-9]\+\s*)\|([^)]*,\s*[0-9]\+\s*,[^)]*)/ {
s//\n&\n/
h
s/.*\n\(.*\)\n.*//
s/,\s*[0-9]\+\s*,/,/g
s/(\s*[0-9]\+\s*,\s*/(/
s/\s*,\s*[0-9]\+\s*)/)/
s/\s*(\s*[0-9]*\s*)//
G
s/\(.*\)\n\(.*\)\n.*\n\(.*\)//
b loop
}
顶部的正则表达式现在看起来更可怕了。知道它由四个子模式组成应该会有所帮助
(\s*[0-9]\+\s*)
(\s*[0-9]\+\s*,[^)]*)
([^)]*,\s*[0-9]\+\s*)
([^)]*,\s*[0-9]\+\s*,[^)]*)
与 \|
进行或运算。这应该涵盖所有情况,并且不匹配括号中的 foo12
、12bar
和 foo12bar
之类的内容(除非它们中也有独立的数字)。
这是一个 awk
版本:
awk -F' *\(|\)' '{for (i=2;i<=NF;i+=2) {n=split($i,a," *, *");f="";for (j=1;j<=n;j++) f=f (a[j]!~/[[:digit:]]/?a[j]",":""); $i=f?"("f")":"";sub(/,)/,")",$i)}}1' file
Foo 12 (bar)
Foo 12 (bar)
Foo
目录文件
Foo 12 (bar, 13, more)
Foo 12 (13, bar, 14) (434, tar ,56)
Foo (14, 13)
awk -F' *\(|\)' '{for (i=2;i<=NF;i+=2) {n=split($i,a," *, *");f="";for (j=1;j<=n;j++) f=f (a[j]!~/[[:digit:]]/?a[j]",":""); $i=f?"("f")":"";sub(/,)/,")",$i)}}1' file
Foo 12 (bar,more)
Foo 12 (bar) (tar)
Foo
更具可读性:
awk -F' *\(|\)' '
{
for (i=2;i<=NF;i+=2) {
n=split($i,a," *, *")
f=""
for (j=1;j<=n;j++)
f=f (a[j]!~/[[:digit:]]/?a[j]",":"")
$i=f?"("f")":""
sub(/,)/,")",$i)
}
}
1' file
sed ':retry
# remove "( number )"
s/( *[0-9]* *)//
# remove first ", number" (not at first place)
s/^\(\([^(]*([^(]*)\)*[^(]*([^)]*\), *[0-9]\{1,\} *\([,)]\)//
t retry
# remove " number" (first place)
s/^\(\([^(]*([^(]*)\)*[^(]*(\) *[0-9]\{1,\}\(,\{0,1\}\)\()\{0,1\}\)]*//
# case needed where only "( number)" or "()" are the result at this moment
t retry
' YourFile
- (posix 版本所以
--POSIX
在 GNU sed 上)