Arbeiten mit Arrays in Bash

Programmierer verwenden Bash regelmäßig, um viele Aufgaben im Zusammenhang mit der Softwareentwicklung zu lösen. Gleichzeitig werden Bash-Arrays oft als eines der unverständlichsten Merkmale dieser Shell angesehen (wahrscheinlich sind Arrays in dieser Hinsicht nach regulären Ausdrücken an zweiter Stelle). Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, lädt alle in die wunderbare Welt der Bash-Arrays ein, die, wenn Sie sich an ihre ungewöhnliche Syntax gewöhnen, viele Vorteile bringen können.

Bild

Die wahre Herausforderung, die Bash-Arrays bieten


Das Schreiben über Bash ist umstritten. Tatsache ist, dass Artikel über Bash häufig zu Benutzerhandbüchern werden, die sich mit Geschichten über die syntaktischen Merkmale der betreffenden Befehle befassen. Dieser Artikel ist anders geschrieben. Wir hoffen, dass Sie ihn nicht im nächsten "Benutzerhandbuch" finden.

Stellen Sie sich vor diesem Hintergrund ein reales Szenario für die Verwendung von Arrays in Bash vor. Angenommen, Sie stehen vor der Aufgabe, ein Dienstprogramm anhand eines neuen internen Satzes von Tools zu bewerten und zu optimieren, die in Ihrem Unternehmen verwendet werden. Im ersten Schritt dieser Studie müssen Sie sie mit verschiedenen Parametersätzen testen. Der Test zielt darauf ab zu untersuchen, wie sich ein neuer Satz von Werkzeugen verhält, wenn sie eine andere Anzahl von Threads verwenden. Zur Vereinfachung der Darstellung nehmen wir an, dass die „Toolbox“ eine aus C ++ - Code kompilierte „Black Box“ ist. Bei Verwendung ist der einzige Parameter, den wir beeinflussen können, die Anzahl der für die Datenverarbeitung reservierten Threads. Das Aufrufen des zu untersuchenden Systems über die Befehlszeile sieht folgendermaßen aus:

./pipeline --threads 4 

Die Grundlagen


Zunächst deklarieren wir ein Array, das die Werte des Parameters --threads , mit dem wir das System testen möchten. Dieses Array sieht folgendermaßen aus:

 allThreads=(1 2 4 8 16 32 64 128) 

In diesem Beispiel sind alle Elemente Zahlen, aber tatsächlich können Sie in Bash-Arrays sowohl Zahlen als auch Zeichenfolgen gleichzeitig speichern. Zum Beispiel ist die Deklaration eines solchen Arrays durchaus akzeptabel:

 myArray=(1 2 "three" 4 "five") 

Stellen Sie wie bei anderen Bash-Variablen sicher, dass um das = -Zeichen keine Leerzeichen vorhanden sind. Andernfalls betrachtet bash den Variablennamen als den Namen des Programms, das ausgeführt werden soll, und = das erste Argument!

Nachdem wir das Array initialisiert haben, extrahieren wir einige Elemente daraus. Hier können Sie beispielsweise feststellen, dass der echo $allThreads nur das erste Element des Arrays ausgibt.

Um die Gründe für dieses Verhalten zu verstehen, lassen Sie uns ein wenig von Arrays abweichen und uns daran erinnern, wie man mit Variablen in bash arbeitet. Betrachten Sie das folgende Beispiel:

 type="article" echo "Found 42 $type" 

Angenommen, Sie haben eine Variable vom $type , die eine Zeichenfolge enthält, die ein Substantiv darstellt. Fügen Sie nach diesem Wort den Buchstaben s . Sie können diesen Buchstaben jedoch nicht einfach am Ende des Variablennamens einfügen, da dadurch der Befehl zum Zugriff auf die Variable in $types wird. Das heißt, wir arbeiten mit einer völlig anderen Variablen. In dieser Situation können Sie ein Konstrukt wie echo "Found 42 "$type"s" . Es ist jedoch am besten, dieses Problem in geschweiften Klammern zu lösen: echo "Found 42 ${type}s" , mit dem wir bash mitteilen können, wo der Variablenname beginnt und endet (interessanterweise wird in JavaScript ES6 dieselbe Syntax zum Einbetten von Variablen verwendet in Ausdrücken in Musterzeichenfolgen ).

