Shellkonfiguration – Übersicht
Aliase sind alternative Aufrufformen für ein Kommando oder eine Kommandozeile (bzw. Teile davon). Typischerweise sind es Abkürzungen für sehr häufig benötigte Kommandos (z.B. ls
mit Optionen), selten benutzte Kommandos, deren Details man leicht vergisst, oder sehr umfangreiche Kommandozeilen. Ein Alias ist eine rein textuelle Ersetzung, vergleichbar mit einem Readline-macro. Insbesondere kann ein Alias nicht mit Argumenten arbeiten (man kann allerdings weitere Worte in der Kommandozeile haben, die dann von dem aus dem Alias generierten Kommando ausgewertet werden).
Eine Shellfunktion ist ein mächtigeres Konstrukt, das ein Alias in fast jeder Hinsicht ersetzen kann. Insbesondere können Funktionen mit Argumenten arbeiten.
Theoretisch können auch Aliase komplexen Inhalt haben, aber typischerweise verbergen sich hinter einem Alias kurze Kommandozeilen und hinter einer Funktion mehrere Kommandos.
Aliase und Funktionen sind eine verbreitete Möglichkeit für Distributionen, das Benutzererlebnis (von Leuten ohne ausgeprägte Shell-Kenntnisse) zu verbessern, indem sinnvolle Optionen voreingestellt werden (z.B. --color=auto
für grep
).
Wenn ein externes Kommando verwendet werden soll, auch wenn ein Alias oder eine Funktion (oder ein shell builtin) dafür definiert ist, kann man entweder das Kommandowort (teilweise) mit quoting versehen (\grep
statt grep
) oder das shell builtin command
voranstellen: command grep
. Dies ist insbesondere relevant, wenn man ein Kommando mit einer Funktion überlagert und innerhalb der Funktion das externe Kommando aufruft.
Eine Alias-Definition hat die Form alias ..='cd ..'
. Die Definition eines konkreten Alias kann man sich mit type cmd
oder alias cmd
anzeigen lassen. Löschen kann man den Alias mit unalias cmd
. Eine Liste aller Alias-Definitionen kann man mit alias
ausgeben. Die Auswertung von Aliasen erfolgt standardmäßig nur in interaktiven Shells (lässt sich mit shopt expand_aliases
konfigurieren). In Shellscripten ist die Nutzung von Funktionen und Variablen sinnvoller.
Normalerweise wird immer nur das erste Wort eines simple command darauf überprüft, ob ein Alias dafür definiert ist. Wenn aber das letzte Zeichen einer Alias-Definition ein Leerzeichen ist und auf das Alias-Wort ein weiteres Wort folgt, wird auch das darauf überprüft, ob dafür ein Alias definiert ist. In dieser Weise kann man also auch für Argumente ein Alias anlegen. Ich habe von dieser Möglichkeit bisher nie Gebrauch gemacht, aber das könnte sinnvoll sein, wenn man häufig dieselben langen Argumente für ein Kommando hat. Ein einzelnes langes Argument könnte man genauso in eine Variable packen (was den Vorteil hätte, dass man sie überall verwenden kann). Aber etwas wie arg1
tippt sich angenehmer als ${arg1[@]}
:
ec:0 01:08:07 hl@notebook:~ start cmd:> alias ald='ls -ld ' ec:0 01:08:23 hl@notebook:~ start cmd:> alias arg1='/etc/pki/trust/anchors /usr/lib/os-probes/mounted/efi /usr/share/vim/vim91/autoload' ec:0 01:09:09 hl@notebook:~ start cmd:> set -x ec:0 01:09:11 hl@notebook:~ start cmd:> ald arg1 + ls -ld /etc/pki/trust/anchors /usr/lib/os-probes/mounted/efi /usr/share/vim/vim91/autoload drwxr-xr-x 1 root root 0 26. Jul 17:15 /etc/pki/trust/anchors drwxr-xr-x 1 root root 36 28. Apr 2023 /usr/lib/os-probes/mounted/efi drwxr-xr-x 1 root root 1190 16. Aug 01:43 /usr/share/vim/vim91/autoload ec:0 01:13:44 hl@notebook:~ start cmd:> arg1=(/etc/pki/trust/anchors /usr/lib/os-probes/mounted/efi /usr/share/vim/vim91/autoload) ec:0 01:13:46 hl@notebook:~ start cmd:> ald ${arg1[@]} drwxr-xr-x 1 root root 0 26. Jul 17:15 /etc/pki/trust/anchors drwxr-xr-x 1 root root 36 28. Apr 2023 /usr/lib/os-probes/mounted/efi drwxr-xr-x 1 root root 1190 16. Aug 01:43 /usr/share/vim/vim91/autoload
Alias | Definition | Anmerkungen |
---|---|---|
.. |
cd .. |
|
... |
cd ../.. |
|
l |
ls -lhL |
o.ä. |
ll |
ls -l |
o.ä. |
la |
ls -la |
o.ä. |
Alias | Definition | Anmerkungen |
---|---|---|
r |
realpath |
Häufig brauche ich den kompletten Pfad einer Datei im aktuellen Verzeichnis. realpath erspart einem das Zusammenkopieren des Verzeichnispfads und des Dateinamens. |
ssrv |
systemctl --type=service list-units |
Funktioniert auch mit filtern pattern: ssrv 'sy*' |
Alias | Definition | Anmerkungen |
---|---|---|
grep |
grep --color=auto |
|
egrep |
egrep --color=auto |
|
fgrep |
fgrep --color=auto |
Alias | Definition | Anmerkungen |
---|---|---|
less |
less -WiNS |
-W markiert die erste ungelesene Zeile; -i sorgt bei Suchen für das Ignorieren von Groß- und Kleinschreibung; -N zeigt Zeilennummern an (kann zur Laufzeit durch wiederholte Eingabe von -n umgeschaltet werden); -S verhindert das Umbrechen von Zeilen, die nicht auf den Bildschirm passen |
od |
od -t c -t x1 |
Anzeige der einzelnen Bytes sowohl als ASCII / Escape-Sequenz als auch in hex. |
ps |
ps -eo pid,ppid,user,stat,time,etime,args |
Man kann dem Vergessen von Kommandonamen oder dem Pfad von eigenen Scripten (gerade bei Usern, die sie nur selten verwenden) entgegenwirken, indem man ein einheitliches Präfix definiert (etwa eine Abkürzung des Firmennamens, org-
) und für jedes Kommando oder jede Kommandozeile einen Alias definiert. Auf diese Weise kann man sich mittels tab completion trivial die Liste der relevanten Kommandos ausgeben lassen, indem man o r g - Tab Tab eingibt.
Für die einfache Eingabe des Kommandoworts gibt es (ab bash 5.x) eine Alternative zu Aliasen: programmable completion.
Wenn man keine Lust hat, ständig systemctl
oder journalctl
einzugeben, kann man sich entweder Aliase definieren (alias s=systemctl
oder alias j=journalctl
) oder programmable completion nutzen, so dass die Eingabe von s Tab das s
zu systemctl
und die Eingabe von j Tab das j
zu journalctl
expandiert:
complete -W 'systemctl journalctl' -o bashdefault -I
Die Kombination eines Alias mit programmable completion ermöglicht eine engere Vervollständigungs-Auswahl für den Alias:
ec:0 16:46:50 hl@notebook:~ start cmd:> complete -W '/etc/pki/trust/anchors /usr/lib/os-probes/mounted/efi /usr/share/vim/vim91/autoload' -- -l -c ec:0 16:47:49 hl@notebook:~ start cmd:> alias -l='ls -lhL' ec:0 16:47:57 hl@notebook:~ start cmd:> alias -c='cd' ec:0 16:48:04 hl@notebook:~ start cmd:> -l / /etc/pki/trust/anchors /usr/lib/os-probes/mounted/efi /usr/share/vim/vim91/autoload
Mit etwas mehr Konfigurationsaufwand (einer eigenen Funktion für die tab completion des Alias) kann man den Tippaufwand weiter reduzieren, so dass man die Argumente mit einem einzelnen Buchstaben auswählen kann. Die Shellfunktion:
_PROG_COMPL_ABBR_ARGS () { local keys=( e o a ) local DEFAULT_COMPLETIONS_EVAL_STRING='local -A PROG_COMPL_ABBR_ARGS=( [e]=/etc/pki/trust/anchors [o]=/usr/lib/os-probes/mounted/efi [a]=/usr/share/vim/vim91/autoload ['?']=help )' local word="${COMP_WORDS[$COMP_CWORD]}" if ! declare -p PROG_COMPL_ABBR_ARGS >/dev/null 2>&1; then eval "$DEFAULT_COMPLETIONS_EVAL_STRING" fi if [ -z "$word" ]; then COMPREPLY=( "${!PROG_COMPL_ABBR_ARGS[@]}" ) return 0 fi if [ "$word" = '?' ]; then COMPREPLY=( ) echo >&2 for key in "${keys[@]}"; do printf '%2s : %s\n' "${key}" "${PROG_COMPL_ABBR_ARGS["${key}"]}" >&2 done return 0 fi if [ "${PROG_COMPL_ABBR_ARGS["$word"]:+set}" = 'set' ]; then COMPREPLY=( "${PROG_COMPL_ABBR_ARGS["$word"]}" ) else COMPREPLY=( "$word" ) fi }
Da die Anzeige der verfügbaren Abkürzungen allein einem nicht wirklich weiterhilft, kann man sich mit ? Tab die Bedeutungen anzeigen lassen.
ec:0 17:45:54 hl@notebook:~ start cmd:> . completion.PROG_COMPL_ABBR_ARGS.sh ec:0 18:05:48 hl@notebook:~ start cmd:> complete -F _PROG_COMPL_ABBR_ARGS -- -l -c ec:0 18:05:55 hl@notebook:~ start cmd:>
Die Eingabe von - l Space e Tab ändert dann -l e
in -l /etc/pki/trust/anchors
.
Indem man z.B. mit declare -A PROG_COMPL_ABBR_ARGS=([y]="/etc/systemd/system" )
die assoziative Array-Variable in der aufrufenden Shell setzt, kann man die Standardkonfiguration der Funktion überschreiben.
Shellfunktionen sind, besser als Aliase, dafür geeignet, Funktionalität zu implementieren, die man in der Shell häufig benötigt.
Eine Funktion wird folgendermaßen angelegt (oder überschrieben):
func_name () { : some shell code; }
Die Definition einer Shellfunktion kann man mit type func_name
und declare -fp func_name
anzeigen lassen.
Shellfunktionen sind typischerweise in großem Umfang im Hintergrund aktiv, ohne dass der Benutzer dies mitbekommt. Dies betrifft vor allem die Nutzung von programmable completion.
Hier nun einige Beispiele für Shellfunktionen, die ich mir gebaut habe:
screen
-WrapperWenn bereits eine screen
-Session läuft, dann sollte man durch das Kommando screen
mit dieser Session verbunden werden. Diese Funktion prüft, ob bereits eine Session mit dem jeweiligen Namen existiert. Wenn ja, wird mit ihr verbunden; wenn nicht, wird eine gestartet.
Wenn screen
in einer root-Shell aufgerufen wird, sollten nicht unterschiedliche User dieselbe Sitzung nutzen. Diese Funktion setzt deshalb den Session-Namen (je nach Konfiguration) auf den Wert in /proc/self/loginuid
oder auf den zugehörigen Usernamen.
screen () { local session_name loginuid login_name count local use_value='name' # 'name' | 'uid' if [ "$EUID" -eq 0 ]; then if [ -f '/proc/self/loginuid' ]; then loginuid="$(< /proc/self/loginuid)" if [ "$use_value" = 'name' ]; then login_name="$( id -un "$loginuid" 2>/dev/null )" if [ -n "$login_name" ]; then session_name="$login_name" else session_name="$loginuid" fi else session_name="$loginuid" fi else session_name='root' fi else session_name="$USER" fi if [ $# -eq 0 ]; then count="$(command screen -ls | grep -cE $'^\t''[0-9]+[.]'"${session_name}"$'\t')" if [ "$count" -eq 1 ]; then command screen -S "$session_name" -x elif [ "$count" -eq 0 ]; then command screen -S "$session_name" else echo "ERROR: Es laufen mehrere screen-Sitzungen mit dem Namen '${session_name}'; Abbruch" fi else command screen "$@" fi }
backup-file
Es kommt häufig vor, dass man ein Backup einer Datei anlegen will, bevor man sie bearbeitet. Diese Funktion braucht nur den Dateinamen (oder Dateipfad) als Argument, fügt das Datum in den Dateinamen ein, vermeidet das Überschreiben früherer Backups und erhält die Metadaten (Zeitstempel, Besitzer, Gruppe, Zugriffsrechte).
backup-file () { local path date_string suffix='.bak' new_path if [ $# -ne 1 ]; then echo "ERROR: argument number is not 1" echo "${FUNCNAME[0]} <file path>" return 1 fi path="$1" if [ ! -f "$path" ]; then echo "ERROR: the argument path does not exist or is nor a regular file" echo "${FUNCNAME[0]} <file path>" return 1 fi date_string="$(date +%Y-%m-%d)" new_path="${path}.${date_string}${suffix}" if [ -e "$new_path" ]; then echo "ERROR: there is already a file at the backup path" command ls -lh "$new_path" echo "${FUNCNAME[0]} <file path>" return 1 fi if ( set -x; cp -p --no-clobber "$path" "$new_path" ); then echo "Backup file created successfully" command ls -lh "$path" "$new_path" return 0 else echo "ERROR: Backup file creation failed" return 1 fi }
lcat
: Anzeige von Datei- und Verzeichnis-Inhalten Wenn ich mich im Dateisystem umsehe, wechsele ich vielfach zwischen der Anzeige eines Verzeichnisinhalts und der Anzeige des Inhalts von Textdateien. Um diesen Prozess zu beschleunigen, habe ich eine Funktion geschrieben, die das Argument entsprechend behandelt, so dass ich das Kommando nicht austauschen muss (alternativ könnte man wohl auch einfach einen Dateimanager wie mc
verwenden...); eine Mischung aus ls
und cat
, daher der Name lcat
:
lcat () { test $# -eq 0 && return 1 local file for file; do if [ ! -e "$file" ]; then echo "File '${file}' does not exist; skipping" continue fi if [ -f "$file" ]; then echo "cat regular file '${file}':" cat "$file" continue fi # else echo "ls -ld '${file}':" command ls -ld "$file" echo echo ----------------------------------- echo echo "ls -l '${file}':" command ls -l "$file" done }
psp
: detaillierte ps
-Ausgabe für die übergebene PIDpsp () { test $# -ne 1 && return 1 if [[ $1 =~ ^[1-9][0-9]*$ ]]; then pid="$1" else echo "ERROR: Das Argument ('${1}') ist ungültig; Abbruch" return 1 fi if [ ! -d "/proc/${pid}" ]; then echo "ERROR: Es existiert kein Prozess mit der PID '${1}'; Abbruch" return 1 fi /bin/ps -o 'pid,ppid,pgid,sess,nlwp,euser,nice,class,rtprio,stat,tty,time,etime,args' -p "$pid" | cat }
pspv
: noch detailliertere ps
-Ausgabe für die übergebene PIDpspv () { test $# -ne 1 && return 1 if [[ $1 =~ ^[1-9][0-9]*$ ]]; then pid="$1" else echo "ERROR: Das Argument ('${1}') ist ungültig; Abbruch" return 1 fi if [ ! -d "/proc/${pid}" ]; then echo "ERROR: Es existiert kein Prozess mit der PID '${1}'; Abbruch" return 1 fi /bin/ps -o 'pid,ppid,sess,nlwp,ruser,euser,userns,mntns,netns,nice,class,rtprio,stat,tty,rss,maj_flt,time,etime,unit,args' -p "$pid" | cat }