J'ai une ligne (ou plusieurs lignes) de nombres délimités par un caractère arbitraire. Quels outils UNIX puis-je utiliser pour trier les éléments de chaque ligne numériquement, en conservant le délimiteur?
Les exemples comprennent:
- liste des numéros; entrée
10 50 23 42
:; trié:10 23 42 50
- Adresse IP; entrée
10.1.200.42
:; trié:1.10.42.200
- CSV; entrée
1,100,330,42
:; trié:1,42,100,330
- délimité par des tuyaux; entrée
400|500|404
:; trié:400|404|500
Étant donné que le délimiteur est arbitraire, n'hésitez pas à fournir (ou étendre) une réponse en utilisant un délimiteur à un caractère de votre choix.
sort
numeric-data
Jeff Schaller
la source
la source
cut
prend en charge les délimiteurs arbitraires avec son-d
option.4,325 comma 55 comma 42,430
ne se produira pas, ni1.5 period 4.2
).Réponses:
Vous pouvez y parvenir avec:
remplacez les points
.
par votre délimiteur.ajoutez
-u
à lasort
commande ci-dessus pour supprimer les doublons.ou avec
gawk
( GNUawk
), nous pouvons traiter de nombreuses lignes tandis que celles ci-dessus peuvent également être étendues:remplacer
*
comme séparateur de champSEP='*'
par votre délimiteur .Remarques:
Vous devrez peut-être utiliser l'
-g, --general-numeric-sort
option desort
au lieu de-n, --numeric-sort
pour gérer n'importe quelle classe de nombres (entier, flottant, scientifique, hexadécimal, etc.).En
awk
aucun besoin de changement, il s'en chargera toujours.la source
En utilisant
perl
il y a une version évidente; diviser les données, les trier, les rejoindre à nouveau.Le délimiteur doit être répertorié deux fois (une fois dans
split
et une fois dansjoin
)par exemple pour un
,
Donc
Puisque le
split
est une expression régulière, le personnage peut avoir besoin de citer:En utilisant les options
-a
et-F
, il est possible de supprimer le fractionnement. Avec la-p
boucle, comme précédemment et définissez les résultats sur$_
, qui s'imprimeront automatiquement:la source
-l
option au lieu d'utiliserchomp
. Cela ajoute également la nouvelle ligne lors de l'impression. Voir aussi-a
(avec-F
) pour la partie de fendage.-l
et-F
, c'est encore plus agréable:perl -F'/\./' -le 'print join(".", sort {$a <=> $b} @F)'
-l
option; J'avais raté ça!-F
indicateur à l'origine car il ne fonctionne pas correctement dans toutes les versions (par exemple, votre ligne dans CentOS 7 - perl 5.16.3 - renvoie une sortie vierge, bien que cela fonctionne très bien sur Debian 9). Mais combiné avec-p
cela donne un résultat légèrement plus petit, j'ai donc ajouté cela comme alternative à la réponse. montrant comment-F
les utiliser. Merci!-a
et-n
options automatiquement quand-F
est utilisé et-n
quand-a
est utilisé ... alors changez simplement-le
en-lane
Utiliser Python et une idée similaire à celle de la réponse de Stephen Harris :
Donc quelque chose comme:
Malheureusement, devoir faire les E / S manuellement rend cela beaucoup moins élégant que la version Perl.
la source
Script bash:
Exemple:
Basé sur
Fractionner la chaîne en un tableau dans Bash
Comment trier un tableau dans Bash
Rejoindre les éléments d'un tableau?
la source
coquille
Le chargement d'une langue de niveau supérieur prend du temps.
Pour quelques lignes, le shell lui-même peut être une solution.
Nous pouvons utiliser la commande externe
sort
et la commandetr
. L'une est assez efficace pour trier les lignes et l'autre est efficace pour convertir un délimiteur en nouvelles lignes:Ce besoin bash en raison de l'utilisation de
<<<
seulement. S'il est remplacé par un here-doc, la solution est valable pour posix.Ceci est en mesure de trier les champs avec des onglets, des espaces ou des caractères glob shell (
*
,?
,[
). Pas de nouvelles lignes car chaque ligne est en cours de tri.Changez
<<<"$2"
pour<"$2"
traiter les noms de fichiers et appelez-le comme:Le délimiteur est le même pour tout le fichier. Si c'est une limitation, elle pourrait être améliorée.
Cependant, un fichier avec seulement 6000 lignes prend 15 secondes pour être traité. Vraiment, le shell n'est pas le meilleur outil pour traiter les fichiers.
Awk
Pour plus de quelques lignes (plus de quelques 10), il est préférable d'utiliser un vrai langage de programmation. Une solution awk pourrait être:
Ce qui ne prend que 0,2 seconde pour le même fichier de 6000 lignes mentionné ci-dessus.
Comprenez que les
<"$2"
fichiers for peuvent être modifiés<<<"$2"
pour les lignes dans les variables shell.Perl
La solution la plus rapide est Perl.
Si vous voulez trier un changement de fichier
<<<"$a"
simplement"$a"
et ajouter des-i
options à perl pour rendre l'édition de fichier "en place":la source
Utilisation
sed
pour trier les octets d'une adresse IPsed
n'a pas desort
fonction intégrée, mais si vos données sont suffisamment limitées dans la plage (comme avec les adresses IP), vous pouvez générer un script sed qui implémente manuellement un tri à bulles simple . Le mécanisme de base consiste à rechercher les numéros adjacents qui sont dans le désordre. Si les numéros sont en panne, échangez-les.Le
sed
script lui-même contient deux commandes de recherche et d'échange pour chaque paire de nombres dans le désordre: une pour les deux premières paires d'octets (forçant un délimiteur de fin à être présent pour marquer la fin du troisième octet), et un deuxième pour la troisième paire d'octets (fin avec EOL). Si des échanges se produisent, le programme se branche en haut du script, à la recherche de numéros qui ne sont pas en ordre. Sinon, il se ferme.Le script généré est en partie:
Cette approche code en dur la période en tant que délimiteur, ce qui doit être échappé, sinon elle serait "spéciale" à la syntaxe des expressions régulières (autorisant n'importe quel caractère).
Pour générer un tel script sed, cette boucle fera:
Redirigez la sortie de ce script vers un autre fichier, par exemple
sort-ips.sed
.Un échantillon pourrait alors ressembler à ceci:
La variation suivante sur le script de génération utilise les marqueurs de limite de mot
\<
et\>
pour se débarrasser de la nécessité de la deuxième substitution. Cela réduit également la taille du script généré de 1,3 Mo à un peu moins de 900 Ko et réduit considérablement le temps d'exécution desed
lui - même (à environ 50% -75% de l'original, selon l'sed
implémentation utilisée):la source
sed
est ridicule, c'est pourquoi c'est un défi intéressant.Voici un bash qui devine le délimiteur par lui-même:
Ce n'est peut-être pas très efficace ni propre mais ça marche.
Utilisez comme
bash my_script.sh "00/00/18/29838/2"
.Renvoie une erreur lorsque le même délimiteur n'est pas utilisé de manière cohérente ou lorsque deux délimiteurs ou plus se succèdent.
Si le délimiteur utilisé est un caractère spécial, il est échappé (sinon
sed
renvoie une erreur).la source
Cette réponse est basée sur une mauvaise compréhension du Q., mais dans certains cas, elle se trouve être correcte de toute façon. Si l'entrée est des nombres entièrement naturels et ne comporte qu'un seul délimiteur par ligne (comme avec les exemples de données dans le Q.), cela fonctionne correctement. Il traitera également les fichiers avec des lignes qui ont chacune leur propre délimiteur, ce qui est un peu plus que ce qui était demandé.
Cette fonction shell
read
s à partir de l'entrée standard, utilise la substitution de paramètres POSIX pour trouver le délimiteur spécifique sur chaque ligne, (stockée dans$d
), et utilisetr
pour remplacer$d
par une nouvelle ligne\n
etsort
s les données de cette ligne, puis restaure les délimiteurs d'origine de chaque ligne:Appliqué aux données fournies dans le PO :
Production:
la source
Pour les délimiteurs arbitraires:
Sur une entrée comme:
Il donne:
la source
Cela devrait gérer tout délimiteur non numérique (0-9). Exemple:
Production:
la source
Avec
perl
:Avec
ruby
, qui est quelque peu similaire àperl
Commande personnalisée et passage juste la chaîne de délimitation (pas regex). Fonctionne si l'entrée contient également des données flottantes
Commande personnalisée pour
perl
Pour en savoir plus - J'avais déjà cette liste pratique de monoplaces perl / ruby
la source
Ce qui suit est une variation de la réponse de Jeff dans le sens où il génère un
sed
script qui fera le tri par bulles, mais est suffisamment différent pour justifier sa propre réponse.La différence est qu'au lieu de générer O (n ^ 2) expressions régulières de base, cela génère O (n) expressions régulières étendues. Le script résultant fera environ 15 Ko. Le temps d'exécution du
sed
script est en fractions de seconde (il faut un peu plus de temps pour générer le script).Il est limité au tri des entiers positifs délimités par des points, mais il n'est pas limité à la taille des entiers (augmentez simplement
255
dans la boucle principale) ou au nombre d'entiers. Le délimiteur peut être modifié en modifiantdelim='.'
le code.J'ai fait ma tête pour obtenir les bonnes expressions régulières, alors je vais laisser la description des détails pour un autre jour.
Le script ressemblera à ceci:
L'idée derrière les expressions régulières générées est de faire correspondre les motifs pour les nombres inférieurs à chaque entier; ces deux numéros seraient hors service et sont donc échangés. Les expressions régulières sont regroupées en plusieurs options OR. Portez une attention particulière aux plages ajoutées à chaque élément, parfois elles le sont
{0}
, ce qui signifie que l'élément immédiatement précédent doit être omis de la recherche. Les options d'expression régulière, de gauche à droite, correspondent à des nombres plus petits que le nombre donné par:Pour donner un exemple, prenez
101
(avec des espaces supplémentaires pour la lisibilité):Ici, la première alternance permet les nombres 100 à 100; la deuxième alternance permet de 0 à 99.
Un autre exemple est
154
:Ici, la première option permet 150 à 153; le second permet 100 à 149 et le dernier permet 0 à 99.
Tester quatre fois en boucle:
Production:
la source
Division de l'entrée en plusieurs lignes
À l'aide de
tr
, vous pouvez diviser l'entrée à l'aide d'un délimiteur arbitraire en plusieurs lignes.Cette entrée peut ensuite être parcourue
sort
(en utilisant-n
si l'entrée est numérique).Si vous souhaitez conserver le délimiteur dans la sortie, vous pouvez ensuite utiliser à
tr
nouveau pour rajouter le délimiteur.p.ex. en utilisant l'espace comme délimiteur
cat input.txt | tr " " "\n" | sort -n | tr "\n" " "
entrée:
1 2 4 1 4 32 18 3
sortie:1 1 2 3 4 4 18 32
la source