Website-Suche

Fortgeschrittene Linux-Subshells mit Beispielen


Wenn Sie unseren vorherigen Artikel über Linux-Subshells für Anfänger mit Beispielen gelesen haben oder bereits Erfahrung mit Subshells haben, wissen Sie, dass Subshells eine leistungsstarke Möglichkeit sind, Bash-Befehle inline und kontextsensitiv zu manipulieren.

In diesem Tutorial lernen Sie:

  • So erstellen Sie erweiterte Subshell-Befehle
  • Wo Sie fortgeschrittenere Subshells in Ihrem eigenen Code verwenden können
  • Beispiele für erweiterte Subshell-Befehle

Fortgeschrittene Linux-Subshells mit Beispielen

Verwendete Softwareanforderungen und Konventionen

Beispiel 1: Zählen von Dateien

if [ $(ls [a-z]* 2>/dev/null | wc -l) -gt 0 ]; then echo "Found one or more occurrences of [a-z]* files!"; fi

Hier haben wir eine if-Anweisung mit dem ersten Vergleichswert, einer Subshell. Dies funktioniert gut und bietet ein hohes Maß an Flexibilität, wenn es um das Schreiben von if-Anweisungen geht. Es unterscheidet sich von der binären (true oder false) ähnlichen Operation von beispielsweise einer if grep -q 'search_term' ./docfile.txt-Anweisung. Vielmehr wird er per se als Standardvergleich ausgewertet (abgeglichen mit der Größer-als-Null-Klausel -gt 0).

Die Subshell versucht, Dateien mit dem Namen [a-z]* aufzulisten, d.h. Dateien, die mit mindestens einem Buchstaben im Bereich von a-z beginnen, gefolgt von einem beliebigen nachfolgenden Zeichen. Fehlersicher ist es, indem 2>/dev/null hinzugefügt wird – d.h. jeder angezeigte Fehler (auf stderr – die Standardfehlerausgabe, gekennzeichnet durch die 2) wird > auf /dev/null – d.h. das Linux-Nullgerät – umgeleitet und somit ignoriert.

Schließlich übergeben wir die ls-Eingabe an wc -l, die für uns zählt, wie viele Zeilen (oder in diesem Fall Dateien) gesehen wurden. Wenn das Ergebnis größer als 0 war, wird der Informationshinweis angezeigt.

Beachten Sie, dass der Kontext, in dem die Subshell ausgeführt wird, unterschiedlich ist. Erstens, in diesem Fall, arbeitet die Subshell innerhalb des aktuellen Arbeitsverzeichnisses (d.h. $PWD), was bemerkenswerterweise auch die Standardeinstellung ist, d.h. Subshells starten standardmäßig mit ihrer eigenen Umgebung PWD, die auf das aktuelle Arbeitsverzeichnis gesetzt ist. Zweitens arbeitet die Subshell im Kontext einer if-Anweisung.

Dieser Befehl erzeugt keine Ausgabe, da er in einem leeren Verzeichnis ausgeführt wird. Beachten Sie jedoch, dass die Tatsache, dass keine Ausgabe erzeugt wird, auch bedeutet, dass unsere Fehlerunterdrückung funktioniert. Lassen Sie uns Folgendes überprüfen:

if [ $(ls [a-z]* | wc -l) -gt 0 ]; then echo "Found one or more occurrences of [a-z]* files!"; fi
ls: cannot access '[a-z]*': No such file or directory

Wie das Entfernen der Fehlerunterdrückung funktioniert hat, sehen wir im vorherigen Beispiel. Als nächstes erstellen wir eine Datei und sehen, wie unser Einzeiler funktioniert:

touch a
if [ $(ls [a-z]* 2>/dev/null | wc -l) -gt 0 ]; then echo "Found one or more occurrences of [a-z]* files!"; fi
Found one or more occurrences of [a-z]* files!

Toll, es sieht so aus, als ob unser Einzeiler-Skript gut funktioniert. Fügen wir als Nächstes eine sekundäre Datei hinzu und sehen, ob wir die Nachricht verbessern können

touch b
if [ $(ls [a-z]* 2>/dev/null | wc -l) -gt 0 ]; then echo "Found one or more occurrences of [a-z]* files!"; fi
Found one or more occurrences of [a-z]* files!
if [ $(ls [a-z]* 2>/dev/null | wc -l) -gt 0 ]; then echo "Found exactly $(ls [a-z]* 2>/dev/null | wc -l) occurrences of [a-z]* files!"; fi
Found exactly 2 occurrences of [a-z]* files!

Hier sehen wir, dass das Hinzufügen einer zweiten Datei (durch Berührung b) keinen Unterschied macht (wie im ersten if-Befehl zu sehen), es sei denn, wir ändern die Ausgabe so, dass tatsächlich gemeldet wird, wie viele Dateien gefunden wurden, indem wir eine sekundäre Subshell in die Ausgabe einfügen.

Dies ist jedoch nicht optimal codiert; In diesem Fall müssen zwei Subshells ausgeführt werden (die Kosten für die Erstellung einer Subshell sind sehr gering, aber wenn Sie viele Subshells in hoher Frequenz erstellen, spielen die Kosten eine Rolle), und die direkte Auflistung wird zweimal angefordert (wodurch zusätzliche I/O generiert und unser Code auf die Geschwindigkeit des I/O-Subsystems und des verwendeten Festplattentyps verlangsamt wird). Lassen Sie uns dies in eine Variable einfügen:

COUNT="$(ls [a-z]* 2>/dev/null | wc -l)"; if [ ${COUNT} -gt 0 ]; then echo "Found exactly ${COUNT} occurrences of [a-z]* files!"; fi
Found exactly 2 occurrences of [a-z]* files!

Großartig. Dies ist optimalerer Code; es wird eine einzelne Subshell verwendet und das Ergebnis wird in einer Variablen gespeichert, die dann zweimal verwendet wird, und es ist nur ein einziges Disk-Verzeichnis, das aufgelistet wird, erforderlich. Beachten Sie auch, dass diese Lösung möglicherweise threadsicherer ist.

Wenn z.B. in der if-Anweisung, die zwei Subshells hatte, in der Zeit zwischen der Ausführung dieser Subshells eine dritte Datei erstellt wurde, könnte das Ergebnis wie folgt aussehen: Es wurden genau 3 Vorkommen von [a-z]*-Dateien gefunden!, während die erste if-Anweisung (unter Verwendung der ersten Subshell) wirklich auf if 2 -gt 0 qualifiziert wurde – d.h. 2. In diesem Fall würde es kaum einen Unterschied machen, aber Sie können sehen, wie es bei manchen Codierungen sehr wichtig werden kann, darauf zu achten.

Beispiel 2: Unterschalen für die Berechnung

touch z
echo $[ $(date +%s) - $(stat -c %Z ./z) ]
1
echo $[ $(date +%s) - $(stat -c %Z ./z) ]
5

Hier haben wir eine Datei erstellt, nämlich z, und anschließend mit dem zweiten Befehl das Alter der Datei in Sekunden ermittelt. Ein paar Sekunden später haben wir den Befehl erneut ausgeführt und wir können sehen, dass die Datei jetzt 5 Sekunden alt ist.

Der Befehl date +%s gibt uns die aktuelle Zeit in Sekunden seit der Epoche (1970-01-01 UTC), und stat -c %Z gibt uns die Sekunden seit der Epoche für die Datei, die zuvor erstellt wurde und jetzt hier als ./z referenziert wird, so dass wir anschließend nur noch diese beiden voneinander subtrahieren müssen. Wir setzen das Datum +%s an die erste Stelle, da dies die höchste Zahl (die aktuelle Zeit) ist und berechnen somit den Offset in Sekunden korrekt.

Die Option -c für stat gibt einfach an, dass wir eine bestimmte Ausgabeformatierung wünschen, in diesem Fall %Z, oder mit anderen Worten die Zeit seit der Epoche. Für date ist die Syntax für die gleiche Idee +%s, allerdings in Verbindung mit der aktuellen Uhrzeit und nicht bezogen auf eine bestimmte Datei.

Beispiel 3: Subshells in sed und andere Tools

echo '0' > a
sed -i "s|0|$(whoami)|" ./a
cat a
roel

Wie Sie sehen können, können wir eine Subshell in fast jedem Befehl verwenden, den wir auf der Befehlszeile ausführen.

In diesem Fall erstellen wir eine Datei a mit dem Inhalt 0 und ersetzen anschließend inline die 0 bis $ (whoami), die beim Ausführen der Subshell während des Parses des Befehls den Benutzernamen roel ersetzt. Achten Sie darauf, keine einfachen Anführungszeichen zu verwenden, da dies die untergeordnete Shell inaktiv macht, da die Zeichenfolge als Literaltext interpretiert wird:

echo '0' > a
sed -i 's|0|$(whoami)|' ./a
cat a
$(whoami)

Beachten Sie hier, dass die sed-aktivierte Syntax (s|0|...|) immer noch korrekt funktioniert (!), während die Bash-Subshell-Funktionalität $() dies nicht tat!

Beispiel 4: Verwenden von eval und einer for-Schleife

LOOPS=3
echo {1..${LOOPS}}
{1..3}
eval echo {1..${LOOPS}}
1 2 3
for i in $(echo {1..${LOOPS}}); do echo "${i}"; done
{1..3}
for i in $(eval echo {1..${LOOPS}}); do echo "${i}"; done
1
2
3

Dieses Beispiel ist zwar nicht der optimale Weg, um eine einfache for-Schleife durchzuführen, zeigt uns aber einige Möglichkeiten, Subshells auch innerhalb von Schleifen zu integrieren. Wir verwenden die eval-Anweisung, um den {1..3}-Text in 1 2 3 zu verarbeiten, der dann direkt in der for-Schleifenwiederholungsklausel verwendet werden kann.

Manchmal ist die Verwendung von Subshells und die Bereitstellung von Informationen im Kontext über Subshells nicht immer selbstverständlich und erfordert möglicherweise einige Tests, Optimierungen und Feinabstimmungen, bevor die Subshells wie erwartet ausgeführt werden. Das ist normal und entspricht weitgehend der normalen Bash-Codierung.

Schlussfolgerung

In diesem Artikel haben wir einige ausführlichere und fortgeschrittene Beispiele für die Verwendung von Subshells in Bash untersucht. Die Leistungsfähigkeit von Subshells ermöglicht es Ihnen, die meisten einzeiligen Skripte in viel leistungsfähigere Versionen davon umzuwandeln, ganz zu schweigen von der Möglichkeit, sie in Ihren Skripten zu verwenden. Wenn Sie anfangen, Subshells zu erkunden und einige gute Möglichkeiten finden, sie zu verwenden, posten Sie sie bitte unten in den Kommentaren!

Genießen!

Verwandte Artikel: