Comment trouver la ligne avec le moins de caractères


22

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 -mpour 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 4forcar 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.


5
Et s'il y a plusieurs lignes d'une longueur de 4? Doivent-ils également être imprimés?
chaos

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.
Matthew D. Scholefield

Réponses:


13

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:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

Explication

  • perl -lne: -nsignifie "lire le fichier d'entrée ligne par ligne", -lentraîne la suppression des nouvelles lignes de fin de chaque ligne d'entrée et l'ajout d'une nouvelle ligne à chaque printappel; et -eest le script qui sera appliqué à chaque ligne.
  • $m//=$_: défini $msur la ligne actuelle ( $_) sauf si $mdéfini. L' //=opérateur est disponible depuis Perl 5.10.0.
  • $m=$_ if length()<length($m): si la longueur de la valeur actuelle de $mest 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. Le if $.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:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

Explication

  • @K=sort{length($a) <=> length($b)}<>: <>voici un tableau dont les éléments sont les lignes du fichier. Le sortles 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

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 

1
Ajouter -Cpour 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).
Stéphane Chazelas

17

Avec sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT

Celui-là est mon préféré ici, je n'ai jamais pensé à SQL ...
chaos

2
C'est le statut de golf de code intelligent
shadowtalker

2
Cela va-t-il lire le fichier entier en mémoire et / ou créer une deuxième copie sur disque? Si c'est le cas, c'est intelligent mais inefficace.
John Kugelman prend en charge Monica

1
@JohnKugelman Cela absorbera probablement les 4 lignes entières dans une base de données temporaire uniquement en mémoire (c'est ce qui straceindique). 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 comme sqlite3 $(mktemp)et toutes les données seront écrites sur le disque.
FloHimself

J'obtiens les erreurs suivantes: "" "xaa: 8146:" caractère "" "et" "" xaa: 8825: 1 colonnes attendues mais trouvé 2 - extras ignorés "" ". Le fichier se compose de documents json 1 par ligne .
Ahmedov

17

Voici une variante d'une awksolution pour imprimer la première ligne minimale trouvée:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

qui peut simplement être étendu par une condition pour imprimer toutes les lignes minimales:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'

12

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'))"


que dit l'étain?
mikeserv

@mikeserve: il dit, "imprime le minimum de sys.stdin, en utilisant len ​​comme clé" ;-)
Steve Jessop

1
ahh. rien sur la taille binaire, le fluage des dépendances ou le temps d'exécution, alors?
mikeserv

2
@mikeserv: non, les petits caractères ne sont pas sur l'étain. C'est sur un feuillet d'information dans un classeur verrouillé, dans une cave, derrière une porte marquée "méfiez-vous du léopard".
Steve Jessop

Gotcha - ainsi de suite.
mikeserv

10

J'aime toujours les solutions avec des scripts shell purs (pas d'exec!).

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

Remarque :

Il y a un problème avec les octets NUL en entrée. Donc, printf "ab\0\0\ncd\n" | bash this_scriptimprime abau lieu de cd.


C'est vraiment le plus pur. Cependant, la maladresse des tests bashme convaincrait de donner un résultat intermédiaire à la sortplace.
orion

2
Avez-vous essayé de mettre votre banc d'essai sans banc d'essai? solution contre d'autres qui le font? Voici une comparaison des différences de performances entre exec! et pas d'exec! des solutions à un problème similaire. L'exécution d'un processus séparé est très rarement avantageuse quand elle araignée - sous des formes comme 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.
mikeserv

1
@DigitalTrauma - une chaîne contiguë étendue de chiffres n'est pas plus ou moins exempte des conditions qui rendent la citation de shell nécessaire que toute autre chaîne développée. $IFSn'est pas discriminatoire en termes de chiffres - même s'il n'y en a pas dans une $IFSvaleur 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.
mikeserv


1
Merci à tous pour les commentaires et les votes positifs (une partie du représentant devrait aller à @cuonglm pour avoir corrigé ma réponse). En général, je ne recommande pas aux autres de pratiquer quotidiennement les scripts shell purs, mais cette compétence peut être très utile dans certaines conditions extrêmes où rien d'autre qu'un lien statique /bin/shn'est disponible. Cela m'est arrivé plusieurs fois avec des hôtes SunOS4 /usrperdus ou .soendommagé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.
yaegashi

9

Voici une zshsolution pure (il imprime toutes les lignes avec la longueur minimale, à partir de file):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

Exemple d'entrée:

seven/7
4for
8 eight?
five!
four

La sortie est:

4for
four

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:

IFS=$'\n';

Jusqu'ici tout va bien, maintenant la partie difficile. printutilise le -ldrapeau pour imprimer le résultat séparé par des sauts de ligne au lieu d'espaces.

Maintenant, nous commençons à l'intérieur:

$(<file)

Le fichier est lu ligne par ligne et traité comme un tableau. Ensuite:

${(o@)...//?/?}

Le odrapeau 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:

${~...[1]}

Nous prenons le premier élément du tableau [1], qui est le plus court, dans votre cas, c'est maintenant ????.

${(M)$(<file):#...}

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:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}

8
tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field

... et le gagnant est ... ligne 2, semble-t-il.

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?

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:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line

Cela imprime ...

2
4for

Un autre, juste sed:

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile

La syntaxe est conforme aux normes - mais ce n'est pas une garantie que n'importe quel ancien sedgé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:

\(.\)\(\n.*\)*

Qui correspond à différentes chaînes de différentes manières. Par exemple:

string1\nstring2\nstring3

... correspond à sin \1et à ''la chaîne nulle in \2.

1\nstring2\nstring3

... est associé à 1in \1and \nstring2\nstring3in\2

\nstring2\nstring3

... correspond à \nin \1et à ''la chaîne nulle in \2. Cela serait problématique s'il y avait une chance qu'un \newline se produise à la tête de l'espace de motif - mais les commandes /^\n/D, et //!gsont 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.

\nstring2
string1

... correspond \net sencore \1et les deux obtiennent la ''chaîne nulle \2. Les lignes vides ne correspondent pas du tout.

Lorsque le motif est appliqué globalement, 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:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g

... si tous s'appliquent (pas successivement) à la chaîne suivante ...

string1\nstring2

... le transformera en ...

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2

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 ...

^               \nremembered line$

... et ensuite la nligne 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:

^   \n               \nremembered line\nnew$

Enfin, une substitution est appliquée à cet espace de motif:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//

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 Dsupprimé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 ...

new

... 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 ...

remembered line\nnew

... va commencer le cycle suivant à la place, et la première substitution en supprimera la chaîne ...

\nnew

...à 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:

4for

Mais, sérieusement, utilisez tr.



Avez-vous même besoin d'insérer des numéros de ligne? Ma lecture de l'OP est que seule la ligne la plus courte est requise, et pas nécessairement le numéro de ligne de cette ligne. Je suppose qu'il n'y a aucun mal à le montrer pour être complet.
Digital Trauma

@DigitalTrauma - non, probablement pas. Mais ce n'est guère très utile sans eux - et ils sont si bon marché. Lorsque je travaille un flux, je préfère toujours inclure un moyen de reproduire à l'identique l'entrée d'origine dans la sortie - les numéros de ligne rendent cela possible ici. Par exemple, pour transformer les résultats de la première autour du pipeline: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. Et le second est une simple question d'inclure un autre sed --expressionscript à la fin.
mikeserv

@DigitalTrauma - oh, et dans le premier exemple , les numéros de ligne n'affectent le comportement « comme départage lorsque les lignes de même longueur se produisent en entrée - de sorte que la ligne se produisant plus tôt flotte toujours vers le haut dans ce cas. sort
mikeserv

7

Essayer:

awk '{ print length, $0 }' testfile | sort -n | cut -d" " -f2- | head -1

L'idée est d'utiliser awkpour imprimer la longueur de chaque ligne en premier. Cela apparaîtra comme:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

Ensuite, utilisez le nombre de caractères pour trier les lignes sort, cutpour vous débarrasser du nombre et headpour 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 )


+1 pour la logique mais cela ne fonctionnera pas dans tous les cas. Si les deux lignes ont le même nombre de caractères et qui est minimum. Il ne vous donnera que la première ligne rencontrée à cause dehead -1
Thushi

Pour obtenir la ligne la plus longue, il est un peu plus efficace d'inverser le tri que de l'utiliser tail(comme headpeut quitter dès que son travail est terminé, sans lire le reste de son entrée).
Toby Speight

@Thushi En utilisant un peu de regex, après avoir imprimé les numéros de ligne, tout sauf les lignes portant le même numéro que la ligne 1 pourrait être supprimé, produisant ainsi toutes les lignes les plus courtes.
Matthew D. Scholefield

5

Avec POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file

Cela ne fonctionnera pas si plusieurs lignes ont le même nombre de caractères et qui est également minimum.
Thushi