Nun zurück zu den Arrays. Es stellt sich heraus, dass geschweifte Klammern normalerweise nicht benötigt werden, wenn mit Variablen gearbeitet wird, sie werden jedoch benötigt, um mit Arrays zu arbeiten. Mit ihnen können Sie Indizes festlegen, um auf die Elemente des Arrays zuzugreifen. Beispielsweise gibt ein Befehl der Form echo ${allThreads[1]} das zweite Element des Arrays aus. Wenn Sie geschweifte Klammern in der obigen Konstruktion vergessen, nimmt bash [1] als Zeichenfolge wahr und verarbeitet, was entsprechend geschieht.

Wie Sie sehen können, haben Arrays in Bash eine seltsame Syntax, aber in ihnen beginnt zumindest die Nummerierung der Elemente von vorne. Dies macht sie ähnlich wie Arrays aus vielen anderen Programmiersprachen.

Möglichkeiten, auf Array-Elemente zuzugreifen


Im obigen Beispiel haben wir ganzzahlige Indizes in Arrays verwendet, die explizit angegeben sind. Betrachten Sie nun zwei weitere Möglichkeiten, um mit Arrays zu arbeiten.

Die erste Methode ist anwendbar, wenn wir das $i te Element des Arrays benötigen, wobei $i eine Variable ist, die den Index des gewünschten Array-Elements enthält. Sie können dieses Element mit einem Konstrukt der Form echo ${allThreads[$i]} aus dem Array extrahieren.

Mit der zweiten Methode können Sie alle Elemente des Arrays anzeigen. Es besteht darin, den numerischen Index durch das @ -Symbol zu ersetzen (es kann als Befehl interpretiert werden, der auf alle Elemente des Arrays verweist). Es sieht so aus: echo ${allThreads[@]} .

Iterieren über Array-Elemente in Schleifen


Die obigen Prinzipien der Arbeit mit Array-Elementen sind für uns nützlich, um das Problem der Aufzählung von Array-Elementen zu lösen. In unserem Fall bedeutet dies, dass der zu untersuchende pipeline Befehl mit jedem der Werte gestartet wird, der die Anzahl der Threads symbolisiert und in einem Array gespeichert ist. Es sieht so aus:

 for t in ${allThreads[@]}; do ./pipeline --threads $t done 

Array-Indizes in Schleifen auflisten


Betrachten Sie nun einen etwas anderen Ansatz zum Sortieren von Arrays. Anstatt über die Elemente zu iterieren, können wir über die Indizes des Arrays iterieren:

 for i in ${!allThreads[@]}; do ./pipeline --threads ${allThreads[$i]} done 

Lassen Sie uns analysieren, was hier passiert. Wie wir bereits gesehen haben, repräsentiert eine Konstruktion der Form ${allThreads[@]} alle Elemente des Arrays. Wenn wir hier ein Ausrufezeichen ${!allThreads[@]} , verwandeln wir dieses Konstrukt in ${!allThreads[@]} , was dazu führt, dass die Indizes des Arrays zurückgegeben werden (in unserem Fall von 0 bis 7).

Mit anderen Worten, die for Schleife for alle Indizes des als Variable $i dargestellten Arrays, und im Hauptteil der Schleife wird auf die Elemente des Arrays, die als Werte des Parameters --thread dienen, mit dem Konstrukt ${allThreads[$i]} --thread .

