web-dev-qa-db-fra.com

Tri sur le dernier champ d'une ligne

Quel est le moyen le plus simple de trier une liste de lignes, en triant sur le dernier champ de chaque ligne? Chaque ligne peut avoir un nombre variable de champs.

Quelque chose comme

sort -k -1

c’est ce que je veux, mais sort (1) ne prend pas de nombres négatifs pour sélectionner des champs à la fin au lieu du début.

J'aimerais aussi pouvoir choisir le délimiteur de champ.

Edit: Pour ajouter une spécificité à la question: La liste que je veux trier est une liste de chemins. Les noms de chemin peuvent être de profondeur arbitraire d'où le nombre variable de champs. Je veux trier sur le composant du nom de fichier.

Ces informations supplémentaires peuvent modifier la manière dont la ligne est manipulée pour extraire le dernier champ (le nom de base (1) peut être utilisé), mais ne modifie pas les exigences de tri.

par exemple.

/a/b/c/10-foo
/a/b/c/20-bar
/a/b/c/50-baz
/a/d/30-bob
/a/e/f/g/h/01-do-this-first
/a/e/f/g/h/99-local

Je veux que cette liste soit triée sur les noms de fichiers, qui commencent tous par des chiffres indiquant l'ordre dans lequel les fichiers doivent être lus.

J'ai ajouté ma réponse ci-dessous, c'est comment je le fais actuellement. J'espérais qu'il existait un moyen plus simple - peut-être un utilitaire de tri différent - peut-être sans avoir à manipuler les données.

33
camh

Voici une ligne de commande Perl (notez que votre shell peut vous demander d'échapper au $s):

Perl -e "print sort {(split '/', $a)[-1] <=> (split '/', $b)[-1]} <>"

Dirigez simplement la liste dans celle-ci ou, si la liste est dans un fichier, placez le nom du fichier à la fin de la ligne de commande.

Notez que ce script ne modifie pas réellement les données, vous n'avez donc pas besoin de faire attention à quel délimiteur vous utilisez.

Voici un exemple de sortie:

> Perl -e "print sort {(split '/', $ a) [- 1] <=> (split '/', $ b) [- 1]}" files.txt 
/a/e/f/g/h/01-do-this-first 
/a/b/c/10-foo 
/a/b/c/20-bar 
/a/d/30-bob 
/a/b/c/50-baz 
/a/e/f/g/h/99-local 
12
Gabe
awk '{print $NF,$0}' file | sort | cut -f2- -d' '

En gros, cette commande fait:

  1. Répéter le dernier champ au début, séparé par un espace (OFS par défaut)
  2. Trier, résoudre les noms de fichiers dupliqués en utilisant le chemin complet ($ 0) pour le tri
  3. Couper le premier champ répété, f2- signifie du deuxième au dernier champ
12
François Rousseau

quelque chose comme ça

awk '{print $NF"|"$0}' file | sort -t"|" -k1 | awk -F"|" '{print $NF }'
6
ghostdog74

Une ligne dans Perl pour inverser l'ordre des champs d'une ligne:

Perl -lne 'print join " ", reverse split / /'

Vous pouvez l'utiliser une fois, diriger la sortie pour trier, puis la renvoyer et vous obtiendrez ce que vous voulez. Vous pouvez remplacer / / par / +/ afin de réduire les espaces. Et vous êtes bien entendu libre d'utiliser l'expression régulière souhaitée pour diviser les lignes.

3
integer

Je pense que la seule solution serait d'utiliser awk:

  1. Placez le dernier champ à l’avant avec awk.
  2. Trier les lignes.
  3. Remettez le premier champ à la fin. 
2
Thevs
#!/usr/bin/Ruby

f = ARGF.read
lines = f.lines

broken = lines.map {|l| l.split(/:/) }

sorted = broken.sort {|a, b|
    a[-1] <=> b[-1]
}

fixed = sorted.map {|s| s.join(":") }

puts fixed

Si toutes les réponses impliquent Perl ou awk, vous pourriez aussi bien résoudre le problème dans son langage de script. (Incidemment, j'ai d'abord essayé Perl et je me suis rapidement rappelé que je n'aimais pas les listes de listes de Perl. J'aimerais bien voir la version d'un gourou de Perl.)

0
sarnold

Remplacez le dernier séparateur de la ligne par un autre séparateur ne figurant pas dans la liste, effectuez un tri dans le deuxième champ à l'aide de cet autre séparateur en tant que délimiteur sort (1), puis annulez la modification du délimiteur.

delim=/
new_delim=" "
cat $list \
| sed "s|\(.*\)$delim|\1$new_delim|" \
| sort -t"$new_delim" -k 2,2 \
| sed "s|$new_delim|$delim|"

Le problème est de savoir quel délimiteur à utiliser ne figure pas dans la liste. Vous pouvez effectuer plusieurs passages sur la liste, puis grep pour une succession de délimiteurs potentiels, mais tout cela est plutôt désagréable - en particulier lorsque le concept de "trier sur le dernier champ d'une ligne" est si simplement exprimé, alors que la solution ne l'est pas.

Edit: Un délimiteur sûr à utiliser pour $ new_delim est NUL car il ne peut pas apparaître dans les noms de fichiers, mais je ne sais pas comment mettre un caractère NUL dans un script Bourne/POSIX Shell (pas bash) et si sort et sed gèrent correctement il.

0
camh

Voici une version de Python Oneliner, notez qu’elle suppose que le champ est un entier, vous pouvez le changer si nécessaire.

echo file.txt | python3 -c 'import sys; list(map(sys.stdout.write, sorted(sys.stdin, key=lambda x: int(x.rsplit(" ", 1)[-1]))))'
0
Pykler

Je veux que cette liste soit triée sur les noms de fichiers, qui commencent tous par des nombres Indiquant l’ordre de lecture des fichiers.

find . | sed 's#.*/##' | sort

sed remplace toutes les parties de la liste des résultats qui se terminent par des barres obliques. les noms de fichiers sont ce qui reste, et vous triez sur cela.

0
commonpike