Als Systemadministrator gehören Shells zum täglichen Betrieb. Shells bieten oft mehr Optionen und Flexibilität als eine grafische Benutzeroberfläche (GUI). Sich täglich wiederholende Aufgaben können einfach durch Skripte automatisiert werden, oder Aufgaben können so geplant werden, dass sie zu bestimmten Zeiten während des Tages ausgeführt werden. Eine Shell bietet eine bequeme Möglichkeit, mit dem System zu interagieren, und ermöglicht es Ihnen, in kürzerer Zeit mehr zu tun. Es gibt viele verschiedene Shells, darunter Bash, zsh, tcsh und PowerShell.
In diesem zweiteiligen Blogbeitrag teile ich einige der Bash-Einzeiler, die ich verwende, um meine Arbeit zu beschleunigen und mehr Zeit zum Kaffeetrinken zu lassen. In diesem ersten Beitrag werde ich den Verlauf, die letzten Argumente, die Arbeit mit Dateien und Verzeichnissen, das Lesen von Dateiinhalten und Bash-Funktionen behandeln. In Teil zwei werde ich Shell-Variablen, den Find-Befehl, Dateideskriptoren und das Ausführen von Operationen aus der Ferne untersuchen.
Verwenden Sie den Verlaufsbefehl
Der history
Befehl ist praktisch. history
ermöglicht mir zu sehen, welche Befehle ich auf einem bestimmten System ausgeführt habe oder welche Argumente an diesen Befehl übergeben wurden. Ich verwende history
um Befehle erneut auszuführen, ohne sich an irgendetwas erinnern zu müssen.
Die Aufzeichnung der letzten Befehle wird standardmäßig in ~/.bash_history.
gespeichert Dieser Speicherort kann geändert werden, indem die Shell-Variable HISTFILE geändert wird. Es gibt andere Variablen, wie HISTSIZE (Zeilen, die für die aktuelle Sitzung im Speicher gespeichert werden sollen) und HISTFILESIZE (wie viele Zeilen in der Verlaufsdatei aufbewahrt werden sollen). Wenn Sie mehr über history
erfahren möchten , siehe man bash
.
Nehmen wir an, ich führe den folgenden Befehl aus:
$> sudo systemctl status sshd
Bash sagt mir, dass der sshd-Dienst nicht läuft, also möchte ich als Nächstes den Dienst starten. Ich hatte seinen Status mit meinem vorherigen Befehl überprüft. Dieser Befehl wurde im history
gespeichert , damit ich darauf verweisen kann. Ich führe einfach aus:
$> !!:s/status/start/
sudo systemctl start sshd
Der obige Ausdruck hat folgenden Inhalt:
- !! - den letzten Befehl aus dem Verlauf wiederholen
- :s/status/start/ - ersetzt status mit Start
Als Ergebnis wird der sshd-Dienst gestartet.
Als Nächstes erhöhe ich den Standardwert HISTSIZE von 500 auf 5000, indem ich den folgenden Befehl verwende:
$> echo “HISTSIZE=5000” >> ~/.bashrc && source ~/.bashrc
Was ist, wenn ich die letzten drei Befehle in meinem Verlauf anzeigen möchte? Ich gebe ein:
$> history 3
1002 ls
1003 tail audit.log
1004 history 3
Ich führe tail
aus auf audit.log
durch Bezugnahme auf die Verlaufszeilennummer. In diesem Fall verwende ich Zeile 1003:
$> !1003
tail audit.log
..
..
Stellen Sie sich vor, Sie haben etwas von einem anderen Terminal oder Ihrem Browser kopiert und Sie fügen versehentlich die Kopie (die Sie im Kopierpuffer haben) in das Terminal ein. Diese Zeilen werden im Verlauf gespeichert, was Sie hier nicht möchten. Hier also unset HISTFILE &&beenden ist praktisch
$> unset HISTFILE && exit
oder
$> kill -9 $$
Letztes Argument des vorherigen Befehls referenzieren
Wenn ich Verzeichnisinhalte für verschiedene Verzeichnisse auflisten möchte, kann es sein, dass ich ziemlich oft zwischen den Verzeichnissen wechsle. Es gibt einen netten Trick, mit dem Sie auf das letzte Argument des vorherigen Befehls verweisen können. Zum Beispiel:
$> pwd
/home/username/
$> ls some/very/long/path/to/some/directory
foo-file bar-file baz-file
Im obigen Beispiel /some/very/long/path/to/some/directory
ist das letzte Argument des vorherigen Befehls.
Wenn ich cd
möchte (Verzeichnis wechseln) zu diesem Ort, gebe ich etwa Folgendes ein:
$> cd $_
$> pwd
/home/username/some/very/long/path/to/some/directory
Verwenden Sie jetzt einfach einen Bindestrich, um dorthin zurückzukehren, wo ich war:
$> cd -
$> pwd
/home/username/
Dateien und Verzeichnisse bearbeiten
Stellen Sie sich vor, ich möchte eine Verzeichnisstruktur erstellen und eine Reihe von Dateien mit unterschiedlichen Erweiterungen in diese Verzeichnisse verschieben.
Zuerst erstelle ich die Verzeichnisse auf einmal:
$> mkdir -v dir_{rpm,txt,zip,pdf}
mkdir: created directory 'dir_rpm'
mkdir: created directory 'dir_txt'
mkdir: created directory 'dir_zip'
mkdir: created directory 'dir_pdf'
Als nächstes verschiebe ich die Dateien basierend auf der Dateierweiterung in jedes Verzeichnis:
$> mv -- *.rpm dir_rpm/
$> mv -- *.pdf dir_pdf/
$> mv -- *.txt dir_txt/
$> mv -- *.zip dir_txt/
Die doppelten Bindestriche --
bedeuten Ende der Optionen. Dieses Flag verhindert, dass Dateien, die mit einem Bindestrich beginnen, als Argumente behandelt werden.
Als nächstes möchte ich alle *.txt-Dateien in *.log-Dateien ersetzen/verschieben, also gebe ich ein:
$> for f in ./*.txt; do mv -v ”$file” ”${file%.*}.log”; done
renamed './file10.txt' -> './file10.log'
renamed './file1.txt' -> './file1.log'
renamed './file2.txt' -> './file2.log'
renamed './file3.txt' -> './file3.log'
renamed './file4.txt' -> './file4.log'
Anstatt den for
zu verwenden Schleife oben, kann ich den prename
installieren befehlen und das obige Ziel wie folgt erreichen:
$> prename -v 's/.txt/.log/' *.txt
file10.txt -> file10.log
file1.txt -> file1.log
file2.txt -> file2.log
file3.txt -> file3.log
file4.txt -> file4.log
Wenn ich eine Konfigurationsdatei ändere, erstelle ich oft eine Sicherungskopie der Originaldatei, indem ich einen einfachen Kopierbefehl verwende. Zum Beispiel:
$> cp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.back
Wie Sie sehen können, ist das Wiederholen des gesamten Pfads und das Anhängen von .back an die Datei nicht so effizient und wahrscheinlich fehleranfällig. Es gibt einen kürzeren, saubereren Weg, dies zu tun. Hier kommt es:
$> cp /etc/sysconfig/network-scripts/ifcfg-eth0{,.back}
Sie können verschiedene Überprüfungen von Dateien oder Variablen durchführen. Führen Sie help test
aus für weitere Informationen.
Verwenden Sie den folgenden Befehl, um festzustellen, ob eine Datei ein symbolischer Link ist:
$> [[ -L /path/to/file ]] && echo “File is a symlink”
Hier ist ein Problem, auf das ich kürzlich gestoßen bin. Ich wollte mehrere Dateien auf einmal komprimieren/enttarnen. Ohne nachzudenken, tippte ich:
$> tar zxvf *.gz
Das Ergebnis war:
tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors
Die Tar-Dateien waren:
iptables.tar.gz
openvpn.tar.gz
…..
Warum hat es nicht funktioniert und warum sollte ls -l *.gz
stattdessen arbeiten? Unter der Haube sieht es so aus:
$> tar zxvf *.gz
Wird wie folgt transformiert:
$> tar zxvf iptables.tar.gz openvpn.tar.gz
tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors
Der tar
Der Befehl findet voraussichtlich openvpn.tar.gz in iptables.tar.gz. Ich habe das mit einem einfachen for
gelöst Schleife:
$> for f in ./*.gz; do tar zxvf "$f"; done
iptables.log
openvpn.log
Ich kann sogar zufällige Passwörter generieren, indem ich Bash verwende! Hier ist ein Beispiel:
$> alphanum=( {a..z} {A..Z} {0..9} ); for((i=0;i<=${#alphanum[@]};i++)); do printf '%s' "${alphanum[@]:$((RANDOM%255)):1}"; done; echo
Hier ist ein Beispiel, das OpenSSL verwendet:
$> openssl rand -base64 12
JdDcLJEAkbcZfDYQ
Eine Datei Zeile für Zeile lesen
Angenommen, ich habe eine Datei mit vielen IP-Adressen und möchte mit diesen IP-Adressen arbeiten. Zum Beispiel möchte ich dig
ausführen zum Abrufen von Reverse-DNS-Informationen für die in der Datei aufgeführten IP-Adressen. Ich möchte auch IP-Adressen überspringen, die mit einem Kommentar (# oder Hashtag) beginnen.
Ich werde fileA als Beispiel verwenden. Sein Inhalt ist:
10.10.12.13 some ip in dc1
10.10.12.14 another ip in dc2
#10.10.12.15 not used IP
10.10.12.16 another IP
Ich könnte jede IP-Adresse kopieren und einfügen und dann dig
ausführen manuell:
$> dig +short -x 10.10.12.13
Oder ich könnte dies tun:
$> while read -r ip _; do [[ $ip == \#* ]] && continue; dig +short -x "$ip"; done < ipfile
Was ist, wenn ich die Spalten in Datei A tauschen möchte? Zum Beispiel möchte ich IP-Adressen in die Spalte ganz rechts einfügen, damit fileA so aussieht:
some ip in dc1 10.10.12.13
another ip in dc2 10.10.12.14
not used IP #10.10.12.15
another IP 10.10.12.16
Ich führe aus:
$> while read -r ip rest; do printf '%s %s\n' "$rest" "$ip"; done < fileA
Bash-Funktionen verwenden
Funktionen in Bash unterscheiden sich von denen, die in Python, C, awk oder anderen Sprachen geschrieben wurden. In Bash würde eine einfache Funktion, die ein Argument akzeptiert und "Hallo Welt" ausgibt, so aussehen:
func() { local arg=”$1”; echo “$arg” ; }
Ich kann die Funktion so aufrufen:
$> func foo
Manchmal ruft sich eine Funktion selbst rekursiv auf, um eine bestimmte Aufgabe auszuführen. Zum Beispiel:
func() { local arg="$@"; echo "$arg"; f "$arg"; }; f foo bar
Diese Rekursion wird ewig laufen und viele Ressourcen verbrauchen. In Bash können Sie FUNCNEST verwenden, um die Rekursion zu begrenzen. Im folgenden Beispiel setze ich FUNCNEST=5, um die Rekursion auf fünf zu begrenzen.
func() { local arg="$@"; echo "$arg"; FUNCNEST=5; f "$arg"; }; f foo bar
foo bar
foo bar
foo bar
foo bar
foo bar
bash: f: maximum function nesting level exceeded (5)
Verwenden Sie eine Funktion, um die neueste oder älteste Datei abzurufen
Hier ist eine Beispielfunktion, um die neueste Datei in einem bestimmten Verzeichnis anzuzeigen:
latest_file()
{
local f latest
for f in "${1:-.}"/*
do
[[ $f -nt $latest ]] && latest="$f"
done
printf '%s\n' "$latest"
}
Diese Funktion zeigt die älteste Datei in einem bestimmten Verzeichnis an:
oldest_file()
{
local f oldest
for file in "${1:-.}"/*
do
[[ -z $oldest || $f -ot $oldest ]] && oldest="$f"
done
printf '%s\n' "$oldest"
}
Dies sind nur einige Beispiele dafür, wie Sie Funktionen in Bash verwenden können, ohne andere externe Befehle aufzurufen.
Manchmal erwische ich mich dabei, einen Befehl immer wieder mit vielen Parametern einzugeben. Ein Befehl, den ich oft verwende, ist kubectl
(Kubernetes-CLI). Ich bin es leid, diesen langen Befehl auszuführen! Hier ist der ursprüngliche Befehl:
$> kubectl -n my_namespace get pods
oder
$> kubectl -n my_namespace get rc,services
Diese Syntax erfordert, dass ich -n my_namespace
manuell einfüge Jedes Mal, wenn ich den Befehl ausführe. Es gibt einen einfacheren Weg, dies mit einer Funktion zu tun:
$> kubectl () { command kubectl -n my_namespace ”$@” ; }
Jetzt kann ich kubectl
ausführen ohne -n namespace
eingeben zu müssen jedes Mal:
$> kubectl get pods
Ich kann die gleiche Technik auf andere Befehle anwenden.
Abschluss
Dies sind nur einige hervorragende Tricks, die es für Bash gibt. Im zweiten Teil werde ich einige weitere Beispiele zeigen, einschließlich der Verwendung von find und der Remote-Ausführung. Ich ermutige Sie, diese Tricks zu üben, um Ihre Verwaltungsaufgaben über die Befehlszeile einfacher und genauer zu machen.
[ Kostenloser Online-Kurs:Technischer Überblick zu Red Hat Enterprise Linux. ]