Das Lesen dieses Codes ist schwieriger als im vorherigen Beispiel. Daher stellt sich die Frage, wofür all diese Schwierigkeiten sind. Und wir brauchen dies, weil Sie in einigen Situationen bei der Verarbeitung von Arrays in Schleifen sowohl die Indizes als auch die Werte der Elemente kennen müssen. Wenn Sie beispielsweise das erste Element des Arrays überspringen müssen, erspart uns das Durchlaufen der Indizes beispielsweise die Notwendigkeit, eine zusätzliche Variable zu erstellen und diese in einer Schleife zu erhöhen, um mit Array-Elementen zu arbeiten.

Arrays füllen


Bisher haben wir das System untersucht, indem wir den pipeline Befehl --threads und ihm jeden der Werte des Parameters --threads uns interessiert. Angenommen, dieser Befehl gibt die Dauer eines bestimmten Prozesses in Sekunden an. Wir möchten die bei jeder Iteration zurückgegebenen Daten abfangen und in einem anderen Array speichern. Dies gibt uns die Möglichkeit, nach Abschluss aller Tests mit den gespeicherten Daten zu arbeiten.

Nützliche Syntaxkonstrukte


Bevor wir uns mit dem Hinzufügen von Daten zu Arrays befassen, schauen wir uns einige nützliche Syntaxkonstrukte an. Zunächst benötigen wir einen Mechanismus, um Daten zu erhalten, die von Bash-Befehlen ausgegeben werden. Um die Ausgabe eines Befehls zu erfassen, müssen Sie die folgende Konstruktion verwenden:

 output=$( ./my_script.sh ) 

Nach Ausführung dieses Befehls wird die $output myscript.sh in der $output gespeichert.

Die zweite Konstruktion, die sehr bald nützlich sein wird, ermöglicht es uns, neue Daten an Arrays anzuhängen. Es sieht so aus:

 myArray+=( "newElement1" "newElement2" ) 

Problemlösung


Wenn Sie nun alles zusammenstellen, was wir gerade gelernt haben, können Sie ein Skript zum Testen des Systems erstellen, das einen Befehl mit jedem der Parameterwerte aus dem Array ausführt und im anderen Array speichert, was dieser Befehl anzeigt.

 allThreads=(1 2 4 8 16 32 64 128) allRuntimes=() for t in ${allThreads[@]}; do runtime=$(./pipeline --threads $t) allRuntimes+=( $runtime ) done 

Was weiter?


Wir haben gerade untersucht, wie Bash-Arrays verwendet werden, um die beim Starten eines Programms verwendeten Parameter zu durchlaufen und die von diesem Programm zurückgegebenen Daten zu speichern. Die Optionen für die Verwendung von Arrays sind jedoch nicht auf dieses Szenario beschränkt. Hier noch ein paar Beispiele.

Problemwarnungen


In diesem Szenario betrachten wir eine Anwendung, die in Module unterteilt ist. Jedes dieser Module verfügt über eine eigene Protokolldatei. Wir können ein cron Job-Skript schreiben, das bei Problemen in der entsprechenden Protokolldatei die Person, die für jedes der Module verantwortlich ist, per E-Mail benachrichtigt:

 #  -    logPaths=("api.log" "auth.log" "jenkins.log" "data.log") logEmails=("jay@email" "emma@email" "jon@email" "sophia@email") #         for i in ${!logPaths[@]}; do log=${logPaths[$i]} stakeholder=${logEmails[$i]} numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l ) #       5  if [[ "$numErrors" -gt 5 ]]; then   emailRecipient="$stakeholder"   emailSubject="WARNING: ${log} showing unusual levels of errors"   emailBody="${numErrors} errors found in log ${log}"   echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient" fi done 

API-Anfragen


Angenommen, Sie möchten Informationen darüber sammeln, welche Benutzer Ihre Beiträge auf Medium kommentieren. Da wir keinen direkten Zugriff auf die Datenbank dieser Site haben, werden SQL-Abfragen nicht behandelt. Sie können jedoch verschiedene APIs verwenden, um auf diese Art von Daten zuzugreifen.

