So finden Sie die Zeile mit den wenigsten Zeichen

22

Ich schreibe ein Shell-Skript mit allgemeinen UNIX-Befehlen. Ich muss die Zeile mit den wenigsten Zeichen abrufen (inklusive Leerzeichen). Es können bis zu 20 Zeilen vorhanden sein.

Ich weiß, dass ich head -$L | tail -1 | wc -mdie Anzahl der Zeichen in Zeile L ermitteln kann. Das Problem ist, dass ich mir als einzige Methode vorstellen kann, manuell ein Durcheinander von if-Anweisungen zu schreiben und die Werte zu vergleichen.

Beispieldaten:

seven/7
4for
8 eight?
five!

Würde zurückkehren, 4forda diese Zeile die wenigsten Zeichen hatte.

In meinem Fall sollte eine einzelne Zeile zurückgegeben werden, wenn mehrere Zeilen die kürzeste Länge haben. Es spielt keine Rolle, welches ausgewählt wird, solange es die Mindestlänge hat. Ich sehe jedoch keinen Nachteil darin, anderen Benutzern in anderen Situationen beide Möglichkeiten aufzuzeigen.

Matthew D. Scholefield
quelle
5
Was ist, wenn es mehrere Zeilen mit einer Länge von 4 gibt? Sollen sie auch gedruckt werden?
Chaos
In meinem Fall sollte eine einzelne Zeile zurückgegeben werden, wenn mehrere Zeilen die kürzeste Länge haben. Es spielt keine Rolle, welches ausgewählt wird, solange es die Mindestlänge hat. Ich sehe jedoch keinen Nachteil darin, anderen Benutzern in anderen Situationen beide Möglichkeiten aufzuzeigen.
Matthew D. Scholefield

Antworten:

13

Ein Perl-Weg. Beachten Sie, dass dieser Ansatz nur eine Zeile ausgibt, wenn es viele Zeilen mit derselben kürzesten Länge gibt:

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

Erläuterung

  • perl -lne: -nbedeutet "Eingabedatei zeilenweise lesen" und -lbewirkt , dass nachfolgende Zeilenumbrüche aus jeder Eingabezeile entfernt werden und jedem printAufruf ein Zeilenumbruch hinzugefügt wird . und -eist das Skript, das auf jede Zeile angewendet wird.
  • $m//=$_: $mauf die aktuelle Zeile setzen ( $_), sofern nicht anders $mdefiniert. Der //=Operator ist ab Perl 5.10.0 verfügbar.
  • $m=$_ if length()<length($m): Wenn die Länge des aktuellen Werts von $mgrößer als die Länge der aktuellen Zeile ist, speichern Sie die aktuelle Zeile ( $_) als $m.
  • END{print $m if $.}: Wenn alle Zeilen verarbeitet wurden, wird der aktuelle Wert $mder kürzesten Zeile gedruckt . Die if $.sorgt dafür , dass dies geschieht nur , wenn die Zeilennummer ( $.) definiert ist, vermeidet eine Leerzeile für leeren Eingangsdruck.

Da Ihre Datei klein genug ist, um in den Arbeitsspeicher zu passen, können Sie alternativ Folgendes tun:

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

Erläuterung

  • @K=sort{length($a) <=> length($b)}<>: <>Hier ist ein Array, dessen Elemente die Zeilen der Datei sind. Die sortsortieren sie nach ihrer Länge und die sortierten Zeilen werden als Array gespeichert @K.
  • print "$K[0]": Gibt das erste Element des Arrays aus @K: die kürzeste Zeile.

Wenn Sie alle kürzesten Zeilen drucken möchten , können Sie verwenden

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 
terdon
quelle
1
Addieren -C, um die Länge in Form der Anzahl der Zeichen anstelle der Anzahl der Bytes zu messen. Hat in einem UTF-8-Gebietsschema $$weniger Bytes als (2 vs 3), aber mehr Zeichen (2 vs 1).
Stéphane Chazelas
17

Mit sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT
FloHimself
quelle
Das hier ist mein Favorit, ich habe nie an SQL gedacht ...
Chaos
2
Dies ist der Code Golf Status clever
shadowtalker 04.06.15
2
Liest dies die gesamte Datei in den Speicher und / oder erstellt es eine zweite Kopie auf der Festplatte? Wenn ja, ist es klug, aber ineffizient.
John Kugelman unterstützt Monica am
1
@JohnKugelman Dies wird wahrscheinlich die gesamten 4 Zeilen in einer temporären Speicherdatenbank aufsaugen (das ist es, was straceanzeigt). Wenn Sie mit sehr großen Dateien arbeiten müssen (und Ihr System nicht austauscht), können Sie dies erzwingen, indem Sie einfach einen Dateinamen wie anhängen, sqlite3 $(mktemp)und alle Daten werden auf die Festplatte geschrieben.
FloHimself
Ich erhalte die folgenden Fehlermeldungen: "" "xaa: 8146: nicht entkappte Zeichen" "und" "xaa: 8825: 1 erwartete Spalten, aber 2 gefunden - Extras ignoriert" ". Die Datei besteht aus json-Dokumenten 1 pro Zeile .
Ahmedov
17

Hier ist eine Variante einer awkLösung zum Drucken der ersten gefundenen Mindestzeile:

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

die einfach um eine Bedingung erweitert werden kann, um alle Mindestzeilen zu drucken:

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

Python erscheint ziemlich kurz und der Code macht das, was er verspricht:

python -c "import sys; print min(sys.stdin, key=len),"

Ich gebe zu, dass das letzte Komma dunkel ist. Es verhindert, dass die print-Anweisung einen zusätzlichen Zeilenumbruch hinzufügt. Zusätzlich können Sie dies in Python 3 schreiben, das 0 Zeilen unterstützt, wie:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"

Steve Jessop
quelle
Was sagt die Dose?
mikeserv
@ mikeserve: es heißt "druckt das Minimum von sys.stdin, wobei len als Schlüssel verwendet wird" ;-)
Steve Jessop
1
ahh. also nichts über binäre Größe, Abhängigkeitsschleich oder Ausführungszeit?
mikeserv
2
@mikeserv: Nein, das Kleingedruckte ist nicht in der Dose. Es befindet sich auf einem Beipackzettel in einem verschlossenen Aktenschrank in einem Keller hinter einer Tür mit der Aufschrift "Vorsicht vor dem Leoparden".
Steve Jessop
Gotcha - so auf dem Display.
mikeserv
10

Ich liebe immer Lösungen mit reinem Shell-Scripting (keine 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"

Hinweis :

Es liegt ein Problem mit NUL-Bytes in der Eingabe vor. Also printf "ab\0\0\ncd\n" | bash this_scriptdruckt abstatt cd.

yaegashi
quelle
Das ist wirklich das reinste. Allerdings bashwürde mich die Ungeschicklichkeit der Tests überzeugen, sortstattdessen ein Zwischenergebnis einzugeben.
Orion
2
Haben Sie versucht, Ihre no exec Bank ! Lösung gegen andere? Hier ist ein Vergleich der Leistungsunterschiede zwischen exec! und keine exec! Lösungen für ein ähnliches Problem. Das Ausführen eines separaten Prozesses ist sehr selten von Vorteil, wenn er sich spinnt - in Formularen wie, var=$(get data)weil er den Datenfluss auf einen einzelnen Kontext beschränkt -, aber wenn Sie Daten durch eine Pipeline - in einem Stream - bewegen, ist jeder angewendete Exec im Allgemeinen hilfreich - weil er spezialisiertes Arbeiten ermöglicht Anwendung modularer Programme nur bei Bedarf.
mikeserv
1
@DigitalTrauma - Eine erweiterte zusammenhängende Zeichenfolge ist nicht mehr oder weniger von den Bedingungen ausgenommen, die Shell-Anführungszeichen erforderlich machen, als jede andere erweiterte Zeichenfolge. $IFSist nicht ziffernunterscheidend - auch wenn keiner in einem Standardwert $IFSenthalten ist, obwohl viele Shells eine voreingestellte Umgebungskonfiguration für akzeptieren $IFS- und daher ist dies kein besonders zuverlässiger Standard.
mikeserv
1
Vielen Dank an alle für die Kommentare und Upvotes (einige der Mitarbeiter sollten sich an @cuonglm wenden, um meine Antwort zu korrigieren). Im Allgemeinen empfehle ich anderen nicht, täglich reines Shell-Scripting zu üben, aber diese Fertigkeit kann in einigen extremen Situationen, in denen nichts anderes als statisches Verknüpfen /bin/shverfügbar ist, als sehr nützlich erachtet werden . Es ist mir mehrmals mit SunOS4-Hosts passiert, die /usrverloren gegangen oder .sobeschädigt sind, und jetzt im modernen Linux-Zeitalter stoße ich gelegentlich immer noch auf ähnliche Situationen mit eingebetteten Systemen oder unzureichenden Boot-Systemen. BusyBox ist eines der großartigen Dinge, die wir kürzlich erworben haben.
Yaegashi
9

Hier eine reine zshLösung (es werden alle Zeilen mit der minimalen Länge gedruckt, ab file):

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

Beispiel Eingabe:

seven/7
4for
8 eight?
five!
four

Ausgabe ist:

4for
four

Ich denke, es bedarf einer kurzen Erklärung :-)


