【问题标题】:Sort alphanumeric string in Unix using custom sort order使用自定义排序顺序在 Unix 中对字母数字字符串进行排序
【发布时间】:2017-01-23 10:41:43
【问题描述】:

我有一个不按顺序排列的名字列表。如何使用 alphabetical 部分的 custom 排序顺序以正确的字母数字顺序获取它们?

我的档案numbers.txt

alpha-1
beta-3
alpha-10
beta-5
alpha-5
beta-1
gamma-7
gamma-1
delta-10
delta-2

主要的一点是我的脚本应该认识到它应该在beta之前打印alpha,在gamma之前打印beta,在gamma之前打印delta

也就是说,单词应该根据它们所代表的希腊字母表中的字母顺序进行排序。

预期顺序:

alpha-1
alpha-5
alpha-10
beta-1
beta-3
beta-5
gamma-1
gamma-7
delta-2
delta-10

PS:我试过sort -n numbers.txt,但它不符合我的需要。

【问题讨论】:

  • 正如我之前所说,重点是要遵守特定的顺序(alpha、beta、gamma 然后是 delta)。我认为'sort -k'不会解决这个问题。没有?
  • 我现在更清楚地看到了你想要做什么,不幸的是,用普通的sort 真的不可能。使用awk 可能是可能的,但最好的解决方案可能是创建一个程序(例如 Python)来进行排序。
  • 实际上可以通过装饰、排序和取消装饰来完成。即根据行内容添加前缀(可以使用 sed 等来完成),根据前缀排序,然后剥离前缀(与添加它的方式相同)

标签: shell sorting unix


【解决方案1】:

您可以使用辅助awk命令如下:

awk -F- -v keysInOrder="alpha,beta,gamma,delta" '
    BEGIN {
        split(keysInOrder, a, ",")
        for (i = 1; i <= length(a); ++i) keysToOrdinal[a[i]] = i
    }
    { print keysToOrdinal[$1] "-" $0 }
' numbers.txt | sort -t- -k1,1n -k3,3n | cut -d- -f2-
  • awk 命令用于:

    • 将自定义键映射到反映所需排序顺序的数字上;请注意,完整的键列表必须按顺序通过变量keysInOrder 传递。

    • 将数字作为辅助列添加到输入中,也使用分隔符-;例如,beta-3 变为 2-beta-3,因为 beta 在排序键的有序列表中位于 2 位置。

  • sort 然后按 映射数字 以及第二列中的原始数字对 awk 的输出进行排序,从而产生所需的自定义排序顺序。

  • cut 然后删除辅助。再次映射数字。

【讨论】:

【解决方案2】:

这是一个 Python 解决方案。不要试图用 Bash、sed、awk 做困难的事情。你通常可以完成你想要的,但它会更令人困惑、更容易出错并且更难维护。

#!/usr/bin/env python3

# Read input lines
use_stdin = True
if use_stdin:
    import sys
    lines = sys.stdin.read().strip().split()
else:
    # for testing
    with open('numbers.txt') as input:
        lines = input.read().strip().split()

# Create a map from greek letters to integers for sorting
greek_letters = """alpha beta     gamma   delta epsilon zeta
                   eta   theta    iota    kappa lambda  mu
                   nu    xi       omicron pi    rho     sigma
                   tau   upsilon  phi     chi   psi     omega"""
gl = greek_letters.strip().split()
gl_map = {letter:rank for rank, letter in enumerate(gl)}

# Split each line into (letter, number)
a = (x.split('-') for x in lines)
b = ((s, int(n)) for s,n in a)

# Using an order-preserving sort, sort by number, then letter
by_number = lambda x: x[1]
by_greek_letter = lambda x: gl_map.get(x[0])
c = sorted(sorted(b, key=by_number), key=by_greek_letter)

# Re-assemble and print
for s,n in c:
    print('-'.join((s, str(n))))