Um lange Gespräche über Authentifizierung und Token zu vermeiden, verwenden wir als Endpunkt den öffentlichen API- Testdienst JSONPlaceholder . Nachdem wir eine Veröffentlichung vom Dienst erhalten und Daten aus seinem Code an den E-Mail-Adressen der Kommentatoren extrahiert haben, können wir diese Daten in ein Array einfügen:

 endpoint="https://jsonplaceholder.typicode.com/comments" allEmails=() #   10  for postId in {1..10}; do #    API       response=$(curl "${endpoint}?postId=${postId}") #  jq   JSON       allEmails+=( $( jq '.[].email' <<< "$response" ) ) done 

Bitte beachten Sie, dass hier das Tool jq verwendet wird, mit dem JSON in der Befehlszeile analysiert werden kann. Wir werden nicht näher auf die Arbeit mit jq eingehen, wenn Sie an diesem Tool interessiert sind - siehe die Dokumentation dazu.

Bash oder Python?


Arrays - eine nützliche Funktion, die nicht nur in Bash verfügbar ist. Derjenige, der Skripte für die Befehlszeile schreibt, hat möglicherweise eine logische Frage, in welchen Situationen es sich lohnt, bash zu verwenden, und in welchen beispielsweise Python.

Meiner Meinung nach liegt die Antwort auf diese Frage darin, wie sehr der Programmierer von einer bestimmten Technologie abhängt. Angenommen, wenn das Problem direkt in der Befehlszeile gelöst werden kann, verhindert nichts die Verwendung von Bash. Für den Fall, dass beispielsweise das Skript, an dem Sie interessiert sind, Teil eines in Python geschriebenen Projekts ist, können Sie Python gut verwenden.

Um das hier betrachtete Problem zu lösen, können Sie beispielsweise ein in Python geschriebenes Skript verwenden. Dies führt jedoch dazu, dass Wrapper für Python-Wrapper für bash geschrieben werden:

 import subprocess all_threads = [1, 2, 4, 8, 16, 32, 64, 128] all_runtimes = [] #         for t in all_threads: cmd = './pipeline --threads {}'.format(t) #   subprocess   ,    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) output = p.communicate()[0] all_runtimes.append(output) 

Vielleicht ist die Lösung für dieses Problem mit Bash ohne andere Technologien kürzer und verständlicher, und hier können Sie ganz auf Python verzichten.

Zusammenfassung


In diesem Material haben wir viele Designs analysiert, die für die Arbeit mit Arrays verwendet wurden. Hier ist eine Tabelle, in der Sie finden, was wir überprüft haben und etwas Neues.
SyntaxkonstruktBeschreibung
arr=()Erstellen Sie ein leeres Array
arr=(1 2 3)Array-Initialisierung
${arr[2]}Das dritte Element eines Arrays abrufen
${arr[@]}Alle Array-Elemente abrufen
${!arr[@]}Array-Indizes abrufen
${#arr[@]}Berechnung der Arraygröße
arr[0]=3Überschreiben des ersten Elements eines Arrays
arr+=(4)Verbinden eines Array von Werten
str=$(ls)Speichern der ls als Zeichenfolge
arr=( $(ls) )Speichern der Ausgabe des ls als Array von Dateinamen
${arr[@]:s:n}Abrufen von Array-Elementen vom Element mit dem Index s zum Element mit dem Index s+(n-1)

Auf den ersten Blick mögen Bash-Arrays ziemlich seltsam erscheinen, aber die Möglichkeiten, die sie bieten, sind es wert, mit diesen Kuriositäten umzugehen. Wir glauben, dass Sie Bash-Arrays, wenn Sie sie beherrschen, ziemlich oft verwenden werden. Es ist leicht vorstellbar, dass unzählige Szenarien nützlich sein können.

Liebe Leser! Wenn Sie interessante Beispiele für die Verwendung von Arrays in Bash-Skripten haben, teilen Sie diese bitte mit.

Source: https://habr.com/ru/post/de413725/


All Articles