Supposons que j'ai la liste des chemins d'accès des fichiers stockés dans un tableau
filearray=("dir1/0010.pdf" "dir2/0003.pdf" "dir3/0040.pdf" )
Je veux trier les éléments du tableau en fonction des noms de base des noms de fichiers, dans l'ordre numérique
sortedfilearray=("dir2/0003.pdf" "dir1/0010.pdf" "dir3/0040.pdf")
Comment puis je faire ça?
Je peux seulement trier leurs parties de nom de base:
basenames=()
for file in "${filearray[@]}"
do
filename=${file##*/}
basenames+=(${filename%.*})
done
sortedbasenamearr=($(printf '%s\n' "${basenames[@]}" | sort -n))
Je pense à
- la création d'un tableau associatif dont les clés sont les noms de base et les valeurs sont les noms de chemin, donc l'accès aux chemins se fait toujours via les noms de base.
- créer un autre tableau pour les noms de base uniquement et appliquer
sort
au tableau de noms de base.
Merci.
dir1
dir2
sont juste constitués, et ce sont en fait des chemins d'accès arbitraires.Réponses:
Contrairement à ksh ou zsh, bash n'a pas de support intégré pour le tri des tableaux ou des listes de chaînes arbitraires. Il peut trier des globes ou la sortie de
alias
ouset
outypeset
(bien que ces 3 derniers ne soient pas dans l'ordre de tri des paramètres régionaux de l'utilisateur), mais cela ne peut pas être utilisé pratiquement ici.Il n'y a rien dans le POSIX toolchest qui peut facilement trier des listes arbitraires de chaînes non plus¹ (
sort
trie les lignes, donc seules les séquences courtes (LINE_MAX étant souvent plus courtes que PATH_MAX) de caractères autres que NUL et retour à la ligne, tandis que les chemins de fichiers sont des séquences d'octets non vides autres que 0).Ainsi, bien que vous puissiez implémenter votre propre algorithme de tri dans
awk
(en utilisant l'<
opérateur de comparaison de chaînes) ou mêmebash
(en utilisant[[ < ]]
), pour des chemins arbitraires dansbash
, de manière portable, le plus simple peut être de recourir àperl
:Avec
bash4.4+
, vous pourriez faire:Cela donne un
strcmp()
ordre semblable à. Pour un ordre basé sur les règles de classement des paramètres régionaux comme dans globs ou la sortie dels
, ajoutez un-Mlocale
argument àperl
. Pour le tri numérique (plus comme GNUsort -g
car il prend en charge des nombres comme+3
,1.2e-5
et non pas des milliers de séparateurs, mais pas des hexadimaux), utilisez à la<=>
place decmp
(et encore-Mlocale
pour que la décimale de l'utilisateur soit respectée comme pour lasort
commande).Vous serez limité par la taille maximale des arguments d'une commande. Pour éviter cela, vous pouvez passer la liste des fichiers
perl
sur son stdin au lieu d'arguments via:Avec les anciennes versions de
bash
, vous pourriez utiliser unewhile IFS= read -rd ''
boucle à la place dereadarray -d ''
ou obtenir laperl
sortie de la liste des chemins correctement cités afin de pouvoir la transmettreeval "array=($(perl...))"
.Avec
zsh
, vous pouvez simuler une expansion globale pour laquelle vous pouvez définir un ordre de tri:Avec,
reply=($filearray)
nous forçons en fait l'expansion de glob (qui était initialement juste/
) pour être les éléments du tableau. Ensuite, nous définissons l'ordre de tri en fonction de la queue du nom de fichier.Pour un
strcmp()
ordre similaire, fixez les paramètres régionaux à C. Pour le tri numérique (similaire à GNUsort -V
, passort -n
qui fait une différence significative lors de la comparaison1.4
et1.23
(dans les paramètres régionaux où se.
trouve la marque décimale) par exemple), ajoutez len
qualificatif glob.Au lieu de
oe{expression}
, vous pouvez également utiliser une fonction pour définir un ordre de tri comme:ou plus avancés comme:
(donc
a/foo2bar3.pdf
(2,3 nombres) trie aprèsb/bar1foo3.pdf
(1,3) mais avantc/baz2zzz10.pdf
(2,10)) et utilise comme:Bien sûr, ceux-ci peuvent être appliqués sur de vrais globes car c'est à cela qu'ils sont principalement destinés. Par exemple, pour une liste de
pdf
fichiers dans n'importe quel répertoire, triés par nom de base / queue:¹ Si un
strcmp()
tri basé sur est acceptable, et pour les chaînes courtes, vous pouvez transformer les chaînes en leur codage hexadécimal avecawk
avant de passer àsort
et reconvertir après le tri.la source
sort
dans GNU coreutils permet un séparateur et une clé de champ personnalisés. Vous définissez/
comme séparateur de champ et triez en fonction du deuxième champ pour trier sur le nom de base, au lieu du chemin entier.printf "%s\n" "${filearray[@]}" | sort -t/ -k2
produirala source
sort
, pas une extension GNU. Cela fonctionnera si les chemins sont tous de la même longueur.some/long/path/0011.pdf
? Pour autant que je puisse voir sur sa page de manuel,sort
ne contient aucune option pour trier par le dernier champ.Tri par gawk expression (soutenu par bash « s
readarray
):Exemple de tableau de noms de fichiers contenant des espaces blancs :
Le résultat:
Accès à un seul élément:
Cela suppose qu'aucun chemin de fichier ne contient de caractères de nouvelle ligne. Notez que le tri numérique des valeurs dans
@val_num_asc
s'applique uniquement à la partie numérique principale de la clé (aucune dans cet exemple) avec retour à la comparaison lexicale (basée surstrcmp()
, et non l'ordre de tri des paramètres régionaux) pour les liens.la source
Le tri des noms de fichiers avec des retours à la ligne dans leurs noms entraînera des problèmes à l'
sort
étape.Il génère une
/
liste délimitée avecawk
qui contient le nom de base dans la première colonne et le chemin complet comme les colonnes restantes:C'est ce qui est trié et
cut
est utilisé pour supprimer la première/
colonne délimitée. Le résultat est transformé en un nouveaubash
tableau.la source
/some/dir/
.a/x.c++ b/x.c-- c/x.c++
serait trié dans cet ordre même si trié-
avant+
parce que-
,+
et/
le poids principal de est IGNORE (donc la comparaisonx.c++/a/x.c++
avec lex.c--/b/x.c++
premier se comparexcaxc
auxcbxc
, et seulement en cas de liens les autres poids (où-
avant+
) serait pris en compte./x/
place de/
, mais cela ne résoudrait pas le cas où, dans l'environnement local C sur les systèmes basés sur ASCII,a/foo
trierait après,a/foo.txt
par exemple, car/
trie après.
.Puisque "
dir1
etdir2
sont des chemins d'accès arbitraires", nous ne pouvons pas compter sur eux comme un seul répertoire (ou le même nombre de répertoires). Nous devons donc convertir la dernière barre oblique dans les chemins d'accès en quelque chose qui ne se produit pas ailleurs dans le chemin d'accès. En supposant que le caractère@
ne se produit pas dans vos données, vous pouvez trier par nom de base comme ceci:La première
sed
commande remplace la dernière barre oblique de chaque chemin par le séparateur choisi, la seconde inverse la modification. (Par souci de simplicité, je suppose que les chemins d'accès peuvent être fournis un par ligne. S'ils sont dans une variable shell, convertissez-les d'abord au format un par ligne.)la source
cat pathnames | sed 's|\(.*\)/|\1'$'\4''|' | sort -t$'\4' -k+2nr | sed 's|'$'\4''|/|'
. (Je viens de saisir\4
de la table ascii. Apparemment "FIN DU TEXTE"?)\4
est^D
(contrôle-D). À moins que vous ne le tapiez vous-même au terminal, il s'agit d'un caractère de contrôle ordinaire. En d'autres termes, sûr à utiliser de cette façon.Solution courte (et quelque peu rapide): en ajoutant l'index du tableau aux noms de fichiers et en les triant, nous pouvons plus tard créer une version triée en fonction des indices triés.
Cette solution n'a besoin que des fonctions internes bash ainsi que du
sort
binaire, et fonctionne également avec tous les noms de fichiers qui n'incluent pas de\n
caractère de nouvelle ligne .Pour chaque fichier, nous faisons écho à son nom de base avec son index initial ajouté comme ceci:
puis envoyé
sort -n
.Ensuite, nous parcourons les lignes de sortie, extrayons l'ancien index avec l'expansion de la variable bash
${line##* }
et insérons cet élément à la fin du nouveau tableau.la source
Cela trie en ajoutant les chemins d'accès aux fichiers avec le nom de base, en les triant numériquement, puis en supprimant le nom de base du début de la chaîne:
Ce serait plus efficace si vous aviez les noms de fichiers dans une liste qui pourrait être passée directement via un canal plutôt que comme un tableau de shell, car le travail réel est effectué par la
sed | sort | sed
structure, mais cela suffit.J'ai découvert cette technique lors du codage en Perl; dans cette langue, il était connu comme une transformation schwartzienne .
Dans Bash, la transformation indiquée ici dans mon code échouera si vous avez des non-numériques dans le nom de base du fichier. En Perl, il pourrait être codé de manière beaucoup plus sûre.
la source
$@
ou à$*
partir d'arguments de ligne de commande pour exécuter un scriptPour des noms de fichiers de même profondeur.
Explication
Les informations proviennent de l'homme du tri.
L'impression de la matrice résultante
la source