我有一行(或多行)由任意字符分隔的数字。我可以使用哪些 UNIX 工具对每行的项目进行数字排序,同时保留分隔符?
示例包括:
- 号码列表;输入:
10 50 23 42
; 排序:10 23 42 50
- IP地址; 输入:
10.1.200.42
; 排序:1.10.42.200
- CSV;输入:
1,100,330,42
; 排序:1,42,100,330
- 管道分隔;输入:
400|500|404
; 排序:400|404|500
由于分隔符是任意的,请随意使用您选择的单字符分隔符提供(或扩展)答案。
使用
gawk
(GNUawk
)的asort()
功能:用您的分隔符替换
*
为字段分隔符。SEP='*'
您也可以在单行的情况下使用以下命令(因为最好不要使用 shell-loops 进行文本处理)
用你的分隔符替换点
.
。添加
-u
到sort
上面的命令以删除重复项。注意:
您可能需要使用
-g, --general-numeric-sort
选项sort
而不是-n, --numeric-sort
来处理任何类型的数字(整数、浮点数、科学、十六进制等)。awk
无需更改,它仍然会处理这些。使用
perl
有一个明显的版本;拆分数据,对其进行排序,然后将其重新连接起来。分隔符需要列出两次(一次在 中
split
,一次在 中join
)例如对于一个
,
所以
由于
split
是一个正则表达式,该字符可能需要引用:通过使用
-a
and-F
选项,可以删除拆分。像以前一样使用-p
循环并将结果设置为$_
,这将自动打印:使用 Python 和Stephen Harris 的回答中类似的想法:
所以像:
遗憾的是,必须手动执行 I/O 使得它远不如 Perl 版本优雅。
sed
用于对 IP 地址的八位字节进行排序sed
没有内置sort
函数,但如果您的数据在范围内受到足够的限制(例如使用 IP 地址),您可以生成一个手动实现简单冒泡排序的 sed 脚本。基本机制是寻找无序的相邻数字。如果数字不正确,请交换它们。sed
脚本本身包含两个用于每对无序数字的搜索和交换命令:一个用于前两对八位字节(强制出现尾随分隔符以标记第三个八位字节的结尾),以及一个第二对第三对八位位组(以 EOL 结尾)。如果发生交换,程序会跳转到脚本的顶部,寻找无序的数字。否则,它会退出。生成的脚本部分是:
这种方法将句点硬编码为分隔符,必须对其进行转义,否则它将对正则表达式语法“特殊”(允许任何字符)。
要生成这样的 sed 脚本,此循环将执行以下操作:
将该脚本的输出重定向到另一个文件,例如
sort-ips.sed
.示例运行可能如下所示:
生成脚本的以下变体使用单词边界标记
\<
并\>
摆脱了第二次替换的需要。这还将生成脚本的大小从 1.3 MB 减少到略低于 900 KB,同时大大减少了sed
自身的运行时间(减少到原始脚本的大约 50%-75%,具体取决于sed
所使用的实现):壳
加载更高级别的语言需要时间。
对于几行,shell 本身可能是一个解决方案。
我们可以使用外部命令
sort
,和命令tr
。一种在对行进行排序时非常有效,另一种在将分隔符转换为换行符时很有效:这需要 bash 因为
<<<
只使用。如果将其替换为 here-doc,则该解决方案对 posix 有效。这能够使用制表符、空格或 shell glob 字符(、、、)对字段
*
进行?
排序[
。不是换行符,因为每一行都在排序。更改
<<<"$2"
为<"$2"
处理文件名并将其称为:整个文件的分隔符相同。如果这是一个限制,它可以改进。
然而,一个只有 6000 行的文件需要 15 秒来处理。确实,shell 并不是处理文件的最佳工具。
awk
对于多于几行(多于几十行),最好使用真正的编程语言。一个 awk 解决方案可能是:
对于上面提到的相同的 6000 行文件,这只需要 0.2 秒。
了解
<"$2"
for 文件可以更改回<<<"$2"
shell 变量中的 for 行。Perl
最快的解决方案是 perl。
如果您想对文件更改
<<<"$a"
进行简单排序"$a"
并添加-i
到 perl 选项以使文件版本“就位”:bash 脚本:
例子:
基于
在 Bash 中将字符串拆分为数组
如何在 Bash 中对数组进行排序
加入数组的元素?
这里有一些 bash 自己猜测分隔符:
它可能不是很有效也不是很干净,但它确实有效。
使用喜欢
bash my_script.sh "00/00/18/29838/2"
。当相同的分隔符未一致使用或两个或多个分隔符相互跟随时返回错误。
如果使用的定界符是特殊字符,则将其转义(否则
sed
返回错误)。This answer is based on a misunderstanding of the Q., but in some cases it happens to be correct anyway. If the input is entirely natural numbers, and has only one delimiter per-line, (as with the sample data in the Q.), it works correctly. It'll also handle files with lines that each have their own delimiter, which is a bit more than what was asked for.
This shell function
read
s from standard input, uses POSIX parameter substitution to find the specific delimiter on each line, (stored in$d
), and usestr
to replace$d
with a newline\n
andsort
s that line's data, then restores each line's original delimiters:Applied to the data given in the OP:
Output:
For arbitrary delimiters:
On an input like:
It gives:
以下是Jeff 答案的变体,因为它生成了一个
sed
可以进行冒泡排序的脚本,但其差异足以保证它自己的答案。不同之处在于,它不是生成 O(n^2) 基本正则表达式,而是生成 O(n) 扩展正则表达式。生成的脚本大约有 15 KB 大。脚本的运行时间
sed
是几分之一秒(生成脚本需要更长的时间)。它仅限于对由点分隔的正整数进行排序,但不限于整数的大小(仅
255
在主循环中增加)或整数的数量。可以通过更改delim='.'
代码来更改分隔符。我已经尽力让正则表达式正确,所以我将在另一天继续描述细节。
该脚本将如下所示:
The idea behind the generated regular expressions is to pattern match for numbers that are less than each integer; those two numbers would be out-of-order, and so are swapped. The regular expressions are grouped into several OR options. Pay close attention to the ranges appended to each item, sometimes they are
{0}
, meaning the immediately-previous item is to be omitted from the searching. The regex options, from left-to-right, match numbers that are smaller than the given number by:To spell out an example, take
101
(with additional spaces for readability):Here, the first alternation allows the numbers 100 through 100; the second alternation allows 0 through 99.
Another example is
154
:Here the first option allows 150 through 153; the second allows 100 through 149, and the last allows 0 through 99.
Testing four times in a loop:
Output: