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 -o filenames -A file -- -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-fileEs 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
}