Kun jij deze Bash Script-puzzel oplossen?

Welkom bij de Bash Challenge # 7 door Yes I Know IT & It is FOSS. In deze wekelijkse uitdaging zullen we u een terminalscherm tonen en we zullen op u rekenen om ons te helpen het gewenste resultaat te behalen. Er kunnen veel oplossingen zijn en creatief zijn is het leukste deel van de uitdaging.

Als je het nog niet gedaan hebt, bekijk dan de vorige uitdagingen:

  • Bash-uitdaging 6
  • Bash-uitdaging 5

Je kunt deze uitdagingen (met niet-gepubliceerde uitdagingen) ook in boekvorm kopen en ons steunen:

Klaar om te spelen ? Dus hier is de uitdaging van deze week.

De token-teller

Deze week gaan we terug naar een meer "programmeergerichte" uitdaging. De beschrijving is een beetje abstract, probeer een paar minuten bij me te blijven - en ik hoop dat de onderstaande beschrijving duidelijk genoeg is:

Ik heb een aantal tokens 'ROOD' of 'BLAUW'. Als je wilt, kun je dat beschouwen als een representatie van een evenementenstroom bijvoorbeeld. Ik heb geen specifieke controle over die stream. Ik weet gewoon dat het een of ander token oplevert, onvoorspelbaar. En ik weet dat de stoom eindig is (dat wil zeggen: op een gegeven moment zullen er geen gegevens meer zijn om te lezen).

Voor deze uitdaging heb ik een Bash-functie gebruikt om die stream te produceren. U mag dat hoe dan ook niet veranderen.

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

Mijn doel is om zowel het aantal RODE als BLAUWE tokens te tellen dat er in de stream was. Ik heb zelf een oplossing kunnen vinden om het aantal RODE tokens alleen te tellen:

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

Helaas kon ik geen enkele oplossing vinden om zowel RODE als BLAUWE tokens te tellen. Dat is waarom ik je hulp nodig heb. Enig idee ?

We kijken er naar uit om uw oplossingen te lezen in de commentaarsectie hieronder!

Weinig details

Om deze uitdaging te creëren, gebruikte ik:

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

  • Debian 4.8.7-1 (amd64)
  • Alle opdrachten zijn die met een standaard Debian-distributie
  • Geen opdrachten zijn gealiast

De oplossing

Hoe te reproduceren

Dit is de ruwe code die we hebben gebruikt om deze uitdaging aan te gaan. Als u dat in een terminal uitvoert, kunt u exact hetzelfde resultaat reproduceren als in de illustratie van de uitdaging (ervan uitgaande dat u dezelfde softwareversie als mij gebruikt):

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

Wat was het probleem ?

De enige moeilijkheid hier was dat mijn eerste poging een deel van de invoer afkeurde, omdat ik de datastream rechtstreeks naar de grep stuurde.

In principe zijn er drie manieren om dat probleem op te lossen:

  • Sla de streamgegevens op en verwerk ze daarna;

  • Dupliceer de stream en verwerk twee onafhankelijke paden voor RODE en BLAUWE tokens;
  • Behandel beide gevallen in dezelfde opdracht als ze binnenkomen.

Voor wat het waard is, geef ik na elke oplossing het real-time gebruik dat op mijn systeem is waargenomen. Dit is slechts een indicatie en moet met de nodige voorzichtigheid worden genomen. Dus voel je vrij om zelf de vergelijking te maken!

De winkel- en procesbenadering

De eenvoudigste implementatie van de winkel-en-procesbenadering is duidelijk:

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

Het werkt, maar heeft verschillende nadelen: u moet de gegevens opslaan en de gegevens worden sequentieel verwerkt voor elk token. Meer subtiel: als u tweemaal het bestand stream.cache, hebt u mogelijk een bepaalde raceconditie als een gelijktijdig proces dit bestand tijdens de verwerking bijwerkt.

Nog steeds in de store-and-process-categorie, hier is een heel andere oplossing:

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

Ik beschouw dat als een winkel-en-procesbenadering, omdat het sort eerst alle gegevens moet lezen en opslaan (in RAM of op schijf) voordat ze kunnen worden verwerkt. Om precies te zijn, op mijn Debian-systeem maakt de sorteeropdracht een aantal tijdelijke bestanden in /tmp met rw- machtigingen. In principe heeft deze oplossing dezelfde nadelen als de allereerste, maar met veel slechtste prestaties.

Dubbele stream

Moeten we echt / store / de data / before / processing them? Nee. Een veel slimmer idee zou zijn om de stream in twee delen te splitsen en een soort token in elke deelstroom te verwerken:

 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 zijn geen tussenliggende bestanden. De tee opdracht kopieert de streamgegevens naarmate ze aankomen. Elke verwerkingseenheid krijgt een eigen kopie van de gegevens en kan deze onmiddellijk verwerken.

Dit is een slim idee, omdat we niet alleen gegevens verwerken als ze aankomen, maar we hebben nu parallelle verwerking.

Omgaan met gegevens als ze aankomen

In de informatica zouden we waarschijnlijk zeggen dat de vorige oplossing een functionele benadering van het probleem had. Aan de andere kant zullen de volgende puur imperatieve oplossingen zijn. Hier zullen we elke token lezen, en / of / dit is een RODE token, / dan / we zullen een RODE teller verhogen, / else if / dit is een BLAUW token, we zullen een BLAUWE teller verhogen.

Dit is een eenvoudige Bash-implementatie van dat 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) 

Eindelijk, als een grote fan van het AWK commando, zal ik de verleiding niet weerstaan ​​om het te gebruiken om die uitdaging op een nette en elegante manier op te lossen:

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

Mijn AWK-programma bestaat uit drie regels:

  • Wanneer u een regel tegenkomt die het woord ROOD bevat, verhoogt u ( ++ ) de RODE teller

  • Wanneer u een regel tegenkomt die het woord BLAUW bevat, verhoogt u de BLAUWE teller
  • Geef aan het einde van de invoer beide tellers weer.

Natuurlijk, om volledig te begrijpen dat u moet weten, worden voor de doeleinden van wiskundige operators niet-geïnitialiseerde AWK variabelen als nul beschouwd.

Dat werkt geweldig. Maar het vereist duplicatie van dezelfde regel voor elke token. Geen big deal hier, want we hebben maar twee verschillende tokens. Meer vervelend als we er veel van hebben. Om dat op te lossen, konden we vertrouwen op arrays :

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

We hebben hier slechts twee regels nodig, ongeacht het aantal tokens:

  • Wat het gelezen token ook is ( $0 ), vergroot de bijbehorende arraycel (hier C["RED"] of C["BLUE"] )

  • Aan het einde van de invoer geeft u de inhoud van de array weer voor zowel de cellen "RED" als "BLUE" .

Merk op dat "RED" en "BLUE" nu tekenreeksen zijn (hebt u de dubbele aanhalingstekens om ze heen gezien?) En dat is geen probleem voor AWK omdat het associatieve matrices ondersteunt. En net als gewone variabelen worden niet-geïnitialiseerde cellen in een AWK associatieve array verondersteld nul te zijn voor wiskundige operators.

Zoals ik al eerder heb uitgelegd, heb ik de keuze gemaakt om AWK hier te gebruiken. Maar Perl fans hebben misschien een andere mening over het onderwerp. Als u een van hen bent, waarom publiceert u dan niet uw eigen oplossing in het commentaargedeelte?

Hoe dan ook, we hopen dat je die uitdaging leuk vond. En blijf op de hoogte voor meer plezier!

Aanbevolen

LosslessCut is een belachelijk eenvoudige videosnijder voor Linux
2019
Crisis bij Void Linux als hoofdontwikkelaar ontbreekt in actie
2019
Putty installeren op Ubuntu en andere Linux-distributies
2019