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 -m
die 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, 4for
da 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.
quelle
Antworten:
Ein Perl-Weg. Beachten Sie, dass dieser Ansatz nur eine Zeile ausgibt, wenn es viele Zeilen mit derselben kürzesten Länge gibt:
Erläuterung
perl -lne
:-n
bedeutet "Eingabedatei zeilenweise lesen" und-l
bewirkt , dass nachfolgende Zeilenumbrüche aus jeder Eingabezeile entfernt werden und jedemprint
Aufruf ein Zeilenumbruch hinzugefügt wird . und-e
ist das Skript, das auf jede Zeile angewendet wird.$m//=$_
:$m
auf die aktuelle Zeile setzen ($_
), sofern nicht anders$m
definiert. Der//=
Operator ist ab Perl 5.10.0 verfügbar.$m=$_ if length()<length($m)
: Wenn die Länge des aktuellen Werts von$m
größ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$m
der kürzesten Zeile gedruckt . Dieif $.
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:
Erläuterung
@K=sort{length($a) <=> length($b)}<>
:<>
Hier ist ein Array, dessen Elemente die Zeilen der Datei sind. Diesort
sortieren 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
quelle
-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).Mit
sqlite3
:quelle
strace
anzeigt). 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.Hier ist eine Variante einer
awk
Lösung zum Drucken der ersten gefundenen Mindestzeile:die einfach um eine Bedingung erweitert werden kann, um alle Mindestzeilen zu drucken:
quelle
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'))"
quelle
Ich liebe immer Lösungen mit reinem Shell-Scripting (keine Exec!).
Hinweis :
Es liegt ein Problem mit NUL-Bytes in der Eingabe vor. Also
printf "ab\0\0\ncd\n" | bash this_script
drucktab
stattcd
.quelle
bash
würde mich die Ungeschicklichkeit der Tests überzeugen,sort
stattdessen ein Zwischenergebnis einzugeben.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.$IFS
ist nicht ziffernunterscheidend - auch wenn keiner in einem Standardwert$IFS
enthalten ist, obwohl viele Shells eine voreingestellte Umgebungskonfiguration für akzeptieren$IFS
- und daher ist dies kein besonders zuverlässiger Standard./bin/sh
verfügbar ist, als sehr nützlich erachtet werden . Es ist mir mehrmals mit SunOS4-Hosts passiert, die/usr
verloren gegangen oder.so
beschä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.Hier eine reine
zsh
Lösung (es werden alle Zeilen mit der minimalen Länge gedruckt, abfile
):Beispiel Eingabe:
Ausgabe ist:
Ich denke, es bedarf einer kurzen Erklärung :-)
Zuerst setzen wir das interne Feldtrennzeichen auf newline:
So weit so gut, jetzt der schwierige Teil.
print
verwendet das-l
Flag, um das Ergebnis durch Zeilenumbrüche anstelle von Leerzeichen zu drucken.Nun fangen wir von innen an:
Die Datei wird zeilenweise gelesen und als Array behandelt. Dann:
Das
o
Flag 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:Wir nehmen das erste Array-Element
[1]
, das in Ihrem Fall das kürzeste ist????
.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:
quelle
... und der Gewinner ist ... Zeile 2, wie es scheint.
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
unexpand
sollte:Das druckt ...
Ein anderer, nur
sed
:Die Syntax ist standardkonform - aber das ist keine Garantie dafür, dass alle alten
sed
die\(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:
Womit verschiedene Saiten auf unterschiedliche Weise übereinstimmen. Beispielsweise:
... wird mit
s
in\1
und''
der Nullzeichenfolge in abgeglichen\2
.... passt zu
1
in\1
und\nstring2\nstring3
in\2
... wird mit
\n
in\1
und''
der Nullzeichenfolge in abgeglichen\2
. Dies wäre problematisch, wenn\n
am Anfang des Musterraums eine ewline auftreten könnte - dies wird jedoch mit den Befehlen/^\n/D
, und//!g
verhindert. 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.... match
\n
ands
again in\1
und beide bekommen den''
Nullstring in\2
. Leerzeilen stimmen überhaupt nicht überein.Wenn das Muster
g
lobal angewendet wird, werden die beiden Verzerrungen - sowohl die am weitesten links liegende Standardverzerrung als auch die am\n
wenigsten rechts liegende Ewline-Verzerrung - ausgeglichen, um ein Überspringen zu bewirken. Einige Beispiele:... wenn alles (nicht nacheinander) auf die folgende Zeichenfolge angewendet wurde ...
... verwandelt es in ...
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 ...
... 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 nurq
die 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:
Zuletzt wird eine Ersetzung auf diesen Musterraum angewendet:
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
D
am Ende des Zyklus gelöscht, bevor erneut gestartet wird. Dies bedeutet, dass, wenn die neue Zeile kürzer als die letzte ist, die Zeichenfolge ...... 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 ...
... beginnt stattdessen mit dem nächsten Zyklus, und die erste Ersetzung entfernt die Zeichenfolge ...
...jedes Mal.
In der allerletzten Zeile wird die gespeicherte Zeile als Standardausgabe ausgegeben, und für die angegebenen Beispieldaten wird Folgendes ausgegeben:
Aber im Ernst, benutze
tr
.quelle
REINPUT | sort -t: -nk1,1 | cut -d: -f3-
. Und die zweite ist eine einfache Sache, ein weiteressed
--expression
Skript am Ende einzufügen.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.Versuchen:
Die Idee ist, zuerst
awk
die Länge jeder Zeile zu drucken. Dies wird angezeigt als:Verwenden Sie dann die Zeichenanzahl der Zeilen zu sortieren , indem Sie
sort
,cut
um loszuwerden, die Zählung undhead
die erste Zeile zu halten (die mit den am wenigsten Zeichen).tail
In diesem Fall können Sie natürlich die Zeile mit den meisten Zeichen verwenden.(Dies wurde aus dieser Antwort übernommen )
quelle
head -1
tail
(da sie beendet werdenhead
kann, sobald ihre Arbeit erledigt ist, ohne den Rest ihrer Eingabe zu lesen).Mit POSIX awk:
quelle
L
der beste Buchstabe war, um die Variable zu benennen: D So etwasmin
würde die Dinge klarer machenEinige Ideen von @ mikeserv ausleihen:
Der erste
sed
macht folgendes:h
speichert die ursprüngliche Zeile im Haltepuffer:
- um die Gefahr der Code-Eingabe auszuschließenexpr length "whole line"
- dies ist ein Shell-Ausdruck, der ausgewertet werden kanns
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 ans
ersetzt die neue Zeile durch einen TabulatorDie Anzahl der Zeichen ist jetzt eine Zahl am Anfang jeder Zeile, also
sort -n
sortiert nach Zeilenlänge.Das Finale
sed
entfernt dann bis auf die erste (kürzeste) Zeile und die Zeilenlänge alle Zeilen und gibt das Ergebnis aus.quelle
expr
hier schöner. Ja, ese
wird 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.xargs expr
persönlich entscheiden - aber abgesehen von der Vermeidung einer Zwischenschale ist das wahrscheinlich eher eine stilistische Sache. Ich mag es trotzdem.Mir ist aufgefallen, dass das Ganze in einem
sed
Ausdruck möglich ist. Es ist nicht schön:Aufschlüsselung:
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:
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
quelle
man sed
unter OS X: "Die Escape-Sequenz \ n stimmt mit einem im Musterbereich eingebetteten Zeilenumbruchzeichen überein" . Ich denke also, dass GNU sed\n
das Regex und das Ersetzen zulässt, während BSD nur\n
das Regex und nicht das Ersetzen zulässt .\n
aus dem Musterraum ist eine gute Idee und würde im zweitens///
Ausdruck funktionieren , aber ders/.*/&\n&/
Ausdruck fügt a\n
in den Musterraum ein, in dem es vorher keinen gab. Auch BSD sed scheint nach Etikettendefinitionen und Verzweigungen wörtliche Zeilenumbrüche zu erfordern.sed
Skript 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 :\ label2
und so weiter. Da dies1h
sowiesox;H
der 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 einzuziehenD
.G
erste Zeile gemacht und dens///
Ausdruck geändert habe . Wenn Sie-e
es mit aufteilen , können Sie eine (lange) Zeile ohne wörtliche Zeilenumbrüche verwenden.\n
Escape ist auch fürsed
'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.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.
quelle
push @{$lines{+length}};
undprint @{$lines{+min keys %lines}};
für weniger Tippen verwenden :)perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
perl
wird ein bisschen knorrig für diejenigen von uns, die derperl
kryptischen Natur nicht gewachsen sind . BTW. Der Golfersay
druckt am Ende der Ausgabe eine unechte Leerzeile.So erhalten Sie nur die erste kürzeste Zeile:
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 1
die sich über die gesamte Zeile erstreckenJede 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:
das ist das gleiche wie:
quelle
Unter der Annahme, dass Leerzeilen nicht die kürzeste Zeile sind und Leerzeilen vorhanden sein können, funktioniert die folgende reine AWK:
quelle
Was ist mit sort?
quelle
Mit GNU awk
Lesen Sie jede Zeile in ein Array, das nach Zeilenlänge indiziert ist.
Stellen Sie
PROCINFO["sorted_in"]
diese Option ein, um@ind_num_asc
zu erzwingen, dass der Array-Scan nach dem Array-Index sortiert und numerisch sortiert wirdDie Einstellung
PROCINFO
in 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 esDies hat den Nachteil, dass
nlogn
einige der anderen Ansätzen
mit der Zeit kommenquelle
Mittelstufige Shell-Tools-Methode ohne
sed
oder mitawk
:quelle
$f
Variable zu benötigen . Ich habe eine Vorstellung, dietee
irgendwie möglich sein könnte ...