如何在外壳脚本中按字母顺序对第二列进行排序,然后按数字排序?

时间:2018-11-04 14:59:30

标签: shell unix

我有一个如下的文本文件:

info.txt

files-550519470 19h
files-1662192679 1d
files-247106034 1d
files-1986982365 2d
files-464153317 12m
files-739420408 3d
files-77614277 3m
files-374059185 4d
files-909323637 4d
files-101830442 5d
files-1270496134 5d
files-1797797160 6d
files-812888216 7d
files-118869238 7h

我想根据第二列字母进行排序,然后在同一第二列中按数字的降序排列,然后输出应如下所示:

 files-812888216 7d
 files-1797797160 6d
 files-101830442 5d
 files-101830442 5d
 files-1270496134 5d
 files-374059185 4d
 files-909323637 4d
 files-374059185 4d
 files-909323637 4d
 files-739420408 3d
 files-1986982365 2d
 files-1662192679 1d
 files-247106034 1d
 files-550519470 19h
 files-118869238 7h
 files-464153317 12m
 files-77614277 3m

我可以通过下面的命令根据数字反转,但无法弄清楚字母。有人可以建议吗?

 sort -r -nk2 info.txt

2 个答案:

答案 0 :(得分:2)

使用Decorate, Sort, Undecorate模式:

$ sort -t $'-' -k 2 file | 
sed -E 's/(.*) ([[:digit:]][[:digit:]]*)([dmh]$)/\2 \3 \1 \2\3/' | 
awk 'BEGIN{arr["m"]=1; arr["h"]=60; arr["d"]=60*24}
     {$2=$1*arr[$2]; $1=""; print}' | 
sort -s -k1nr |
cut -d' ' -f3-
files-812888216 7d
files-1797797160 6d
files-101830442 5d
files-101830442 5d
files-1270496134 5d
files-374059185 4d
files-374059185 4d
files-909323637 4d
files-909323637 4d
files-739420408 3d
files-1986982365 2d
files-1662192679 1d
files-247106034 1d
files-550519470 19h
files-118869238 7h
files-464153317 12m
files-77614277 3m

这应该比Bash循环快得多。如果您有gawk来替换sortsed

,可以对其进行进一步优化。

如果您使用GNU或BSD排序,则可以按字母顺序d<h<m进行使用,而不进行转换:

$ sed -E 's/([^-]*)-(.*) ([[:digit:]][[:digit:]]*)([dmh]$)/\2 \4 \3 \1-\2 \3\4/' file |
sort -s -t $' ' -k2,2 -k3,3nr -k1,1 |
cut -d $' ' -f4-
# same output

答案 1 :(得分:1)

@edit

感谢@shelter的帮助!我们可以做到:

sed 's/\(.*\) \([0-9]*\)\([a-zA-Z]*\)/\3 \2 \1 \2\3/' |
sort -k1 -k2nr |
cut -d' ' -f3-
  1. sed在前面添​​加两列,一列带有第三列的字母,第二列带有第三列的数字
  2. 然后我们使用第一列和第二列的数字倒序进行排序
  3. 然后我们删除了额外添加的列。

我保留旧答案作为参考。

这是我的想法,它可以工作,但绝对不是最好的:

sed 's/\(.*\) \([0-9]*\)\([a-zA-Z]*\)/\3 \2 \1 \2\3/' |
sort -k1 | 
{
    presuffix=''
    buff=''
    while IFS=' ' read -r suffix rest; do
        if [ "$presuffix" != "$suffix" ]; then
            echo -n "$buff" | sort -n -r -k1 
            presuffix=$suffix
            buff=''
        fi
        buff+="$rest"$'\n'
    done
    printf "%s" "$buff" | sort -n -r -k1
} |
cut -d' ' -f2-
  1. sed的获得是行首的1d,因此该行以d 1 ... rest of the line开头。因此,该行前面有两列新列-一列我们要按字母顺序排序,另一列我们要按数字排序。
  2. 然后我们使用第一列(字母)进行排序。
  3. 然后,我使用缓冲区将流分成单独的部分,并使用第二个字段(数字)对每个部分进行逆序排序(第一个字段已在while read中删除,因此现在是第一列)。
  4. 然后cut -d' ' -f2-删除了第一列(数字)。
  5. 由于while read部分的原因,这会很慢,但是我没有更好的主意。

@edit:

确实受@shelter评论影响的另一种解决方案。

sed 's/\(.*\) \([0-9]*\)\([a-zA-Z]*\)/\3 \2 \1 \2\3/' |
while IFS=' ' read -r suffix num rest; do
    echo "$(printf "%d * 256 + (256 - %d)\n" "'$suffix" "$num" | bc)" "$rest"
done |
sort -r -n |
cut -d' ' -f2-

假设排序列中只有一个字符后缀(1d1e1h19d)并且排序列中的数字小于256(幻数,可能会增加),我们可以将字符转换为ascii数。

然后,我们可以将ASCII数乘以256,然后将其添加到已排序的列中。该数字减去256,因为我们希望在每个块中使用数字进行反向排序(7d首先,然后是1d)。然后,我们仅对其进行数字排序。

我们可以选择使用printf "(256 - %d) + %d"然后进行逆序数字排序,不同之处仅在于两个字段相等时(例如files-1662192679files-247106034的情况)。

幻数256应该大于排序列中最大的数字,也应该大于排序列中字符的最大ascii表示。可能可以将其扩展为处理已排序的列中的多个字符。