Zuerst setzen wir das interne Feldtrennzeichen auf newline:

IFS=$'\n';

So weit so gut, jetzt der schwierige Teil. printverwendet das -lFlag, um das Ergebnis durch Zeilenumbrüche anstelle von Leerzeichen zu drucken.

Nun fangen wir von innen an:

$(<file)

Die Datei wird zeilenweise gelesen und als Array behandelt. Dann:

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

Das oFlag gibt an, dass das Ergebnis in aufsteigender Reihenfolge sortiert werden soll. Auf diese Weise wird @das Ergebnis auch als Array behandelt. Der Teil hinter ( //?/?) ist eine Ersetzung und ersetzt alle Zeichen durch ein ?. Jetzt:

${~...[1]}

Wir nehmen das erste Array-Element [1], das in Ihrem Fall das kürzeste ist ????.

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

Die Zuordnung wird für jedes Array-Element separat durchgeführt, und die nicht zugeordneten Array-Elemente werden entfernt ( M). Jedes übereinstimmende Element ????(4 Zeichen) bleibt im Array. Die übrigen Elemente haben also 4 Zeichen (die kürzesten).

Bearbeiten: Wenn Sie nur eine der kürzesten Zeilen benötigen, gibt diese geänderte Version die erste aus:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}
Chaos
quelle
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

... und der Gewinner ist ... Zeile 2, wie es scheint.

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

Das Problem dabei ist jedoch, dass jede Zeile mehr als doppelt so lang sein muss, damit sie funktioniert - LINE_MAX wird also effektiv halbiert. Die Ursache ist, dass es verwendet - was, eine Basis 1? - um die Länge der Linie darzustellen. Ein ähnlicher - und vielleicht ordentlicherer - Ansatz könnte darin bestehen, diese Informationen im Stream zu komprimieren. Die erste Idee in dieser Richtung, die mir einfällt, ist, dass ich es tun unexpandsollte:

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

Das druckt ...

2
4for

Ein anderer, nur 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

Die Syntax ist standardkonform - aber das ist keine Garantie dafür, dass alle alten seddie \(reference-group\)\{counts\}richtigen Befehle ausführen - viele nicht.

Es wendet grundsätzlich den gleichen Ausdruck auf wiederholte Eingaben an - was sehr nützlich sein kann, wenn es an der Zeit ist, sie zu kompilieren. Dieses Muster ist:

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

Womit verschiedene Saiten auf unterschiedliche Weise übereinstimmen. Beispielsweise:

string1\nstring2\nstring3

... wird mit sin \1und ''der Nullzeichenfolge in abgeglichen \2.

