У меня следующая проблема. У меня есть массив arr
с некоторыми значениями. Я хочу отсортировать каждое значение в набор различных - и уже объявленных - массивов earr$j
, то есть arr[0]
в earr1
, arr[1]
в earr2
и, в общем случае, arr[j-1]
в earr$j
. (Позже я добавлю элементы похожих arr
s в качестве следующих элементов целевого earr$j
s). Я попытался сделать это с помощью следующего фрагмента кода (который является частью более крупного фрагмента кода):
for j in $(seq 1 $number_of_elements); do earr$j+=(${arr[j-1]}); done
Мне сказали (см. мой пост "https://unix.stackexchange.com/questions/675454/for-loop-and-appending-over-list-of-arrays"), что похоже, что я собираюсь создать двумерный массив (который Bash не поддерживает). Я подчеркиваю, что это не мое намерение, независимо от того, что может означать результат моего плохого использования синтаксиса Bash. Я перепечатываю это, поскольку мой старый пост действительно плохо описывал проблему.
решение1
Если отвечать на вопрос буквально, то это обычно работа для eval
:
for i in "${!arr[@]}"; do
eval '
earr'"$i"'+=( "${arr[i]}" )
'
done
eval
опасно, но безопасно, если используется правильно. Хороший подход для ограничения риска ошибки — заключить все в одинарные кавычки, за исключением тех частей, которые определенно должны подвергнуться некоторому расширению, и убедиться, что часть, которая не находится в одинарных кавычках (здесь $i
она находится в двойных кавычках и будет расширена до содержимого переменной i
), полностью находится под вашим контролем. В этом случае мы знаем, что $i
будет содержать только цифры, так что это не случайные данные, которые eval
можно было бы оценить как шелл-код (сравните с ${arr[i]}
тем, что вы определенно не хотите оставлять за одинарными кавычками).
Я все еще не понимаю, почему вы говорите, что двумерные массивы не подходят. В ksh93
( bash
скопировал большую часть синтаксиса из ksh93, но не копировал многомерные массивы), вы бы сделали:
for i in "${!arr[@]}"; do
earr[i]+=( "${arr[i]}" )
done
В любом случае, если нет особой причины, по которой вам нужно использовать оболочку, я согласен с @cas, что, по-моему, лучше использовать правильный язык программирования, такой как perl
или python
.
решение2
Вот пример того, как сделать то, что вы описали, с помощью Perl и структуры данных Hash-of-Array-of-Arrays (HoAoA).
Чтобы помочь в понимании этого, будут полезны следующие страницы man: perldata
(типы данных perl), perldsc
(структуры данных), perllol
(lol = списки списков), perlref
(ссылки) и perlreftut
(руководство по ссылкам). Вы также можете получить подробную информацию о конкретных функциях perl с помощью perldoc
команды - например, perldoc -f opendir
или perldoc -f grep
.
Обратите внимание, что sort
и grep
используемые в скрипте являются встроенными функциями Perl. Онинетsort
и инструменты grep
командной строки... вы можете вызывать их из perl, если хотите (с обратными кавычками или qx
кавычками, или system()
функцией, или функцией open()
открытия канала, и несколькими другими способами). Используйте perldoc
для получения подробной информации обо всем этом и многом другом.
$ cat HoAoA.pl
#!/usr/bin/perl
use strict;
use Data::Dump qw(dd);
# $h is a ref to Hash-of-Array-ofArrays (HoAoA).
#
# This will be a data structure with the directory names
# (Folder1, Folder2, Folder3) as the hash keys of the top-level
# hash. Each element of that hash will be an array where the
# indexes are the line numbers of the data.txt files in each
# of those directories. The data in these second-level arrays
# will be an array containing the three values in each line of
# data.txt: $$h{directory}[line number][element]
my $h;
# get the directory name from the first command line arg, default to ./
my $dir = shift // './';
# get a list of subdirectories that contain 'data.txt',
# excluding . and ..
opendir(my $dh, "$dir") || die "Couldn't open directory $dir: $!\n";
my @dirs = sort grep { $_ !~ /^\.+$/ && -d $_ && -f "$_/data.txt" } readdir($dh);
closedir($dh);
dd \@dirs; # Data::Dump's dd function is great for showing what's in an array
print "\n";
foreach my $d (@dirs) {
my $f = "$d/data.txt";
open(my $fh,"<",$f) || die "Couldn't open file $f: $!\n";
my $lc=0; # line counter
while(<$fh>) {
chomp; # strip trailing newline char at end-of-line
my @row = split /\s*,\s*/; # assume simple comma-delimited values
push @{ $$h{$d}[$lc++] }, @row;
}
close($fh);
}
# dd is even better for showing complex structured data
dd $h;
print "\n";
# show how to access individual elements, e.g. by changing the
# zeroth element of line 0 of 'Folder1' to 999.
$$h{'Folder1'}[0][0] = 999;
dd $h;
print "\n";
# show how to print the data without using Data::Dump
# a loop like this can also be used to process the data.
# You could also process the data in the main loop above
# as the data is being read in.
foreach my $d (sort keys %{ $h }) { # `foreach my $d (@dirs)` would work too
print "$d/data.txt:\n";
foreach my $lc (keys @{ $$h{$d} }) {
print " line $lc: ", join("\t",@{ $$h{$d}[$lc] }), "\n";
}
print "\n";
}
Примечание: вышеприведенный код написан для обработки простых файлов данных, разделенных запятыми. Для настоящего CSV со всеми его причудами и сложностями (вроде многострочных полей в двойных кавычках со встроенными запятыми) используйтеТекст::CSVmodule. Это сторонний модуль библиотеки, который не входит в основной дистрибутив perl. В Debian и связанных дистрибутивах вы можете установить его с помощью apt-get install libtext-csv-perl libtext-csv-xs-perl
. Другие дистрибутивы, вероятно, имеют похожие имена пакетов. Или вы можете установить его с помощью cpan
(инструмент для установки и управления библиотечными модулями, который ВКЛЮЧЕН в основной дистрибутив perl).
Также обратите внимание: приведенный выше скрипт используетВывод данных. Это сторонний модуль, который полезен для дампа структурированных данных. К сожалению, он не включен в состав основной библиотеки perl. В Debian и т. д apt-get install libdata-dump-perl
. Другие дистрибутивы будут иметь похожее имя пакета. И, в крайнем случае, вы можете установить его с помощью cpan
.
В любом случае, со следующей структурой папок и файлами data.txt:
$ tail */data.txt
==> Folder1/data.txt <==
1,2,3
4,5,6
7,8,9
==> Folder2/data.txt <==
7,8,9
4,5,6
1,2,3
==> Folder3/data.txt <==
9,8,7
6,5,4
3,2,1
Запуск скрипта HoHoA.pl выводит следующий результат:
$ ./HoAoA.pl
["Folder1", "Folder2", "Folder3"]
{
Folder1 => [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
Folder2 => [[7, 8, 9], [4, 5, 6], [1, 2, 3]],
Folder3 => [[9, 8, 7], [6, 5, 4], [3, 2, 1]],
}
{
Folder1 => [[999, 2, 3], [4, 5, 6], [7, 8, 9]],
Folder2 => [[7, 8, 9], [4, 5, 6], [1, 2, 3]],
Folder3 => [[9, 8, 7], [6, 5, 4], [3, 2, 1]],
}
Folder1/data.txt:
line 0: 999 2 3
line 1: 4 5 6
line 2: 7 8 9
Folder2/data.txt:
line 0: 7 8 9
line 1: 4 5 6
line 2: 1 2 3
Folder3/data.txt:
line 0: 9 8 7
line 1: 6 5 4
line 2: 3 2 1