Ich habe folgendes Problem. Ich habe ein Array arr
mit einigen Werten. Ich möchte jeden Wert in eine Reihe verschiedener – und bereits deklarierter – Arrays sortieren earr$j
, also arr[0]
in earr1
, arr[1]
in earr2
und allgemein arr[j-1]
in earr$j
. (Später werde ich die Elemente ähnlicher arr
s als nächste Elemente der Ziel- s anhängen lassen earr$j
.) Ich habe dies mit dem folgenden Codeausschnitt versucht (der Teil eines größeren Codestücks ist):
for j in $(seq 1 $number_of_elements); do earr$j+=(${arr[j-1]}); done
Mir wurde gesagt (siehe meinen Beitrag „https://unix.stackexchange.com/questions/675454/for-loop-and-appending-over-list-of-arrays“), dass es so aussieht, als ob ich beabsichtige, ein 2D-Array zu erstellen (was Bash nicht unterstützt). Ich betone, dass dies nicht meine Absicht ist, unabhängig davon, was das Ergebnis meiner schlechten Verwendung der Bash-Syntax vermuten lässt. Ich poste dies erneut, da mein alter Beitrag das Problem wirklich schlecht beschrieben hat.
Antwort1
Um die Frage wörtlich zu beantworten: Das ist normalerweise ein Job für eval
:
for i in "${!arr[@]}"; do
eval '
earr'"$i"'+=( "${arr[i]}" )
'
done
eval
ist gefährlich, aber sicher, wenn es richtig verwendet wird. Ein guter Ansatz, um das Fehlerrisiko zu begrenzen, besteht darin, alles in einfache Anführungszeichen zu setzen, außer die Teile, die unbedingt erweitert werden müssen, und sicherzustellen, dass der Teil, der nicht in einfachen Anführungszeichen steht (hier $i
steht er stattdessen in doppelten Anführungszeichen und wird zum Inhalt der i
Variable erweitert), vollständig unter Ihrer Kontrolle ist. In diesem Fall wissen wir, dass $i
er nur Ziffern enthält, also sind das keine zufälligen Daten, die eval
als Shell-Code ausgewertet würden (zum Vergleich: ${arr[i]}
Sie möchten die einfachen Anführungszeichen auf keinen Fall weglassen).
Ich verstehe immer noch nicht, warum Sie sagen, dass 2D-Arrays nicht geeignet sind. In ksh93
( bash
hat den Großteil der Syntax von ksh93 kopiert, aber keine mehrdimensionalen Arrays kopiert) würden Sie Folgendes tun:
for i in "${!arr[@]}"; do
earr[i]+=( "${arr[i]}" )
done
Sofern es keinen besonderen Grund für die Verwendung einer Shell gibt, stimme ich mit @cas überein, dass es sich anhört, als wäre die Verwendung einer richtigen Programmiersprache wie perl
oder besser python
.
Antwort2
Hier ist ein Beispiel, wie Sie das von Ihnen beschriebene mit Perl und einer Hash-of-Array-of-Arrays (HoAoA)-Datenstruktur erreichen.
Zum besseren Verständnis sind die folgenden Manpages hilfreich: perldata
(Perl-Datentypen), perldsc
(Datenstrukturen), perllol
(lol = Listen von Listen), perlref
(Referenzen) und perlreftut
(Tutorial für Referenzen). Sie können mit dem Befehl auch Details zu bestimmten Perl-Funktionen erhalten perldoc
– z. B. perldoc -f opendir
oder perldoc -f grep
.
Beachten Sie, dass die im Skript verwendeten sort
und grep
eingebaute Perl-Funktionen sind. Sie sindnichtdie sort
und grep
Kommandozeilentools... Sie können diese von Perl aus aufrufen, wenn Sie möchten (mit Backticks oder qx
Anführungszeichen, oder der system()
Funktion, oder mit der open()
Funktion zum Öffnen einer Pipe und auf verschiedene andere Arten). perldoc
Weitere Informationen zu all diesen und weiteren finden Sie unter.
$ 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";
}
Hinweis: Das Obige ist für die Verarbeitung einfacher, durch Kommas getrennter Datendateien gedacht. Für echte CSV-Dateien mit all ihren Eigenheiten und Komplikationen (wie mehrzeilige Felder in Anführungszeichen mit eingebetteten Kommas) verwenden Sie dasText::CSVModul. Dies ist ein Bibliotheksmodul eines Drittanbieters, das nicht in der Perl-Kerndistribution enthalten ist. Unter Debian und verwandten Distributionen können Sie dies mit installieren apt-get install libtext-csv-perl libtext-csv-xs-perl
. Andere Distributionen haben wahrscheinlich ähnliche Paketnamen. Oder Sie können es mit installieren cpan
(ein Tool zum Installieren und Verwalten von Bibliotheksmodulen, das im Perl-Kern enthalten ist).
Beachten Sie auch: das obige Skript verwendet dieDatenmüll. Dies ist ein Drittanbietermodul, das zum Dumpen strukturierter Daten nützlich ist. Leider ist es nicht Teil der Perl-Kernbibliothek. Unter Debian usw. apt-get install libdata-dump-perl
haben andere Distributionen einen ähnlichen Paketnamen. Und als letzte Möglichkeit können Sie es mit installieren cpan
.
Wie auch immer, mit der folgenden Ordnerstruktur und den folgenden data.txt-Dateien:
$ 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
Das Ausführen des Skripts HoHoA.pl erzeugt die folgende Ausgabe:
$ ./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