1\nstring2\nstring3

... passt zu 1in \1und \nstring2\nstring3in\2

\nstring2\nstring3

... wird mit \nin \1und ''der Nullzeichenfolge in abgeglichen \2. Dies wäre problematisch, wenn \nam Anfang des Musterraums eine ewline auftreten könnte - dies wird jedoch mit den Befehlen /^\n/D, und //!gverhindert. Ich habe verwendet, [^\n]aber andere Bedürfnisse für dieses kleine Skript machten die Portabilität zu einem Problem und ich war nicht zufrieden mit den vielen Möglichkeiten, die es oft falsch interpretiert. Plus .ist schneller.

\nstring2
string1

... match \nand sagain in \1und beide bekommen den ''Nullstring in \2. Leerzeilen stimmen überhaupt nicht überein.

Wenn das Muster global angewendet wird, werden die beiden Verzerrungen - sowohl die am weitesten links liegende Standardverzerrung als auch die am \nwenigsten rechts liegende Ewline-Verzerrung - ausgeglichen, um ein Überspringen zu bewirken. Einige Beispiele:

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

... wenn alles (nicht nacheinander) auf die folgende Zeichenfolge angewendet wurde ...

string1\nstring2

... verwandelt es in ...

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

Grundsätzlich verwende ich den regulären Ausdruck, um immer nur die erste Zeile in einem Musterraum zu behandeln, auf den ich ihn anwende. Dadurch kann ich zwei verschiedene Versionen einer beibehaltenen Linie mit dem kürzesten Übereinstimmungswert und der neuesten Linie unter einen Hut bringen, ohne auf Testschleifen zurückgreifen zu müssen - jede angewendete Substitution behandelt den gesamten Musterraum auf einmal.

Die verschiedenen Versionen sind für wörtliche Zeichenfolgen- / Zeichenfolgenvergleiche erforderlich. Daher muss es eine Version jeder Zeile geben, in der garantiert alle Zeichen gleich sind. Aber natürlich sollte die eine oder andere Zeile tatsächlich die am frühesten auftretende kürzeste Eingabezeile sein, dann sollte die zur Ausgabe gedruckte Zeile wahrscheinlich die Originalversion der Zeile sein - nicht die, die ich zu Vergleichszwecken bereinigt / homogenisiert habe. Und so brauche ich jeweils zwei Versionen.

Es ist bedauerlich, dass eine weitere Notwendigkeit darin besteht, dass viel Puffer gewechselt wird, um dasselbe zu handhaben - aber zumindest überschreitet keiner der Puffer jemals mehr als die vier Leitungen, die erforderlich sind, um auf dem neuesten Stand zu bleiben - und daher ist es möglicherweise nicht schrecklich.

Jedenfalls geschieht für jeden Zyklus als Erstes eine Transformation auf der erinnerten Zeile - denn die einzige tatsächlich gespeicherte Kopie ist das wörtliche Original - in ...

^               \nremembered line$

... und danach nüberschreibt die ext-Eingabezeile irgendeinen alten Puffer. Wenn es nicht mindestens ein einzelnes Zeichen enthält, wird es effektiv ignoriert. Es wäre viel einfacher, wenn ich nur qdie erste leere Zeile ausfüllen würde, aber meine Testdaten enthielten viele davon, und ich wollte mit mehreren Absätzen umgehen.

Wenn es also ein Zeichen enthält, wird seine Literalversion an die gespeicherte Zeile angehängt, und seine beabstandete Vergleichsversion wird wie folgt am Kopf des Musterraums positioniert:

^   \n               \nremembered line\nnew$

Zuletzt wird eine Ersetzung auf diesen Musterraum angewendet:

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

Wenn die neue Zeile also in den Platz passt, der zur Aufnahme der gespeicherten Zeile mit mindestens einem freien Zeichen erforderlich ist, werden die ersten beiden Zeilen ersetzt, ansonsten nur die erste.

Unabhängig vom Ergebnis wird die erste Zeile im Musterbereich immer Dam Ende des Zyklus gelöscht, bevor erneut gestartet wird. Dies bedeutet, dass, wenn die neue Zeile kürzer als die letzte ist, die Zeichenfolge ...

new

... wird an die erste Vertretung im Zyklus zurückgeschickt, die sich immer nur vom ersten Zeilenumbruch abhebt - und bleibt somit ganz. Aber wenn es nicht so ist, dann die Zeichenfolge ...

remembered line\nnew

... beginnt stattdessen mit dem nächsten Zyklus, und die erste Ersetzung entfernt die Zeichenfolge ...

\nnew

...jedes Mal.

In der allerletzten Zeile wird die gespeicherte Zeile als Standardausgabe ausgegeben, und für die angegebenen Beispieldaten wird Folgendes ausgegeben:

4for

Aber im Ernst, benutze tr.

mikeserv
quelle
Müssen Sie überhaupt Zeilennummern einfügen? Meine Lektüre des OP ist, dass nur die kürzeste Zeile erforderlich ist und nicht unbedingt die Zeilennummer dieser Zeile. Der Vollständigkeit halber schade ich nicht.
Digital Trauma
@DigitalTrauma - nah, wahrscheinlich nicht. Aber es ist ohne sie kaum sehr nützlich - und sie kommen so billig. Wenn ich einen Stream bearbeite, bevorzuge ich es immer, ein Mittel zur identischen Wiedergabe der ursprünglichen Eingabe in die Ausgabe einzubeziehen - die Zeilennummern machen dies hier möglich. Um zum Beispiel der Ergebnisse der ersten Pipeline drehen: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. Und die zweite ist eine einfache Sache, ein weiteres sed --expressionSkript am Ende einzufügen.
mikeserv
@DigitalTrauma - oh, und im ersten Beispiel der Zeilennummern Sie beeinflussen sort‚s Verhalten als Tie-Breaker , wenn gleiche Längenleitungen in Eingang auftreten - so die früheste auftretende Linie schwimmt immer oben in diesem Fall.
mikeserv
7

Versuchen:

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

Die Idee ist, zuerst awkdie Länge jeder Zeile zu drucken. Dies wird angezeigt als:

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

Verwenden Sie dann die Zeichenanzahl der Zeilen zu sortieren , indem Sie sort, cutum loszuwerden, die Zählung und headdie erste Zeile zu halten (die mit den am wenigsten Zeichen). tailIn diesem Fall können Sie natürlich die Zeile mit den meisten Zeichen verwenden.

(Dies wurde aus dieser Antwort übernommen )

Bichoy
quelle
+1 für die Logik, aber es wird nicht in allen Fällen funktionieren. Wenn die beiden Zeilen die gleiche Anzahl von Zeichen haben und das Minimum ist. Es wird nur die erste Zeile head -1
angezeigt,
Um die längste Zeile zu erhalten, ist es etwas effizienter, die Sortierung umzukehren, als sie zu verwenden tail(da sie beendet werden headkann, sobald ihre Arbeit erledigt ist, ohne den Rest ihrer Eingabe zu lesen).
Toby Speight
@Thushi Mit etwas Regex können nach dem Drucken der Zeilennummern alle Zeilen mit Ausnahme der Zeilen mit der gleichen Nummer wie Zeile 1 entfernt und somit alle kürzesten Zeilen ausgegeben werden.
Matthew D. Scholefield
5

Mit POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file
cuonglm
quelle
Es funktioniert nicht, wenn mehr als eine Zeile die gleiche Anzahl von Zeichen enthält und dies ebenfalls ein Minimum ist.
Thushi
@Thushi: Es wird die erste Mindestzeile gemeldet.
Dienstag,
Ja. Aber das ist nicht die richtige Ausgabe, oder? Auch die anderen Zeilen haben die Mindestanzahl von Zeichen.
Thushi
1
@Thushi: Das wird in der OP-Anforderung nicht erwähnt und wartet auf das Update von OP.
Dienstag,
3
Ich glaube nicht, dass dies Lder beste Buchstabe war, um die Variable zu benennen: D So etwas minwürde die Dinge klarer machen
fedorqui
3

