![在 bash 中对两个数组的数字范围进行排序](https://linux22.com/image/217934/%E5%9C%A8%20bash%20%E4%B8%AD%E5%AF%B9%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E6%95%B0%E5%AD%97%E8%8C%83%E5%9B%B4%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F.png)
我有两个数组,它们的元素数量与我要操作的元素数量相同。它们从文件读入两个数组(奇数行进入数组 1,偶数行进入数组 2):
arr1=("1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5")
arr2=("4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6")
该数据是指两个数组中同一位置的季数和集数。因此数组 1 ( arr1
) 是季节,数组 2 ( arr2
) 是剧集,它们按元素编号对齐。所以${arr1[0]}
对应于${arr2[0]}
等
我想做的就是对它们进行排序,以便它们首先按季节排序,然后按集排序。所以原始数组(带有注释哪个项目是哪个元素):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
arr1=("1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5")
arr2=("4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
变成:
2 9 1 4 10 12 3 12 13 14 5 16 6 7 8 15
arr1=("1" "1" "1" "2" "2" "3" "3" "3" "3" "3" "4" "5" "7" "7" "7" "7")
arr2=("1" "2" "4" "5" "9" "2" "3" "6" "8" "9" "7" "6" "1" "2" "3" "4")
2 9 1 4 10 12 3 12 13 14 5 16 6 7 8 15
可能的想法:
i
对于中的每一项${arr1[@]}
,将 的相应元素写入${arr2[n]}
文件中。然后该文件就可以sort
在其上运行。
n="0"
for i in "${arr1[@]}"; do
echo "${arr2[${n}]}" >> "${i}.txt"
(( n++ ))
done
但我想尽量避免涉及磁盘写入(sort
如果不需要的话)。
将数据排序到某种单独的数组中?每个季节都有自己的数组,然后可以使用类似的方法对数组进行排序
sort -n "${season1Arr[@]}"
- 但我不知道这是如何完成的。改变数据的处理方式?我无法更改输入文件,但可以更改其处理方式。也许不是根据偶数/奇数行号将行读入两个数组,而是可以通过其他方式管理它们?
为了兼容性,我试图使其尽可能保持纯 bash,但我知道很可能需要使用外部程序。欣赏任何想法。
答案1
这是我在评论中讨论的一个示例,使用 perl 和关联数组(哈希),但使用版本排序而不是我提到的自然排序(简单的数字或字母数字排序,例如 1.10 之前) 1.2,这对于“series.episode”来说显然是错误的):
#!/usr/bin/perl
use strict;
use Sort::Versions;
my %data;
my ($key, $series, $episode);
while (<>) {
chomp; # remove trailing newline from input
if ($. % 2 == 1) {
$series = $_;
} else {
$episode = $_;
$key = "$series.$episode";
$data{$key} = 1;
};
}
print join(", ", sort { versioncmp($a, $b) } keys %data), "\n";
这可以缩短一点 - 真正需要的唯一变量是%data
哈希值和$series
.我使用了$key
和$episode
来使输入值的使用方式变得显而易见。顺便说一句,$_
是默认的输入/值/迭代器。它在 perl 中有很多用途,如果未提供另一个变量,许多函数和语法元素都会使用它 - 在此脚本中,它是循环读取的当前行的值while (<>)
。查看man perlvar
并搜索“常规变量”。
#!/usr/bin/perl
use strict;
use Sort::Versions;
my (%data, $series);
while (<>) {
chomp;
if ($. % 2 == 1) {
$series = $_;
} else {
$data{"$series.$_"} = 1;
};
}
print join(", ", sort { versioncmp($a, $b) } keys %data), "\n";
示例运行(两个版本产生相同的输出):
$ ./sort-series-episode.pl input.txt
1.1, 1.2, 1.4, 1.15, 2.5, 2.9, 3.2, 3.3, 3.6, 3.8, 3.9, 4.7, 5.6, 7.1, 7.2, 7.3, 7.4
注:排序::版本module 不是核心 perl 模块,需要通过cpan
发行版包单独安装 - 例如在 Debian 中,apt-get install libsort-versions-perl
答案2
虽然perl
对于此类任务来说要好得多,但几乎可以在纯 bash 中完成它(对于外部来说是安全的sort
):
#!/bin/bash
arr1=( "1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5" )
arr2=( "4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6" )
# make an array with strings from both initial arrays:
arr_length=${#arr1[@]}
for ((i=0; i< $arr_length; i++)); do
arr_combined[$i]="${arr1[$i]}!${arr2[$i]}"
done
# sort the combined strings
arr_sorted=( $(printf "%s\n" "${arr_combined[@]}" | sort) )
# split the elements of sorted array back into two arrays
for pair in ${arr_sorted[@]} ; do
arr1n+="${pair%!*} "
arr2n+="${pair#*!} "
done
# print the results
printf "%s\n" "${arr1n[@]}"
printf "%s\n" "${arr2n[@]}"
答案3
如果您首先对原始文件中的数字进行排序,然后填充数组,则可以使生活变得更加轻松。我假设你的原始文件包含这样的内容:
$ cat file1
1 1 3 2 4 7 7 7 1 2 3 3 3 3 7 5
4 1 3 5 7 1 2 3 2 9 2 6 8 9 4 6
所以这个命令:
transpose file1 | sort | transpose
会产生你想要的:
1 1 1 2 2 3 3 3 3 3 4 5 7 7 7 7
1 2 4 5 9 2 3 6 8 9 7 6 1 2 3 4
转置函数非常简单。你可以自己写。这是一个例子:
transpose ()
{
awk '{ for (i=1; i<=NF; i++) a[i]= (i in a?a[i] OFS :"") $i; }
END{ for (i=1; i<=NF; i++) print a[i] }' $1
}
答案4
根据您的描述,以下可能是原始输入数据:
1
4
1
1
3
3
2
5
4
7
7
1
7
2
7
3
1
2
2
9
3
2
3
6
3
8
3
9
7
4
5
6
要以您想要的方式处理此问题:
paste - - <file | sort -k 1,1n -k 2,2n
首先将数据转换为两个制表符分隔的列,第一列是季节,第二列是剧集。
这些行在第一列上按数字排序,任何具有相同第一列的行都在第二列上按数字排序。
使用上面显示的输入,将产生:
1 1
1 2
1 4
2 5
2 9
3 2
3 3
3 6
3 8
3 9
4 7
5 6
7 1
7 2
7 3
7 4