如何对单行分隔项进行数字排序?

Jef*_*ler 13 sort numeric-data

我有一行(或多行)由任意字符分隔的数字。我可以使用哪些 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

由于分隔符是任意的,请随意使用您选择的单字符分隔符提供(或扩展)答案。

αғs*_*нιη 14

使用gawk( GNU awk ) 作为asort()函数

gawk -v SEP='*' '{ i=0; split($0, arr, SEP); len=asort(arr);
    while ( ++i<=len ){ printf("%s%s", i>1?SEP:"", arr[i]) }; 
        print "" 
}' infile
Run Code Online (Sandbox Code Playgroud)

用您的分隔符替换*为字段分隔SEP='*'



您也可以在单行的情况下使用以下命令(因为最好不要使用 shell-loops 进行文本处理

tr '.' '\n' <<<"$aline" | sort -n | paste -sd'.' -
Run Code Online (Sandbox Code Playgroud)

用您的分隔符替换 .
添加-usort上面的命令以删除重复项。

注意:
您可能需要使用-g, --general-numeric-sort选项sort而不是-n, --numeric-sort来处理任何类别的数字(整数、浮点数、科学数、十六进制数等)。

$ aline='2e-18,6.01e-17,1.4,-4,0xB000,0xB001,23,-3.e+11'
$ tr ',' '\n' <<<"$aline" |sort -g | paste -sd',' -
-3.e+11,-4,2e-18,6.01e-17,1.4,23,0xB000,0xB001
Run Code Online (Sandbox Code Playgroud)

awk没有必要的改变,它仍然会处理这些。


Ste*_*ris 12

使用perl有一个明显的版本;拆分数据,对其进行排序,然后将其重新连接起来。

分隔符需要列出两次(一次在 中split一次在 中join

例如对于一个 ,

perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
Run Code Online (Sandbox Code Playgroud)

所以

echo 1,100,330,42 | perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
1,42,100,330
Run Code Online (Sandbox Code Playgroud)

由于split是正则表达式,该字符可能需要引用:

echo 10.1.200.42 | perl -lpi -e '$_=join(".",sort {$a <=> $b} split(/\./))'
1.10.42.200
Run Code Online (Sandbox Code Playgroud)

通过使用-a-F选项,可以删除拆分。使用-p循环,和以前一样,将结果设置为$_,这将自动打印:

perl -F'/\./' -aple '$_=join(".", sort {$a <=> $b} @F)'
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用 `-l` 选项代替使用 `chomp`。这也会在打印时重新添加换行符。有关拆分部分,另请参阅 `-a`(带有 `-F`)。 (4认同)
  • @StephenHarris 那是因为较新版本的 perl 在使用 `-F` 时会自动添加 `-a` 和 `-n` 选项,当使用 `-a` 时会自动添加 `-n` 选项......所以只需将 `-le` 更改为`-车道` (2认同)

mur*_*uru 6

使用 Python 和与Stephen Harris 的回答类似的想法:

python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' <delmiter>
Run Code Online (Sandbox Code Playgroud)

所以像:

$ cat foo
10.129.3.4
1.1.1.1
4.3.2.1
$ python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' . < foo
3.4.10.129
1.1.1.1
1.2.3.4
Run Code Online (Sandbox Code Playgroud)

遗憾的是,必须手动执行 I/O 使得这远不如 Perl 版本优雅。


Jef*_*ler 5

使用sed对 IP 地址的八位字节进行排序

sed没有内置sort函数,但如果您的数据在范围内受到足够的限制(例如 IP 地址),您可以生成一个 sed 脚本来手动实现简单的冒泡排序。基本机制是寻找无序的相邻数字。如果数字顺序不正确,则交换它们。

sed脚本本身包含针对每对无序数字的两个搜索和交换命令:一个用于前两对八位位组(强制出现尾随定界符以标记第三个八位位组的结尾),另一个用于搜索和交换命令。第二个用于第三对八位位组(以 EOL 结尾)。如果发生交换,程序会分支到脚本顶部,查找无序的数字。否则,它退出。

生成的脚本部分是:

$ head -n 3 generated.sed
:top
s/255\.254\./254.255./g; s/255\.254$/254.255/
s/255\.253\./253.255./g; s/255\.253$/253.255/

# ... middle of the script omitted ...

$ tail -n 4 generated.sed
s/2\.1\./1.2./g; s/2\.1$/1.2/
s/2\.0\./0.2./g; s/2\.0$/0.2/
s/1\.0\./0.1./g; s/1\.0$/0.1/
ttop
Run Code Online (Sandbox Code Playgroud)

这种方法将句点硬编码为分隔符,必须对其进行转义,否则对于正则表达式语法来说它将是“特殊的”(允许任何字符)。

要生成这样的 sed 脚本,此循环将执行以下操作:

#!/bin/bash

echo ':top'

for (( n = 255; n >= 0; n-- )); do
  for (( m = n - 1; m >= 0; m-- )); do
    printf '%s; %s\n' "s/$n\\.$m\\./$m.$n./g" "s/$n\\.$m\$/$m.$n/"
  done
done

echo 'ttop'
Run Code Online (Sandbox Code Playgroud)

将该脚本的输出重定向到另一个文件,例如sort-ips.sed.

示例运行可能如下所示:

ip=$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256))
printf '%s\n' "$ip" | sed -f sort-ips.sed
Run Code Online (Sandbox Code Playgroud)

生成脚本的以下变体使用单词边界标记\<\>消除第二次替换的需要。这还将生成的脚本的大小从 1.3 MB 减少到略低于 900 KB,同时大大减少了脚本本身的运行时间sed(大约为原始脚本的 50%-75%,具体取决于所sed使用的实现):

#!/bin/bash

echo ':top'

for (( n = 255; n >= 0; --n )); do
  for (( m = n - 1; m >= 0; --m )); do
      printf '%s\n' "s/\\<$n\\>\\.\\<$m\\>/$m.$n/g"
  done
done

echo 'ttop'
Run Code Online (Sandbox Code Playgroud)

  • 这是一个有趣的想法,但它似乎确实让事情变得有点过于复杂了。 (2认同)

小智 5

加载高级语言需要时间。
对于几行,shell 本身可能就是一个解决方案。
我们可以使用外部命令sort、和命令tr。一种对于行排序非常有效,另一种对于将一个分隔符转换为换行符非常有效:

#!/bin/bash
shsort(){
           while IFS='' read -r line; do
               echo "$line" | tr "$1" '\n' |
               sort -n   | paste -sd "$1" -
           done <<<"$2"
    }

shsort ' '    '10 50 23 42'
shsort '.'    '10.1.200.42'
shsort ','    '1,100,330,42'
shsort '|'    '400|500|404'
shsort ','    '3 b,2       x,45    f,*,8jk'
shsort '.'    '10.128.33.6
128.17.71.3
44.32.63.1'
Run Code Online (Sandbox Code Playgroud)

因为使用<<<only所以需要bash。如果将其替换为here-doc,则该解决方案对 posix 有效。
这能够使用制表符、空格或 shell 全局字符 ( *, ?, [) 对字段进行排序。不是换行符,因为每一行都在排序。

更改<<<"$2"<"$2"处理文件名并调用它,如下所示:

shsort '.'    infile
Run Code Online (Sandbox Code Playgroud)

整个文件的分隔符是相同的。如果这是一个限制,那么可以改进。

然而,处理只有 6000 行的文件需要 15 秒。确实,shell 并不是处理文件的最佳工具。

awk

对于超过几行(超过几十行),最好使用真正的编程语言。awk 解决方案可能是:

#!/bin/bash
awksort(){
           gawk -v del="$1" '{
               split($0, fields, del)
               l=asort(fields)
               for(i=1;i<=l;i++){
                   printf( "%s%s" , (i==0)?"":del , fields[i] )
               }
               printf "\n"
           }' <"$2"
         }

awksort '.'    infile
Run Code Online (Sandbox Code Playgroud)

对于上述相同的 6000 行文件,只需要 0.2 秒。

了解<"$2"for 文件可以更改回<<<"$2"for shell 变量内的行。

珀尔

最快的解决方案是perl。

#!/bin/bash
perlsort(){  perl -lp -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' <<<"$2";   }

perlsort ' '    '10 50 23 42'
perlsort '.'    '10.1.200.42'
perlsort ','    '1,100,330,42'
perlsort '|'    '400|500|404'
perlsort ','    '3 b,2       x,45    f,*,8jk'
perlsort '.'    '10.128.33.6
128.17.71.3
44.32.63.1'
Run Code Online (Sandbox Code Playgroud)

如果您想对文件更改<<<"$a"进行简单排序"$a"并添加-i到 perl 选项以使文件版本“就位”:

#!/bin/bash
perlsort(){  perl -lpi -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' "$2"; }

perlsort '.' infile; exit
Run Code Online (Sandbox Code Playgroud)


Ser*_*nyy 5

重击脚本:

#!/usr/bin/env bash

join_by(){ local IFS="$1"; shift; echo "$*"; }

IFS="$1" read -r -a tokens_array <<< "$2"
IFS=$'\n' sorted=($(sort -n <<<"${tokens_array[*]}"))
join_by "$1" "${sorted[@]}"
Run Code Online (Sandbox Code Playgroud)

例子:

$ ./sort_delimited_string.sh "." "192.168.0.1"
0.1.168.192
Run Code Online (Sandbox Code Playgroud)

基于