Einige Ideen von @ mikeserv ausleihen:

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

Der erste sedmacht folgendes:

  • h speichert die ursprüngliche Zeile im Haltepuffer
  • Ersetzen Sie jedes Zeichen in der Zeile durch :- um die Gefahr der Code-Eingabe auszuschließen
  • Ersetzen Sie die gesamte Zeile durch expr length "whole line"- dies ist ein Shell-Ausdruck, der ausgewertet werden kann
  • Der Befehl e tos ist eine GNU sed-Erweiterung , um den Musterraum auszuwerten und das Ergebnis wieder in den Musterraum zu stellen.
  • G Fügt eine neue Zeile und den Inhalt des Haltebereichs (die ursprüngliche Zeile) an den Musterbereich an
  • Das Finale sersetzt die neue Zeile durch einen Tabulator

Die Anzahl der Zeichen ist jetzt eine Zahl am Anfang jeder Zeile, also sort -nsortiert nach Zeilenlänge.

Das Finale sedentfernt dann bis auf die erste (kürzeste) Zeile und die Zeilenlänge alle Zeilen und gibt das Ergebnis aus.

Digitales Trauma
quelle
1
@mikeserv Ja ich finde das exprhier schöner. Ja, es ewird eine Shell für jede Zeile erzeugt. Ich habe den sed-Ausdruck so bearbeitet, dass er jedes Zeichen in der Zeichenkette durch ein :vor dem eval ersetzt.
Digital Trauma
Normalerweise würde ich mich für xargs exprpersönlich entscheiden - aber abgesehen von der Vermeidung einer Zwischenschale ist das wahrscheinlich eher eine stilistische Sache. Ich mag es trotzdem.
mikeserv
3

Mir ist aufgefallen, dass das Ganze in einem sedAusdruck möglich ist. Es ist nicht schön:

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

Aufschlüsselung:

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

Das BSD in OS X ist etwas kniffliger mit Zeilenumbrüchen. Diese Version funktioniert sowohl für BSD- als auch für GNU-Versionen von 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
$

Beachten Sie, dass dies eher eine mögliche Antwort ist als ein ernsthafter Versuch, eine Best-Practice-Antwort zu geben. Ich denke, es bedeutet, dass ich zu viel Code-Colf gespielt habe

Digitales Trauma
quelle
@mikeserv From man sedunter OS X: "Die Escape-Sequenz \ n stimmt mit einem im Musterbereich eingebetteten Zeilenumbruchzeichen überein" . Ich denke also, dass GNU sed \ndas Regex und das Ersetzen zulässt, während BSD nur \ndas Regex und nicht das Ersetzen zulässt .
Digitales Trauma
Das Ausleihen \naus dem Musterraum ist eine gute Idee und würde im zweiten s///Ausdruck funktionieren , aber der s/.*/&\n&/Ausdruck fügt a \nin den Musterraum ein, in dem es vorher keinen gab. Auch BSD sed scheint nach Etikettendefinitionen und Verzweigungen wörtliche Zeilenumbrüche zu erfordern.
Digital Trauma
1
Diese Zeilenumbrüche sind Parameterbegrenzer - Sie benötigen sie, um jeden Befehl abzugrenzen, der einen beliebigen Parameter akzeptiert - zumindest sagt das die Spezifikation. Die Spezifikation besagt auch, dass ein sedSkript eine Textdatei sein soll, mit der Ausnahme, dass es nicht in einer neuen Zeile enden muss . Daher können Sie sie normalerweise auch als separate Argumente abgrenzen - sed -e :\ label -e :\ label2und so weiter. Da dies 1hsowieso x;Hder Fall ist, können Sie einfach auf eine Logik basierend auf wechseln, um Ihre neue Zeile zu erhalten - und Sie können eine führende neue Zeile am Ende des Zyklus aus dem Musterbereich entfernen, ohne eine neue Zeile mit einzuziehen D.
mikeserv
@mikeserv Schön. Ja, ich habe die benötigte neue Zeile eingefügt, indem ich die Gerste Zeile gemacht und den s///Ausdruck geändert habe . Wenn Sie -ees mit aufteilen , können Sie eine (lange) Zeile ohne wörtliche Zeilenumbrüche verwenden.
Digital Trauma
Das \nEscape ist auch für sed's LHS spezifiziert , und ich denke, das ist die wörtliche Aussage der Spezifikation, mit der Ausnahme, dass POSIX-Klammerausdrücke auch so spezifiziert sind, dass alle Zeichen ihre spezielle Bedeutung verlieren - (explizit einschließlich \\) - Innerhalb einer Zeile mit Ausnahme der Klammern ist der Bindestrich als Bereichstrenner und Punkt gleich, Caret, Doppelpunkt für Sortierung, Äquivalenz, Negation und Klassen.
mikeserv
2