【讨论】:

    【解决方案3】:

    我会在这里使用 Perl。该脚本将起作用:

    #!/usr/bin/env perl
    use v5.14;          # turn on modern features
    
    # Greek alphabet
    my @greek_letters =qw(alpha beta     gamma   delta epsilon zeta
                          eta   theta    iota    kappa lambda  mu
                          nu    xi       omicron pi    rho     sigma
                          tau   upsilon  phi     chi   psi     omega);
    
    # An inverted map from letter name to position number;
    # $number{alpha} = 1, $number{beta} = 2, etc:
    my %number;
    @number{@greek_letters} = 1..@greek_letters;
    
    # Read the lines to sort
    chomp(my @lines = <>);
    
    # split on hyphen into arrays of individual fields
    my @rows = map { [ split /-/ ] } @lines;
    
    # prepend the numeric position of each item's Greek letter
    my @keyed = map { [ $number{$_->[0]}, @$_ ] } @rows;
    
    # sort by Greek letter position (first field, index 0) and then
    # by final number (third field, index 2)
    my @sorted = sort {   $a->[0] <=> $b->[0]
                       || $a->[2] <=> $b->[2] } @keyed;
    
    # remove the extra field we added
    splice(@$_, 0, 1) for @sorted;
    
    # combine the fields back into strings and print them out
    say join('-', @$_) for @sorted;
    

    将 Perl 代码保存到文件中(例如,greeksort.pl)并运行 perl greeksort.pl numbers.txt 以获取排序后的输出。

    【讨论】:

    • 人们抱怨 Perl 不容易破译? ;)
    • 我发现它并不比其他答案中的 Python 代码更难阅读,但我添加了内联 cmets 以提高可读性。
    【解决方案4】:

    通用解决方案: 排序 -t- -k 1,1 -k 2,2n numbers.txt

    以下脚本适用于自定义要求。这不是最好的解决方案。 结果将再次存储在 numbers.txt 中

    #!/bin/bash
    
    sort -t- -k 1,1 -k 2,2n numbers.txt > new_test.txt
    while IFS= read -r i
    do 
        if [[ $i == *"delta"* ]] 
        then 
            echo $i >> temp_file
        else 
            echo $i >> new_numbers.txt
        fi 
    done < new_test.txt
    cat temp_file >> new_numbers.txt
    cat new_numbers.txt > numbers.txt
    
    rm -rf new_test.txt
    rm -rf temp_file 
    rm -rf new_numbers.txt
    

    【讨论】:

    • 谢谢,但它仍然在gamma 之前打印delta :/
    • 因为delta 按字母顺序排在gamma 之前。如果您正在寻找能够识别希腊用拉丁字母书写的字母名称并按希腊字母顺序对它们进行排序的东西,我认为您可能不得不自己写一些东西。
    • 更新后的答案现在只处理一种特殊情况 - 这将非常繁琐,而且效率也很低。
    • 提供通用和自定义解决方案,并用 while 循环替换 for。
    • 通用解决方案是按顺序获取任意排序键列表并按它们排序的解决方案。您的解决方案将 single 异常硬编码为字母排序。感谢您修复 for 循环问题。
    【解决方案5】:

    如果你可以访问 awk 和 sed,那么试试这个

    为希腊语排序添加更改..

    猫测试.txt | awk -F "-" '{ printf "%s-%0100i\n" , $1, $2 }' | \ sed 's/^alpha-\(.*\)$/01-\1/' | \ sed 's/^beta-\(.*\)$/02-\1/' | \ sed 's/^gamma-\(.*\)$/03-\1/' | \ sed 's/^delta-\(.*\)$/04-\1/' | \ 排序 | \ sed 's/\(.*\)-\([0]*\)\(.*\)/\1-\3/' | \ sed 's/^01-\(.*\)$/alpha-\1/' | \ sed 's/^02-\(.*\)$/beta-\1/' | \ sed 's/^03-\(.*\)$/gamma-\1/' | \ sed 's/^04-\(.*\)$/delta-\1/'

    【讨论】:

    • 这行得通,但由于有多个sed 命令,效率很低;请注意,您可以将它们全部组合成一个单个 sed 脚本,s 调用与; 分隔您不需要0-填充输入中的数字来实现数字排序;相反,使用sort-n 选项(字段选择性):sort -t- -k 1,1n -k 2,2n。如果您在排序之前没有替换单词,而是将映射的数字添加作为(临时)第一个字段,那么您需要做的就是是在排序后删除该字段,使用cut -d- -f2- - 不再需要sed
    【解决方案6】:

    不要试图用 Bash、sed、awk 做困难的事情

    是的,使用实际的 shell 和非 gnu 用户态命令。首先编写代码并不容易,但至少不会容易出现由对向后兼容性一无所知的白痴维护人员引入的随机错误

    【讨论】:

    • 看起来这应该是对您引用的答案的评论,而不是一个全新的答案。
    猜你喜欢
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多