Angenommen, ich habe eine Liste mit Pfadnamen von Dateien, die in einem Array gespeichert sind
filearray=("dir1/0010.pdf" "dir2/0003.pdf" "dir3/0040.pdf" )
Ich möchte die Elemente im Array nach den Basisnamen der Dateinamen in numerischer Reihenfolge sortieren
sortedfilearray=("dir2/0003.pdf" "dir1/0010.pdf" "dir3/0040.pdf")
Wie kann ich das machen?
Ich kann nur ihre Basisnamen-Teile sortieren:
basenames=()
for file in "${filearray[@]}"
do
filename=${file##*/}
basenames+=(${filename%.*})
done
sortedbasenamearr=($(printf '%s\n' "${basenames[@]}" | sort -n))
Ich denke an
- Erstellen eines assoziativen Arrays, dessen Schlüssel die Basisnamen und deren Werte die Pfadnamen sind, sodass der Zugriff auf die Pfadnamen immer über Basisnamen erfolgt.
- Erstellen eines anderen Arrays nur für Basisnamen und Anwenden
sort
auf das Basisnamen-Array.
Vielen Dank.
dir1
dir2
sind nur erfunden, und sie sind eigentlich willkürliche Pfadnamen.Antworten:
Im Gegensatz zu ksh oder zsh bietet bash keine integrierte Unterstützung für das Sortieren von Arrays oder Listen beliebiger Zeichenfolgen. Es kann Globs oder die Ausgabe von
alias
oderset
oder sortierentypeset
(obwohl die letzten 3 nicht in der Sortierreihenfolge des Benutzers enthalten sind), aber das kann hier praktisch nicht verwendet werden.Es gibt nichts in der POSIX-Toolchest, das beliebige Listen von Zeichenfolgen leicht sortieren kann¹ (
sort
sortiert Zeilen, also nur kurze (LINE_MAX ist oft kürzer als PATH_MAX) Folgen von Zeichen außer NUL und Newline, während Dateipfade nicht leere Folgen von anderen Bytes sind als 0).Während Sie also Ihren eigenen Sortieralgorithmus in
awk
(mithilfe des<
Zeichenfolgenvergleichsoperators) oder sogarbash
(mithilfe[[ < ]]
) implementieren können , ist es für beliebige Pfadebash
möglicherweise am einfachsten, auf Folgendes zurückzugreifenperl
:Mit
bash4.4+
könnten Sie tun:Das gibt eine
strcmp()
ähnliche Reihenfolge. Fügen Sie für eine Reihenfolge, die auf den Sortierregeln des Gebietsschemas wie in Globs oder der Ausgabe von basiertls
, ein-Mlocale
Argument hinzuperl
. Für numerische Sortierung (mehr wie GNUsort -g
wie es unterstützt Zahlen wie+3
,1.2e-5
und nicht die Tausendertrennzeichen , wenn auch nicht hexadimals), verwenden<=>
stattcmp
(und wieder-Mlocale
für das Dezimalzeichen des Benutzers wie für die geehrt werdensort
Befehl).Sie sind durch die maximale Größe der Argumente für einen Befehl begrenzt. Um dies zu vermeiden, können Sie die Liste der Dateien
perl
auf ihrem Standard anstatt über Argumente übergeben:Bei älteren Versionen von
bash
können Sie einewhile IFS= read -rd ''
Schleife anstelle von verwendenreadarray -d ''
oderperl
die Liste der ordnungsgemäß zitierten Pfade ausgeben, an die Sie sie übergeben könneneval "array=($(perl...))"
.Mit
zsh
können Sie eine Glob-Erweiterung vortäuschen, für die Sie eine Sortierreihenfolge definieren können:Mit
reply=($filearray)
erzwingen wir tatsächlich die Glob-Erweiterung (die anfangs nur war/
), um die Elemente des Arrays zu sein. Dann definieren wir die Sortierreihenfolge basierend auf dem Ende des Dateinamens.strcmp()
Legen Sie für eine ähnliche Reihenfolge das Gebietsschema auf C fest. Fügen Sie für eine numerische Sortierung (ähnlich wie bei GNUsort -V
,sort -n
die beim Vergleich keinen signifikanten Unterschied macht,1.4
und1.23
(in Gebietsschemas, in denen.
die Dezimalstelle steht) beispielsweise) dasn
Glob-Qualifikationsmerkmal hinzu.Stattdessen
oe{expression}
können Sie auch eine Funktion verwenden, um eine Sortierreihenfolge wie folgt zu definieren:oder fortgeschrittenere wie:
(also
a/foo2bar3.pdf
(2,3 Zahlen) sortiert nachb/bar1foo3.pdf
(1,3) aber vorc/baz2zzz10.pdf
(2,10)) und verwendet als:Natürlich können diese auf echte Globs angewendet werden, da sie in erster Linie dafür vorgesehen sind. Zum Beispiel für eine Liste von
pdf
Dateien in einem beliebigen Verzeichnis, sortiert nach Basisname / Schwanz:¹ Wenn eine
strcmp()
sortierte Sortierung akzeptabel ist und für kurze Zeichenfolgen, können Sie die Zeichenfolgen in ihre Hex-Codierung umwandeln,awk
bevor Sie sie übergebensort
und nach dem Sortieren wieder umwandeln.quelle
sort
In GNU ermöglicht Coreutils ein benutzerdefiniertes Feldtrennzeichen und einen benutzerdefinierten Schlüssel. Sie legen/
als Feldtrennzeichen fest und sortieren nach dem zweiten Feld, um nach dem Basisnamen anstatt nach dem gesamten Pfad zu sortieren.printf "%s\n" "${filearray[@]}" | sort -t/ -k2
wird herstellenquelle
sort
keine GNU-Erweiterung. Dies funktioniert, wenn alle Pfade gleich lang sind.some/long/path/0011.pdf
? Soweit ich auf der Manpage sehen kann,sort
enthält es keine Option zum Sortieren nach dem letzten Feld.Sortieren mit gawk Ausdruck (unterstützt von bash s
readarray
):Beispielarray von Dateinamen mit Leerzeichen :
Die Ausgabe:
Zugriff auf ein einzelnes Element:
Dies setzt voraus, dass kein Dateipfad Zeilenumbruchzeichen enthält. Beachten Sie, dass die numerische Sortierung der Werte in
@val_num_asc
nur für den führenden numerischen Teil des Schlüssels gilt (in diesem Beispiel keine), wobei auf den lexikalischen Vergleich (basierend aufstrcmp()
der Sortierreihenfolge des Gebietsschemas) für Verknüpfungen zurückgegriffen wird.quelle
Das Sortieren von Dateinamen mit Zeilenumbrüchen führt zu Problemen beim
sort
Schritt.Es wird eine
/
begrenzte Liste generiertawk
, die den Basisnamen in der ersten Spalte und den vollständigen Pfad als verbleibende Spalten enthält:Dies ist das, was sortiert wird und
cut
verwendet wird, um die erste/
begrenzte Spalte zu entfernen . Das Ergebnis wird in ein neuesbash
Array umgewandelt.quelle
/some/dir/
.a/x.c++ b/x.c-- c/x.c++
würde sogar in dieser Reihenfolge sortiert werden , obwohl-
Art vor ,+
weil-
,+
und/
‚s Primärgewicht IGNORE (so den Vergleichx.c++/a/x.c++
gegenx.c--/b/x.c++
vergleicht zuerstxcaxc
gegenxcbxc
und nur im Fall der Bindungen würde die anderen Gewichte (wobei-
kommt vorher+
) würde in Betracht gezogen werden./x/
statt/
, aber das wäre nicht der Fall befassen , in denen in der C - locale auf ASCII - basierten Systemen,a/foo
würde sortiert nacha/foo.txt
etwa weil/
Sorten nach.
.Da "
dir1
unddir2
beliebige Pfadnamen sind", können wir nicht darauf zählen, dass sie aus einem einzelnen Verzeichnis (oder aus der gleichen Anzahl von Verzeichnissen) bestehen. Wir müssen also den letzten Schrägstrich in den Pfadnamen in etwas konvertieren , das an keiner anderen Stelle im Pfadnamen vorkommt. Angenommen, das Zeichen@
kommt in Ihren Daten nicht vor, können Sie nach dem Basisnamen wie folgt sortieren:Der erste
sed
Befehl ersetzt den letzten Schrägstrich in jedem Pfadnamen durch das ausgewählte Trennzeichen, der zweite kehrt die Änderung um. (Der Einfachheit halber gehe ich davon aus, dass die Pfadnamen einmal pro Zeile geliefert werden können. Wenn sie sich in einer Shell-Variablen befinden, konvertieren Sie sie zuerst in ein Format pro Zeile.)quelle
cat pathnames | sed 's|\(.*\)/|\1'$'\4''|' | sort -t$'\4' -k+2nr | sed 's|'$'\4''|/|'
. (Ich habe gerade\4
vom ASCII-Tisch genommen. Anscheinend "END OF TEXT"?)\4
ist^D
(Kontrolle-D). Sofern Sie es nicht selbst am Terminal eingeben, handelt es sich um ein gewöhnliches Steuerzeichen. Mit anderen Worten, sicher auf diese Weise zu verwenden.Kurze (und etwas schnelle) Lösung: Durch Anhängen des Array-Index an die Dateinamen und Sortieren dieser können wir später eine sortierte Version basierend auf den sortierten Angaben erstellen.
Diese Lösung benötigt nur die Bash-Buildins sowie die
sort
Binärdatei und funktioniert auch mit allen Dateinamen, die kein Zeilenumbruchzeichen enthalten\n
.Für jede Datei wird der Basisname mit dem folgenden Index wiedergegeben:
und dann durchgeschickt
sort -n
.Anschließend durchlaufen wir die Ausgabezeilen, extrahieren den alten Index mit der Erweiterung der Bash-Variablen
${line##* }
und fügen dieses Element am Ende des neuen Arrays ein.quelle
Dies wird sortiert, indem den Pfadnamen der Datei der Basisname vorangestellt, dieser numerisch sortiert und dann der Basisname von der Vorderseite der Zeichenfolge entfernt wird:
Es wäre effizienter, wenn Sie die Dateinamen in einer Liste hätten, die direkt durch eine Pipe und nicht als Shell-Array übergeben werden könnte, da die eigentliche Arbeit von der
sed | sort | sed
Struktur erledigt wird , aber dies reicht aus.Diese Technik habe ich zum ersten Mal beim Codieren in Perl kennengelernt. in dieser Sprache war es als Schwartzianische Transformation bekannt .
In Bash schlägt die hier in meinem Code angegebene Transformation fehl, wenn der Basisname der Datei nicht numerisch ist. In Perl könnte es viel sicherer codiert werden.
quelle
$@
oder$*
von Befehlszeilenargumenten zum Ausführen eines Skripts erhaltenFür Dateinamen gleicher Tiefe.
Erläuterung
Informationen werden vom Mann der Sorte genommen.
Der resultierende Array-Druck
quelle