Können Sie dieses Bash-Skript-Puzzle lösen?

Willkommen zur Bash Challenge # 7 von Yes I Know IT & It's FOSS. Bei dieser wöchentlichen Herausforderung zeigen wir Ihnen einen Terminalbildschirm und wir zählen darauf, dass Sie uns dabei helfen, das gewünschte Ergebnis zu erzielen. Es kann viele Lösungen geben, und Kreativität ist der amüsanteste Teil der Herausforderung.

Wenn Sie es noch nicht getan haben, werfen Sie einen Blick auf frühere Herausforderungen:

  • Bash Challenge 6
  • Bash Challenge 5

Sie können diese Herausforderungen (mit unveröffentlichten Herausforderungen) auch in Buchform kaufen und uns unterstützen:

Fertig zu spielen ? Hier ist also die Herausforderung dieser Woche.

Der Tokenzähler

Diese Woche kehren wir zu einer „programmierorientierteren“ Herausforderung zurück. Die Beschreibung ist ein bisschen abstrakt, versuche ein paar Minuten bei mir zu bleiben - und ich hoffe, dass die Beschreibung unten klar genug ist:

Ich habe einen Strom von Token, entweder 'ROT' oder 'BLAU'. Wenn Sie möchten, können Sie dies beispielsweise als Darstellung eines Ereignisstroms betrachten. Ich habe keine besondere Kontrolle über diesen Stream. Ich weiß nur, dass es entweder das eine oder das andere Token erzeugt, unvorhersehbar. Und ich weiß, dass der Dampf endlich ist (dh, irgendwann gibt es keine Daten mehr zum Lesen).

Für diese Herausforderung habe ich eine Bash-Funktion verwendet, um diesen Stream zu erzeugen. Das darf man sowieso nicht ändern.

# You MUST NOT change that : stream() { TOKENS=( "RED" "BLUE" ) for((i=0;i<100;++i)) ; do echo ${TOKENS[RANDOM%2]} done } 

Mein Ziel ist es, sowohl die roten als auch die blauen Marken zu zählen, die sich im Stream befanden. Ich konnte alleine eine Lösung finden, um die Anzahl der ROTEN Token zu zählen:

  # You MUST change that stream | \ grep -F RED | wc -l > RED.CNT cat RED.CNT 

Leider konnte ich keine Lösung finden, um sowohl ROTE als auch BLAUE Token zu zählen. Deshalb brauche ich deine Hilfe. Irgendeine Idee ?

Wir freuen uns darauf, Ihre Lösungen im Kommentarbereich weiter unten zu lesen!

Einige Details

Um diese Herausforderung zu erstellen, habe ich Folgendes verwendet:

  • GNU Bash, Version 4.4.5 (x86_64-pc-linux-gnu)

  • Debian 4.8.7-1 (amd64)
  • Alle Befehle werden mit einer Standard-Debian-Distribution ausgeliefert
  • Es wurden keine Befehle mit Alias ​​versehen

Die Lösung

Wie reproduzieren

Hier ist der Rohcode, mit dem wir diese Herausforderung erstellt haben. Wenn Sie das in einem Terminal ausführen, können Sie genau dasselbe Ergebnis wie in der Abbildung zur Abfrage reproduzieren (vorausgesetzt, Sie verwenden dieselbe Softwareversion wie ich):

 rm -rf ItsFOSS mkdir -p ItsFOSS cd ItsFOSS clear stream() { TOKENS=( "RED" "BLUE" ) for((i=0;i RED.CNT cat RED.CNT 

Was war das Problem ?

Die einzige Schwierigkeit hier war mein erster Versuch, einen Teil der Eingabe zu verwerfen, da ich den Datenstrom direkt an grep sende.

Grundsätzlich gibt es drei Ansätze, um dieses Problem zu lösen:

  • Speichern Sie die Stream-Daten und verarbeiten Sie sie anschließend.

  • Duplizieren Sie den Stream und verarbeiten Sie zwei unabhängige Pfade für ROTE und BLAUE Token.
  • Behandeln Sie beide Fälle im selben Befehl, wie sie ankommen.

Nach jeder Lösung gebe ich die auf meinem System beobachtete Echtzeitnutzung an. Dies ist nur ein Hinweis und muss mit Vorsicht genommen werden. Machen Sie sich also selbst den Vergleich!

Der Filial- und Prozessansatz

Die einfachste Implementierung des Store-and-Process-Ansatzes liegt auf der Hand:

 stream > stream.cache grep -F RED RED.CNT grep -F BLUE BLUE.CNT rm stream.cache (1.3s for 10, 000, 000 tokens) 

Es funktioniert, hat aber einige Nachteile: Sie müssen die Daten speichern, und die Daten werden nacheinander für jedes Token verarbeitet. Etwas subtiler: stream.cache Datei stream.cache zweimal stream.cache, besteht möglicherweise eine Race Condition, wenn ein gleichzeitiger Prozess diese Datei während der Verarbeitung aktualisiert.

Noch in der Kategorie Store-and-Process gibt es eine ganz andere Lösung:

 stream | sort | uniq -c (5.9s for 10, 000, 000 tokens) 

Ich halte das für einen Store-and-Process-Ansatz, da der sort zuerst alle Daten lesen und speichern muss (entweder im RAM oder auf der Festplatte) , bevor er sie verarbeiten kann. Genauer gesagt erstellt der Befehl sort auf meinem Debian-System mehrere temporäre Dateien in /tmp mit rw- Berechtigungen. Grundsätzlich hat diese Lösung die gleichen Nachteile wie die allererste, jedoch mit viel schlechteren Leistungen.

Stream duplizieren

Müssen wir die Daten wirklich / speichern / bevor / verarbeiten? Nein. Eine viel klügere Idee wäre, den Stream in zwei Teile aufzuteilen und in jedem Sub-Stream eine Art Token zu verarbeiten:

 stream | tee >(grep -F RED | wc -l > RED.CNT) \ >(grep -F BLUE | wc -l > BLUE.CNT) \ > /dev/null (0.8s for 10, 000, 000) 

Hier gibt es keine Zwischendateien. Der Befehl tee repliziert die Stream-Daten, sobald sie eintreffen. Jede Verarbeitungseinheit erhält eine eigene Kopie der Daten und kann diese im laufenden Betrieb verarbeiten.

Dies ist eine clevere Idee, da wir nicht nur mit Daten umgehen, wenn sie eintreffen, sondern sie jetzt parallel verarbeiten.

Behandle Daten, sobald sie eintreffen

In der Informatik würden wir wahrscheinlich sagen, dass die vorherige Lösung eine funktionale Herangehensweise an das Problem darstellt. Auf der anderen Seite werden die nächsten rein zwingende Lösungen sein. Hier werden wir jedes Token lesen und / wenn / dies ein ROTES Token ist, / dann / erhöhen wir einen ROTEN Zähler, / andernfalls, wenn / dies ein BLAUES Token ist, erhöhen wir einen BLAUEN Zähler.

Dies ist eine einfache Bash-Implementierung dieser Idee:

 declare -i RED=0 BLUE=0 stream | while read TOKEN; do case "$TOKEN" in RED) RED+=1 ;; BLUE) BLUE+=1 ;; esac done (103.2s for 10, 000, 000 tokens) 

Da ich ein großer Fan des AWK Befehls bin, werde ich der Versuchung nicht widerstehen, es zu verwenden, um diese Herausforderung auf eine ordentliche und elegante Art und Weise zu lösen:

 stream | awk ' /RED/ { RED++ } /BLUE/ { BLUE++ } END { printf "%5d %5d\n", RED, BLUE } ' (2.6s for 10, 000, 000 tokens) 

Mein AWK-Programm besteht aus drei Regeln:

  • Erhöhen Sie ( ++ ) den ROTEN Zähler, wenn Sie auf eine Zeile stoßen, die das Wort ROT enthält

  • Erhöhen Sie den BLAUEN Zähler, wenn Sie auf eine Zeile stoßen, die das Wort BLAU enthält
  • Zeigen Sie am ENDE des Eingangs beide Zähler an.

Um zu verstehen, dass Sie für mathematische Operatoren wissen müssen, dass nicht initialisierte AWK Variablen Null sind.

Das funktioniert super Es ist jedoch erforderlich, dass für jedes Token dieselbe Regel dupliziert wird. Keine große Sache hier, da wir nur zwei verschiedene Token haben. Ärgerlicher, wenn wir viele von ihnen haben. Um das zu lösen, könnten wir uns auf Arrays verlassen :

 stream | awk ' { C[$0]++ } END { printf "%5d %5d\n", C["RED"], C["BLUE"] } ' (2.0s for 10, 000, 000 tokens) 

Wir brauchen hier nur zwei Regeln, unabhängig von der Anzahl der Token:

  • Was auch immer das Lesetoken ( $0 ) ist, erhöhen Sie die entsprechende Array-Zelle (hier entweder C["RED"] oder C["BLUE"] ).

  • Zeigen Sie am ENDE der Eingabe den Inhalt des Arrays für die Zellen "RED" und "BLUE" .

Bitte beachten Sie, dass "RED" und "BLUE" jetzt Zeichenfolgen sind (haben Sie die doppelten Anführungszeichen gesehen?). AWK ist für AWK kein Problem, da es assoziative Arrays unterstützt. Und genau wie bei einfachen Variablen wird angenommen, dass nicht initialisierte Zellen in einem AWK assoziativen Array für mathematische Operatoren Null sind.

Wie ich bereits erklärt habe, habe ich mich für AWK . Aber Perl Fans könnten eine andere Meinung zu diesem Thema haben. Wenn Sie einer von ihnen sind, warum veröffentlichen Sie nicht Ihre eigene Lösung im Kommentarbereich?

Wie auch immer, wir hoffen, Ihnen hat diese Herausforderung gefallen. Und bleiben Sie dran für mehr Spaß!

Empfohlen

Überwachen Sie die Internetgeschwindigkeit mit der Netspeed-Applet-Anzeige in Ubuntu 14.04
2019
So installieren Sie Sublime Text 3 unter Ubuntu Linux
2019
Antergos Linux arbeitet an einem eigenen App Store
2019