Eine andere Perl-Lösung: Speichern Sie die Zeilen in einem Hash von Arrays, wobei der Hash-Schlüssel die Zeilenlänge ist. Dann drucken Sie die Zeilen mit der Minimum-Taste aus.

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for
Glenn Jackman
quelle
Sie können push @{$lines{+length}};und print @{$lines{+min keys %lines}};für weniger Tippen verwenden :)
Cuonglm
Wenn ich Golf gespielt hätte, hätte ich auch nicht den Variablennamen "lines" verwendet:perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
Glenn Jackman
+1 für eine nicht-golfed Version (was funktioniert!), Obwohl nur für die Druck alle Variante. - perlwird ein bisschen knorrig für diejenigen von uns, die der perlkryptischen Natur nicht gewachsen sind . BTW. Der Golfer saydruckt am Ende der Ausgabe eine unechte Leerzeile.
Peter.O
2

So erhalten Sie nur die erste kürzeste Zeile:

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

Um die kürzesten Verbindungen zu erhalten, wechseln Sie einfach {p;q}zup


Eine andere (etwas ungewöhnliche) Methode besteht darin sort, die tatsächliche Sortierung nach Länge durchzuführen . Es ist selbst bei kurzen Linien relativ langsam und wird mit zunehmender Linienlänge dramatisch langsamer.
Die Idee, nach überlappenden Schlüsseln zu sortieren, finde ich jedoch sehr interessant. Ich poste es für den Fall, dass andere es ebenfalls interessant / informativ finden.

So funktioniert es:
Nach Längenvarianten desselben Schlüssels sortieren - key 1die sich über die gesamte Zeile erstrecken
Jede nachfolgende Schlüsselvariante erhöht die Schlüssellänge um ein Zeichen bis zur Länge der längsten Zeile der Datei (bestimmt durch wc -L).

So erhalten Sie nur die erste (sortierte) kürzeste Zeile:

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

das ist das gleiche wie:

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

Unter der Annahme, dass Leerzeilen nicht die kürzeste Zeile sind und Leerzeilen vorhanden sein können, funktioniert die folgende reine AWK:

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
snth
quelle
2

Was ist mit sort?

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

Mit GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Lesen Sie jede Zeile in ein Array, das nach Zeilenlänge indiziert ist.

  • Stellen Sie PROCINFO["sorted_in"]diese Option ein, um @ind_num_asczu erzwingen, dass der Array-Scan nach dem Array-Index sortiert und numerisch sortiert wird

  • Die Einstellung PROCINFOin der obigen Weise zwingt die Linie mit der kleinsten Länge, zuerst in der Durchquerung des Arrays aufgenommen zu werden. Also drucke das erste Element aus dem Array und beende es

Dies hat den Nachteil, dass nlogneinige der anderen Ansätze nmit der Zeit kommen

iruvar
quelle
1

Mittelstufige Shell-Tools-Methode ohne sedoder mit 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
agc
quelle
Es wäre schön, keine $fVariable zu benötigen . Ich habe eine Vorstellung, die teeirgendwie möglich sein könnte ...
agc