J'écris un script shell, en utilisant toutes les commandes UNIX générales. Je dois récupérer la ligne qui a le moins de caractères (espace inclus). Il peut y avoir jusqu'à environ 20 lignes.
Je sais que je peux utiliser head -$L | tail -1 | wc -m
pour trouver le nombre de caractères de la ligne L. Le problème est que la seule méthode à laquelle je peux penser, en utilisant cela, serait d'écrire manuellement un désordre d'instructions if, en comparant les valeurs.
Exemples de données:
seven/7
4for
8 eight?
five!
Reviendrait 4for
car cette ligne avait le moins de caractères.
Dans mon cas, si plusieurs lignes ont la longueur la plus courte, une seule doit être retournée. Peu importe lequel est sélectionné, tant qu'il est de la longueur minimale. Mais je ne vois pas le mal de montrer les deux façons aux autres utilisateurs dans d'autres situations.
la source
Réponses:
Une façon Perl. Notez que s'il existe plusieurs lignes de la même longueur, la plus courte, cette approche n'en imprimera qu'une:
Explication
perl -lne
:-n
signifie "lire le fichier d'entrée ligne par ligne",-l
entraîne la suppression des nouvelles lignes de fin de chaque ligne d'entrée et l'ajout d'une nouvelle ligne à chaqueprint
appel; et-e
est le script qui sera appliqué à chaque ligne.$m//=$_
: défini$m
sur la ligne actuelle ($_
) sauf si$m
défini. L'//=
opérateur est disponible depuis Perl 5.10.0.$m=$_ if length()<length($m)
: si la longueur de la valeur actuelle de$m
est supérieure à la longueur de la ligne actuelle, enregistrez la ligne actuelle ($_
) sous$m
.END{print $m if $.}
: une fois que toutes les lignes ont été traitées, imprimer la valeur actuelle de$m
, la ligne la plus courte. Leif $.
garantit que cela ne se produit que lorsque le numéro de ligne ($.
) est défini, évitant d'imprimer une ligne vide pour une entrée vierge.Alternativement, puisque votre fichier est suffisamment petit pour tenir en mémoire, vous pouvez faire:
Explication
@K=sort{length($a) <=> length($b)}<>
:<>
voici un tableau dont les éléments sont les lignes du fichier. Lesort
les triera en fonction de leur longueur et les lignes triées seront enregistrées en tant que tableau@K
.print "$K[0]"
: affiche le premier élément du tableau@K
: la ligne la plus courte.Si vous souhaitez imprimer toutes les lignes les plus courtes, vous pouvez utiliser
la source
-C
pour mesurer la longueur en termes de nombre de caractères au lieu de nombre d'octets. Dans un environnement local UTF-8,$$
a moins d'octets que€
(2 vs 3), mais plus de caractères (2 vs 1).Avec
sqlite3
:la source
strace
indique). Si vous devez travailler avec des fichiers très volumineux (et que votre système ne change pas), vous pouvez le forcer en ajoutant simplement un nom de fichier commesqlite3 $(mktemp)
et toutes les données seront écrites sur le disque.Voici une variante d'une
awk
solution pour imprimer la première ligne minimale trouvée:qui peut simplement être étendu par une condition pour imprimer toutes les lignes minimales:
la source
Python est assez concis et le code fait ce qu'il dit sur l'étain:
python -c "import sys; print min(sys.stdin, key=len),"
Je reconnais que la virgule finale est obscure. Il empêche la déclaration d'impression d'ajouter un saut de ligne supplémentaire. De plus, vous pouvez écrire ceci en Python 3 prenant en charge 0 lignes comme:
python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"
la source
J'aime toujours les solutions avec des scripts shell purs (pas d'exec!).
Remarque :
Il y a un problème avec les octets NUL en entrée. Donc,
printf "ab\0\0\ncd\n" | bash this_script
imprimeab
au lieu decd
.la source
bash
me convaincrait de donner un résultat intermédiaire à lasort
place.var=$(get data)
parce qu'elle restreint le flux de données à un seul contexte - mais lorsque vous déplacez des données dans un pipeline - dans un flux - chaque exécutable appliqué est généralement utile - car il permet une spécialisation application de programmes modulaires uniquement lorsque cela est nécessaire.$IFS
n'est pas discriminatoire en termes de chiffres - même s'il n'y en a pas dans une$IFS
valeur par défaut , bien que de nombreux shells acceptent une configuration d'environnement prédéfinie pour$IFS
- et ce n'est donc pas un défaut particulièrement fiable./bin/sh
n'est disponible. Cela m'est arrivé plusieurs fois avec des hôtes SunOS4/usr
perdus ou.so
endommagés, et maintenant à l'ère Linux moderne, je rencontre encore occasionnellement des situations similaires avec des systèmes embarqués ou des systèmes qui échouent au démarrage. BusyBox est l'une des grandes choses que nous avons récemment acquises.Voici une
zsh
solution pure (il imprime toutes les lignes avec la longueur minimale, à partir defile
):Exemple d'entrée:
La sortie est:
Je pense qu'il a besoin d'une courte explication :-)
Tout d'abord, nous définissons le séparateur de champ interne sur la nouvelle ligne:
Jusqu'ici tout va bien, maintenant la partie difficile.
print
utilise le-l
drapeau pour imprimer le résultat séparé par des sauts de ligne au lieu d'espaces.Maintenant, nous commençons à l'intérieur:
Le fichier est lu ligne par ligne et traité comme un tableau. Ensuite:
Le
o
drapeau indique que le résultat doit être ordonné dans l'ordre croissant, le@
moyen de traiter le résultat comme un tableau aussi. La partie derrière (//?/?
) est une substitution et remplace tous les caractères par un?
. À présent:Nous prenons le premier élément du tableau
[1]
, qui est le plus court, dans votre cas, c'est maintenant????
.La correspondance est effectuée séparément sur chaque élément du tableau et les éléments du tableau sans correspondance sont supprimés (
M
). Chaque élément qui correspond????
(4 caractères) reste dans le tableau. Les éléments restants sont donc ceux qui ont 4 caractères (les plus courts).Edit: Si vous n'avez besoin que d'une des lignes les plus courtes, cette version modifiée imprime la première:
la source
... et le gagnant est ... ligne 2, semble-t-il.
Mais le problème avec cela est que chaque ligne doit plus que doubler pour qu'elle fonctionne - donc LINE_MAX est effectivement réduit de moitié. La raison en est qu'il utilise - quoi, une base 1? - pour représenter la longueur de la ligne. Une approche similaire - et peut-être plus ordonnée - pourrait être de compresser ces informations en flux. La première idée dans ce sens qui me vient à l'esprit est que je devrais le faire
unexpand
:Cela imprime ...
Un autre, juste
sed
:La syntaxe est conforme aux normes - mais ce n'est pas une garantie que n'importe quel ancien
sed
gérera\(reference-group\)\{counts\}
correctement - beaucoup ne le font pas.Il applique essentiellement le même regexp à l'entrée à plusieurs reprises - ce qui peut être très bénéfique quand il est temps de les compiler. Ce schéma est:
Qui correspond à différentes chaînes de différentes manières. Par exemple:
... correspond à
s
in\1
et à''
la chaîne nulle in\2
.... est associé à
1
in\1
and\nstring2\nstring3
in\2
... correspond à
\n
in\1
et à''
la chaîne nulle in\2
. Cela serait problématique s'il y avait une chance qu'un\n
ewline se produise à la tête de l'espace de motif - mais les commandes/^\n/D
, et//!g
sont utilisées pour empêcher cela. J'ai utilisé,[^\n]
mais d'autres besoins pour ce petit script ont rendu la portabilité préoccupante et je n'étais pas satisfait des nombreuses façons dont il est souvent mal interprété. De plus,.
c'est plus rapide.... correspond
\n
ets
encore\1
et les deux obtiennent la''
chaîne nulle\2
. Les lignes vides ne correspondent pas du tout.Lorsque le motif est appliqué
g
lobalement, les deux biais - à la fois le biais standard le plus à gauche et le biais de la ligne droite le moins à droite\n
- sont contrebalancés pour effectuer un saut. Quelques exemples:... si tous s'appliquent (pas successivement) à la chaîne suivante ...
... le transformera en ...
Fondamentalement, j'utilise l'expression rationnelle pour toujours gérer uniquement la première ligne dans n'importe quel espace de modèle auquel je l'applique. Cela me permet de jongler avec deux versions différentes d'une ligne conservée la plus courte jusqu'à présent et de la ligne la plus récente sans avoir recours à des boucles de test - chaque substitution appliquée gère tout l'espace de motif en même temps.
Les différentes versions sont nécessaires pour les comparaisons chaîne / chaîne littérales - il doit donc y avoir une version de chaque ligne où tous les caractères sont garantis égaux. Mais bien sûr, si l'un ou l'autre devait devenir la ligne la plus courte en entrée la plus ancienne, la ligne imprimée en sortie devrait probablement être la version originale de la ligne - pas celle que j'ai désinfectée / homogénéisée pour la comparaison. Et j'ai donc besoin de deux versions de chacune.
Il est malheureux qu'une autre nécessité soit beaucoup de commutation de tampons pour les gérer - mais au moins aucun tampon ne dépasse jamais plus que les quatre lignes nécessaires pour rester à jour - et donc ce n'est peut-être pas terrible.
Quoi qu'il en soit, pour chaque cycle, la première chose qui se produit est une transformation sur la ligne mémorisée - car la seule copie réellement enregistrée est l'original littéral - en ...
... et ensuite la
n
ligne d'entrée ext écrase tout ancien tampon. S'il ne contient pas au moins un seul caractère, il est effectivement ignoré. Il serait beaucoup plus facile deq
la première ligne vierge, mais mes données de test en contenaient beaucoup et je voulais gérer plusieurs paragraphes.Et donc s'il contient un caractère, sa version littérale est ajoutée à la ligne mémorisée et sa version de comparaison espacée est positionnée en tête de l'espace de motif, comme ceci:
Enfin, une substitution est appliquée à cet espace de motif:
Donc, si la nouvelle ligne peut tenir dans l'espace nécessaire pour contenir la ligne mémorisée avec au moins un caractère à épargner, les deux premières lignes sont remplacées, sinon seulement la première.
Quel que soit le résultat, la première ligne de l'espace de motif est toujours
D
supprimée à la fin du cycle avant de recommencer. Cela signifie que si la nouvelle ligne est plus courte que la dernière, la chaîne ...... est renvoyé à la première substitution dans le cycle qui sera toujours supprimée uniquement à partir du premier caractère de nouvelle ligne - et il reste donc entier. Mais si ce n'est pas le cas, la chaîne ...
... va commencer le cycle suivant à la place, et la première substitution en supprimera la chaîne ...
...à chaque fois.
Sur la toute dernière ligne, la ligne mémorisée est imprimée en sortie standard, et donc pour les données d'exemple fournies, elle imprime:
Mais, sérieusement, utilisez
tr
.la source
REINPUT | sort -t: -nk1,1 | cut -d: -f3-
. Et le second est une simple question d'inclure un autresed
--expression
script à la fin.sort
Essayer:
L'idée est d'utiliser
awk
pour imprimer la longueur de chaque ligne en premier. Cela apparaîtra comme:Ensuite, utilisez le nombre de caractères pour trier les lignes
sort
,cut
pour vous débarrasser du nombre ethead
pour conserver la première ligne (celle avec le moins de caractères). Vous pouvez bien sûr utilisertail
pour obtenir la ligne avec le plus de caractères dans ce cas.(Ceci a été adopté à partir de cette réponse )
la source
head -1
tail
(commehead
peut quitter dès que son travail est terminé, sans lire le reste de son entrée).Avec POSIX awk:
la source
L
c'était la meilleure lettre pour choisir de nommer la variable: D Quelque chose commemin
cela rendrait les choses plus clairesEmprunter certaines des idées de @ mikeserv:
Le premier
sed
fait ce qui suit:h
enregistre la ligne d'origine dans le tampon de maintien:
- ceci permet d'éliminer tout danger d'injection de codeexpr length "whole line"
- c'est une expression shell qui peut être évaluées
est une extension GNU sed pour évaluer l'espace modèle et remettre le résultat dans l'espace modèle.G
ajoute une nouvelle ligne et le contenu de l'espace d'attente (la ligne d'origine) à l'espace de motifs
remplace la nouvelle ligne par un ongletLe nombre de caractères est maintenant un nombre au début de chaque ligne, donc
sort -n
trie par longueur de ligne.La finale
sed
supprime ensuite toutes les lignes sauf la première (la plus courte) et la longueur de la ligne et imprime le résultat.la source
expr
c'est plus agréable ici. Oui,e
fera apparaître un shell pour chaque ligne. J'ai édité l'expression sed pour qu'elle remplace chaque caractère de la chaîne par un:
avant l'éval qui, je pense, devrait supprimer toute possibilité d'injection de code.xargs expr
personnellement - mais, à part éviter une coque intermédiaire, c'est probablement plus une chose stylistique. Je l'aime quand même.Il m'est venu à l'esprit que tout cela est possible en une seule
sed
expression. Ce n'est pas joli:Décomposer cela:
Le sed BSD dans OS X est un peu plus capricieux avec les nouvelles lignes. Cette version fonctionne pour les versions BSD et GNU de sed:
Notez qu'il s'agit plus d'une réponse «parce que c'est possible» que d'une tentative sérieuse de donner une réponse aux meilleures pratiques. Je suppose que cela signifie que je joue trop de code-colf
la source
man sed
OS X: "La séquence d'échappement \ n correspond à un caractère de nouvelle ligne incorporé dans l'espace modèle" . Je pense donc que GNU sed autorise\n
le regex et le remplacement, tandis que BSD ne le permet que\n
dans le regex et non dans le remplacement.\n
de l'espace de motif est une bonne idée et fonctionnerait dans la deuxièmes///
expression, mais l's/.*/&\n&/
expression insère un\n
dans l'espace de motif là où il n'y en avait pas auparavant. BSD sed semble également nécessiter des retours à la ligne littéraux après les définitions d'étiquettes et les branches.sed
script doit être un fichier texte, sauf qu'il n'a pas besoin de se terminer par une nouvelle ligne . Vous pouvez donc généralement les délimiter sous forme d'arguments séparés -sed -e :\ label -e :\ label2
et ainsi de suite. Comme vous le faites de1h
toute façon, vous pouvez simplement passer à une logique basée surx;H
pour obtenir votre nouvelle ligne - et vous pouvez couper une nouvelle ligne de tête à partir de l'espace de motif à la fin du cycle sans tirer une nouvelle ligne avecD
.G
première et en changeant l's///
expression. Le fractionnement en utilisant-e
permet à tout cela d'aller sur une (longue) ligne sans nouvelle ligne littérale.\n
échappement est également spécifié poursed
le LHS de, et je pense que c'est la déclaration textuelle de la spécification, sauf que les expressions de support POSIX sont également spécifiées de telle manière que tous les caractères perdent leur signification spéciale - (y compris explicitement\\
) - à l'intérieur d'un, à l'exception des crochets, le tiret comme séparateur de plage et le point, égal, caret, deux points pour le classement, l'équivalence, la négation et les classes.Autre solution Perl: stocker les lignes dans un hachage de tableaux, la clé de hachage étant la longueur de la ligne. Ensuite, imprimez les lignes avec la clé minimale.
la source
push @{$lines{+length}};
etprint @{$lines{+min keys %lines}};
pour moins de frappe :)perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
perl
devient un peu noueux pour ceux d'entre nous qui ne sont pas à la hauteur deperl
la nature cryptique de. BTW. le golfésay
imprime une ligne vierge parasite à la fin de la sortie.Pour obtenir uniquement la première ligne la plus courte:
Pour obtenir toutes les peluches les plus courtes, passez simplement
{p;q}
àp
Une autre méthode (quelque peu inhabituelle) consiste à
sort
faire le tri réel par longueur . Il est relativement lent, même avec des lignes courtes, et devient considérablement plus lent lorsque la longueur de la ligne augmente.Cependant, je trouve l'idée du tri par clés superposées très intéressante. Je le poste au cas où d'autres pourraient également le trouver intéressant / informatif.
Fonctionnement:
tri par variantes de longueur de la même clé -
key 1
qui s'étend sur toute la ligneChaque variante de clé successive incrémente la longueur de la clé d'un caractère, jusqu'à la longueur de la plus longue ligne du fichier (déterminée par
wc -L
)Pour obtenir uniquement la première ligne la plus courte (triée):
ce qui équivaut à:
la source
En supposant que les lignes vides ne sont pas considérées comme la ligne la plus courte et que des lignes vides peuvent exister, l'AWK pur suivant fonctionnera:
la source
Et le tri?
la source
Avec GNU awk
Lisez chaque ligne dans un tableau indexé par longueur de ligne.
Défini
PROCINFO["sorted_in"]
pour@ind_num_asc
forcer le balayage du tableau à être ordonné par l'index du tableau, trié numériquementLe réglage de
PROCINFO
la manière ci-dessus force la ligne ayant la plus petite longueur à être captée en premier dans la traversée du réseau. Donc, imprimez le premier élément du tableau et quittezCela a l'inconvénient d'être un
nlogn
certain temps, certaines des autres approches sontn
à tempsla source
Méthode des outils shell de niveau intermédiaire, sans
sed
ouawk
:la source
$f
variable; J'ai une idée qui pourrait être possible en utilisant d'unetee
manière ou d'une autre ...