@Thushi: Il signalera la première ligne minimale.
cuonglm

Ouais, mais ce n'est pas une sortie correcte, non? Même les autres lignes ont le nombre minimum de caractères.
Thushi

1
@Thushi: Cela ne mentionne pas dans les exigences OP, en attendant la mise à jour de OP.
cuonglm

3
Je ne pense pas que Lc'était la meilleure lettre pour choisir de nommer la variable: D Quelque chose comme mincela rendrait les choses plus claires
fedorqui

3

Emprunter certaines des idées de @ mikeserv:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

Le premier sedfait ce qui suit:

  • h enregistre la ligne d'origine dans le tampon de maintien
  • Remplacez chaque caractère de la ligne par : - ceci permet d'éliminer tout danger d'injection de code
  • Remplacez toute la ligne par expr length "whole line" - c'est une expression shell qui peut être évaluée
  • La commande e pours 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 motif
  • la finale sremplace la nouvelle ligne par un onglet

Le nombre de caractères est maintenant un nombre au début de chaque ligne, donc sort -ntrie par longueur de ligne.

La finale sedsupprime ensuite toutes les lignes sauf la première (la plus courte) et la longueur de la ligne et imprime le résultat.


1
@mikeserv Oui, je pense que exprc'est plus agréable ici. Oui, efera 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.
Digital Trauma

J'opterais habituellement pour xargs exprpersonnellement - mais, à part éviter une coque intermédiaire, c'est probablement plus une chose stylistique. Je l'aime quand même.
mikeserv

3

Il m'est venu à l'esprit que tout cela est possible en une seule sedexpression. Ce n'est pas joli:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

Décomposer cela:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

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:

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

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


@mikeserv Depuis man sedOS 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 \nle regex et le remplacement, tandis que BSD ne le permet que \ndans le regex et non dans le remplacement.
Digital Trauma

Emprunter \nde l'espace de motif est une bonne idée et fonctionnerait dans la deuxième s///expression, mais l' s/.*/&\n&/expression insère un \ndans 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.
Digital Trauma

1
Ces nouvelles lignes sont des délimiteurs de paramètres - vous en avez besoin pour délimiter toute commande qui pourrait accepter un paramètre arbitraire - du moins, c'est ce que dit la spécification. La spécification indique également qu'un sedscript 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 :\ label2et ainsi de suite. Comme vous le faites de 1htoute façon, vous pouvez simplement passer à une logique basée sur x;Hpour 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 avec D.
mikeserv

@mikeserv Nice. Oui, j'ai inséré la nouvelle ligne dont j'avais besoin en faisant la Gpremière et en changeant l' s///expression. Le fractionnement en utilisant -epermet à tout cela d'aller sur une (longue) ligne sans nouvelle ligne littérale.
Digital Trauma

L' \néchappement est également spécifié pour sedle 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.
mikeserv

2

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.

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for

Vous pouvez utiliser push @{$lines{+length}};et print @{$lines{+min keys %lines}};pour moins de frappe :)
cuonglm

Si je jouais au golf, je n'aurais pas non plus utilisé le nom de variable "lines":perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
glenn jackman

+1 pour une version non-golfée (qui fonctionne!), Mais uniquement pour la variante tout imprimer . - perldevient un peu noueux pour ceux d'entre nous qui ne sont pas à la hauteur de perlla nature cryptique de. BTW. le golfé sayimprime une ligne vierge parasite à la fin de la sortie.
Peter.O

2

Pour obtenir uniquement la première ligne la plus courte:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

Pour obtenir toutes les peluches les plus courtes, passez simplement {p;q}àp


Une autre méthode (quelque peu inhabituelle) consiste à sortfaire 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 1qui s'étend sur toute la ligne
Chaque 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):

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

ce qui équivaut à:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1

2

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:

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt

2

Et le tri?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-

1

Avec GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Lisez chaque ligne dans un tableau indexé par longueur de ligne.

  • Défini PROCINFO["sorted_in"]pour @ind_num_ascforcer le balayage du tableau à être ordonné par l'index du tableau, trié numériquement

  • Le réglage de PROCINFOla 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 quittez

Cela a l'inconvénient d'être un nlogncertain temps, certaines des autres approches sont nà temps


1

Méthode des outils shell de niveau intermédiaire, sans sedou awk:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1

Ce serait bien de ne pas avoir besoin d'une $fvariable; J'ai une idée qui pourrait être possible en utilisant d'une teemanière ou d'une autre ...
agc
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.