我正在研究如何在 Bash 中不使用递归的情况下生成一组数字的所有非重复排列,我发现这个答案有效,但我想了解原因。
假设您有三个数字:1、2、3。
以下命令将生成所有可能的非重复排列:
printf "%s\n" {1,2,3}{1,2,3}{1,2,3} | sort -u | sed '/\(.\).*\1/d'
123
132
213
231
312
321
我理解当参数是集合 {1, 2, 3} 的大括号扩展三次(这将打印每个可能的结果)时printf
with 的作用。%s
我知道这sort -u
只会输出唯一的行。
我知道它sed /<pattern>/d
用于删除任何匹配的行<pattern>
。
读到里面的模式sed
,我有点困惑。我知道如何阅读regex
,但我不知道这种模式在sed
命令中是如何工作的。
\( = literal '('
. = any character, once
\) = literal ')'
.* = any character, zero or more times
\1 = reference to first captured group
那么该命令如何sed
从此模式中删除非唯一值regex
?我不明白如何引用一个被捕获的组,而实际上并没有一个被捕获的组?模式中使用括号进行字面匹配?在命令出现之前,关于这次执行的一切对我来说都是有意义的sed
。
答案1
默认情况下,这是 sed 的基本正则表达式 (BRE),因此\(.\)
是包含任意一个字符的捕获组。然后,它.*
会跳过所有内容,并\1
匹配组匹配的任何内容。如果可以让所有的内容都匹配,那么某个字符会出现两次,一次是针对组,一次是针对反向引用。
事实上,如果我没有弄错的话,这甚至不适用于标准扩展正则表达式,因为(无论出于何种原因)它们不支持反向引用。反向引用仅在下面提到“BRE 匹配多个字符”,不在 ERE 下,事实上,与 ERE 相同的功能在我的 macOS 上不起作用(它采用\1
as 表示字面数字1
):
$ printf "%s\n" 122 321 | sed -E -e '/(.).*\1/d'
122
不过,GNU 工具确实支持 ERE 中的反向引用。
(我认为sort -u
这里没有必要,大括号扩展的组合应该产生没有重复的所有组合。)
答案2
第一步,你需要\(.\)
正确理解。在基本正则表达式中,它是一个捕获组,捕获必须由 重现的任何字符\1
。这些不是字面意义上的括号。
现在,最酷的部分来了!正则表达式的每个元素在每种情况下匹配什么?
Left \(.\) .* \1 Right Result
111 1 1 1 Deleted!
112 1 1 2 Deleted!
113 1 1 3 Deleted!
121 1 2 1 Deleted!
122 1 2 2 Deleted!
123 ? ? ? NoMatch
131 1 3 1 Deleted!
132 ? ? ? NoMatch
133 1 3 3 Deleted!
在 上122
,如果不清楚:由于表达式未锚定,因此1
向左走去,中间2
匹配捕获组\(.\)
,最后一个2
匹配反向引用\1
。.*
(与正则表达式匹配的零个或多个字符)将尽力适应字符串,因此在这种情况下它会收缩为空字符串。
如果您怀疑,请尝试
echo 122 | grep --color=always '\(.\).*\1'
您会看到只有22
已着色。
将其与锚定的正则表达式的版本:
$ printf "%s\n" {1,2,3}{1,2,3}{1,2,3} | sort -u | sed '/^\(.\).*\1$/d'
112
113
122
123
132
133
...
现在没有“左”和“右”槽:
^\(.\) .* \1$ Result
111 1 1 1 Deleted!
112 ? ? ? NoMatch
113 ? ? ? NoMatch
121 1 2 1 Deleted!
122 ? ? ? NoMatch
123 ? ? ? NoMatch
131 1 3 1 Deleted!
132 ? ? ? NoMatch
133 ? ? ? NoMatch
此版本中第一个数字必须是最后一个数字,因此匹配项较少。