Shellkonfiguration – Übersicht

Shelloptionen (de)aktivieren (bash)

In den über 30 Jahren seit der Veröffentlichung von bash sind einige Optionen hinzugekommen. Diese werden sinnvollerweise nicht standardmäßig aktiviert. Es kann sogar sinnvoll sein, Features, die man nicht nutzt (bzw. nicht beherrscht) zu deaktivieren, damit sie nicht versehentlich Schaden anrichten (s.u.).

Bei der Verwendung in Shellscripten muss man gegebenenfalls daran denken, dass diese Funktion auf älteren Systemen nicht zur Verfügung steht, so dass man in eine problematische Situation geraten kann, wenn das Script auf älteren Systemen genutzt wird.

Die beiden wichtigsten Methoden zur Änderung der Shellkonfiguration sind die bash-Builtins (interne Kommandos) shopt und set. Beide Kommandos können die aktuelle Konfiguration ausgeben (column -t für die besser lesbare Formatierung):

ec:0   03:09:50  hl@notebook:~
start cmd:> bash --version
GNU bash, Version 5.2.26(1)-release (x86_64-suse-linux)
[...]

ec:0   03:11:14  hl@notebook:~
start cmd:> shopt | column -t
autocd                   off
assoc_expand_once        off
cdable_vars              off
cdspell                  off
checkhash                off
checkjobs                off
checkwinsize             on
cmdhist                  on
compat31                 off
compat32                 off
compat40                 off
compat41                 off
compat42                 off
compat43                 off
compat44                 off
complete_fullquote       on
direxpand                off
dirspell                 off
dotglob                  off
execfail                 off
expand_aliases           on
extdebug                 off
extglob                  on
extquote                 on
failglob                 on
force_fignore            on
globasciiranges          on
globskipdots             on
globstar                 off
gnu_errfmt               off
histappend               on
histreedit               off
histverify               on
hostcomplete             off
huponexit                off
inherit_errexit          off
interactive_comments     on
lastpipe                 off
lithist                  off
localvar_inherit         off
localvar_unset           off
login_shell              off
mailwarn                 off
no_empty_cmd_completion  off
nocaseglob               off
nocasematch              off
noexpand_translation     off
nullglob                 off
patsub_replacement       on
progcomp                 on
progcomp_alias           off
promptvars               on
restricted_shell         off
shift_verbose            off
sourcepath               on
varredir_close           off
xpg_echo                 off

ec:0   03:15:21  hl@notebook:~
start cmd:> set -o | column -t
allexport             off
braceexpand           on
emacs                 on
errexit               off
errtrace              off
functrace             off
hashall               on
histexpand            on
history               on
ignoreeof             off
interactive-comments  on
keyword               off
monitor               on
noclobber             off
noexec                off
noglob                off
nolog                 off
notify                off
nounset               off
onecmd                off
physical              off
pipefail              off
posix                 off
privileged            off
verbose               off
vi                    off
xtrace                off

Diese Seite behandelt nur einen kleinen Bruchteil dieser Funktionen (und nur oder jedenfalls überwiegend diejenigen, die nicht standardmäßig aktiviert sind). Es bietet sich an, die Bedeutung aller Funktionen mal nachzulesen, damit man einen Überblick bekommt, was die Shell alles kann. Die man-page von bash ist riesig, aber mit den folgenden Kommandos kann man direkt an die richtige Stelle springen:

set man --pager="less --pattern='^\s*set \['" bash
shopt man --pager="less --pattern='^\s*shopt \['" bash

Einige nützliche Shelloptionen

shopt options

Diese Optionen kann man mit shopt -s <optionname> aktivieren und mit shopt -u <optionname> deaktivieren.

dotglob

Standardmäßig erfasst globbing (echo *vimrc, echo ?vimrc, echo [.]vimrc) nur diejenigen Dateinamen (im weiteren Sinn), die nicht mit einem Punkt beginnen; man muss den Punkt am Anfang explizit angeben (dass [.] nicht als explizit durchgeht, kann man schon erheiternd finden...). Will man beide Gruppen erfassen, braucht man etwas wie echo * .*. Das ist aber (potentiell) problematisch, wenn man nullglob nicht verwenden kann (und macht die alphabetische Sortierung kaputt). dotglob ändert dieses Verhalten, so dass echo * alle Dateinamen erfasst.

extglob

Mit dieser Option schaltet man eine Art regulärer Ausdruck für globbing frei, allerdings mit anderer Syntax.

pattern‐list pattern pattern|pattern pattern|pattern|pattern...
?(pattern‐list) Matches zero or one occurrence of the given patterns
*(pattern‐list) Matches zero or more occurrences of the given patterns
+(pattern‐list) Matches one or more occurrences of the given patterns
@(pattern‐list) Matches one of the given patterns
!(pattern‐list) Matches anything except one of the given patterns

Beispiele:

failglob

Wenn ein globbing pattern keine Treffer generiert, will man zumeist, dass die fragliche Operation gar nicht ausgeführt wird; analog zu for filename in *.txt; do echo "$filename"; done, wenn nullglob aktiviert ist.

Diese Option sorgt dafür, dass die Shell das Kommando gar nicht erst ausführt:

ec:0   19:59:53  hl@notebook:~
start cmd:> for filename in *.txt; do echo "$filename"; done
bash: Keine Entsprechung: *.txt

ec:1   19:59:59  hl@notebook:~
start cmd:> ls -ld *.txt
bash: Keine Entsprechung: *.txt

ec:1   20:00:32  hl@notebook:~
start cmd:>

globstar

Diese Option aktiviert einen zusätzlichen globbing-Ausdruck (bzw. ändert dessen Bedeutung): **

* und ? beinhalten keine /, sie erfassen also nur Dateien (im weiteren Sinn) im jeweiligen Verzeichnis. ** erfasst alle Dateien in dem jeweiligen Verzeichnis und in beliebig tiefer liegenden Unterverzeichnissen. **/ erfasst alle Verzeichnisse im jeweiligen Verzeichnis und in beliebig tiefer liegenden Unterverzeichnissen:

start cmd:> echo /var/**/S*Trust*CA.pem
/var/lib/ca-certificates/openssl/SecureTrust_CA.pem /var/lib/ca-certificates/pem/SecureTrust_CA.pem

lastpipe

Wenn man Daten in einer Pipeline aufbereitet und in der letzten Komponente der Pipeline einer Variable zuweisen will, stößt man auf das Problem, dass diese Variable nicht im scope der aufrufenden Shell existiert. Sie wird in einer subshell erzeugt oder beschrieben, und diese Änderung ist weg, sobald die Ausführung der Pipeline beendet ist:

start cmd:> cmd1 | cmd2 | while read value; do : whatever; if : whatever; then var="$value"; fi; done

Dieses Problem kann man in vielen Fällen umgehen, indem man die Pipeline "wegoptimiert", etwa durch die (explizite) Nutzung einer temporären Datei oder die implizite Variante:

while read value; do : whatever; if : whatever; then var="$value"; fi; done <<< "$( cmd1 | cmd2 )"

In Scripten (genauer: wenn job control ist deaktiviert) kann man über diese Option erreichen, dass die letzten Komponente einer Pipeline im aktuellen Shellkontext läuft:

ec:0   22:50:07  hl@notebook:~/scripts
start cmd:> cat test.sh
declare -p var
echo foo | read var
declare -p var
shopt -s lastpipe
echo foo | read var
declare -p var

ec:0   22:50:37  hl@notebook:~/scripts
start cmd:> bash -x test.sh
+ declare -p var
test.sh: Zeile 1: declare: var: Nicht gefunden.
+ echo foo
+ read var
+ declare -p var
test.sh: Zeile 3: declare: var: Nicht gefunden.
+ shopt -s lastpipe
+ read var
+ echo foo
+ declare -p var
declare -- var="foo"

ec:0   22:50:49  hl@notebook:~/scripts
start cmd:>

nocaseglob

Diese Option deaktiviert die Unterscheidung von Groß- und Kleinschreibung beim globbing (pathname expansion).

ec:0   00:33:38  hl@notebook:~
start cmd:> echo WL*
wlan.txt

nocasematch

Diese Option deaktiviert die Unterscheidung von Groß- und Kleinschreibung in case-Ausdrücken und beim pattern matching in [[ ]]-Ausdrücken.

ec:0   00:37:05  hl@notebook:~
start cmd:> [[ FOO = f* ]]; echo $?
1

ec:0   00:37:24  hl@notebook:~
start cmd:> shopt -s nocasematch

ec:0   00:37:30  hl@notebook:~
start cmd:> [[ FOO = f* ]]; echo $?
0

nullglob

Wenn ein globbing pattern keine Treffer generiert, wird standardmäßig das unveränderte pattern Teil der Kommandozeile. Das ist fast nie sinnvoll, wenn das pattern für die Shell gedacht war (wie etwa bei ls -l *.txt). Wenn es für die Anwendung gedacht war (wie etwa bei find . -name *.txt), dann wären Treffer sogar fatal. In diesen Fällen sollte das pattern immer quoting-geschützt sein, so dass keine pathname expansion stattfindet. Diese Option sorgt dafür, dass das pattern komplett aus der Kommandozeile entfernt wird. Auch das ist nicht immer unproblematisch (weswegen die Option failglob eingeführt wurde).

Diese Option ist ideal für globbing-Schleifen, die dann komplett übersprungen werden:

for file in *.txt; do : whatever; done

set options

Diese Optionen kann man mit set -<optionname> aktivieren und mit shopt +<optionname> deaktivieren.

allexport (-a)

Wenn nicht existente Variablen in der Shell angelegt werden, stehen sie nur in der Shell (und in Shubshells) zur Verfügung, nicht aber im Environment neuer Kindprozesse. Dies gilt nur für diejenigen Variablen, für die das export-Attribut gesetzt ist. Dies kann man explizit pro Variable tun (declare -x var=foo, export var=foo), oder man nutzt diese Option, um automatisch alle neu beschriebenen Variablen mit diesem Attribut zu versehen (bestehende Variablen werden nicht entsprechend angepasst, solange sie nicht beschrieben werden).

ec:0   20:34:48  hl@notebook:~
start cmd:> declare -p var
bash: declare: var: Nicht gefunden.

ec:1   20:35:11  hl@notebook:~
start cmd:> var=foo

ec:0   20:35:18  hl@notebook:~
start cmd:> ( declare -p var )
declare -- var="foo"

ec:0   20:36:06  hl@notebook:~
start cmd:> bash -c 'declare -p var'
bash: Zeile 1: declare: var: Nicht gefunden.

ec:1   20:36:24  hl@notebook:~
start cmd:> set -a

ec:0   20:36:33  hl@notebook:~
start cmd:> bash -c 'declare -p var'
bash: Zeile 1: declare: var: Nicht gefunden.

ec:1   20:36:35  hl@notebook:~
start cmd:> var=foo

ec:0   20:36:39  hl@notebook:~
start cmd:> bash -c 'declare -p var'
declare -x var="foo"

errexit (-e)

Diese Option ist quasi nur für Scripte relevant. Sie sorgt dafür, dass die (Sub-)Shell abgebrochen wird, wenn ein Kommando oder eine Funktion sich mit einem Fehlercode beendet, sofern der exit code nicht mit ! invertiert wird, dies außerhalb einer Kommandostruktur passiert, die den exit code auswertet (if, elif, while, until), und es auch nicht die letzte Komponente einer Pipeline oder &&- oder ||-Kommandoliste ist.

Diese Option ist vor allem für "heikle" Scripte (solche mit erheblichem Schadensspotential) relevant, bei denen man möchte, dass sie abbrechen, wenn etwas Unerwartetes passiert. In solchen Scripten sollte man sowieso alle möglichen Fehler abfangen; insofern ist diese Option vor allem eine Art Sicherheitsnetz für den Fall, dass dies irgendwo vergessen wurde.

noclobber (-C)

Diese Option ist eine Schutzmaßnahme gegen das versehentliche Überschreiben von Dateien durch Ausgabeumleitungen (>, &>, >&).

Wenn diese Option aktiv ist, wird zum Überschreiben von Dateien durch Ausgabeumleitungen der Operator >| benötigt.

noexec (-n)

Diese Option ist nur sinnvoll als Parameter von bash beim Aufruf von Scripten oder von Code mittels -c-Parameter. Sie verhindert das Ausführen der Kommandos. Damit kann man Code gefahrlos auf syntaktische Fehler prüfen. Am nützlichsten ist dies zusammen mit Debugoptionen: bash -vnx script.sh

nounset (-u)

Dies ist eine Schutzoption, ähnlich errexit. Sie sorgt dafür, dass Kommandozeilen nicht ausgeführt werden, wenn sie undefinierte Variablen referenzieren (Ausnahmen: $@ und $*). Ein Script wird dann abgebrochen, eine interaktive Shell gibt eine Fehlermeldung aus. Die Verwendung nichtinitialisierter Variablen ist in Shell-Code weniger dramatisch als in C, kann aber auch hier für einigen Ärger sorgen und spricht in beiden Fällen für Programmierfehler oder wenigstens Schlampigkeit.

pipefail

Normalerweise ist der exit code einer Pipeline derjenige der letzten Komponente. Wenn diese Option aktiv ist und mindestens eine der Komponenten einen anderen exit code als 0 liefert, wird als exit code der Pipeline der exit code der spätesten Komponente verwendet, der nicht 0 ist.

Unabhängig von dieser Option kann man auf die einzelnen exit codes der Komponenten der letzten Pipeline über das Array PIPESTATUS zugreifen.

verbose (-v)

Diese Option ist vor allem beim Debuggen von Scripten nützlich. Sie sorgt dafür, dass vor der Ausführung eines Kommandos die Codezeile oder die Codezeilen, die das Kommando enthalten, im Original ausgegeben werden. Diese Option ist vor allem in Kombination mit xtrace nützlich.

xtrace (-x)

Diese Option ist vor allem beim Debuggen von Scripten nützlich. Sie sorgt dafür, dass vor der Ausführung eines Kommandos die Codezeile oder die Codezeilen, die das Kommando enthalten, im Original ausgegeben werden. Diese Option ist vor allem in Kombination mit verbose nützlich.

Da die Ausgaben von verbose und xtrace auf stderr erfolgen, sind Debugaufrufe in folgender Form sinnvoll:

bash -vx script.sh 2>&1 | less

Risiken der (De-)Aktivierung von Shellfunktionen

history expansion

Grundsätzlich ist die shell history immens nützlich; das gilt aber nicht für alle Varianten des Zugangs zu dieser Funktion für alle Nutzer.

Ich bin nur sehr selten in der Situation, dass history expansion nutzen möchte. Die weitaus meisten Anwendungsfälle (das Einfügen einzelner Komponenten) erledige ich gefühlt besser (das ist natürlich Geschmackssache) mit Readline-keybindings. Wenn ich mehrere zusammenhängende Komponenten benötige, editiere ich die fragliche Kommandozeile. Dadurch habe ich die Syntax der history expansion zumeist vergessen, wenn sie denn mal nützlich wäre (und sich grob an die Syntax zu erinnern, ist ja nicht hilfreich).

Der – zumindest von mir so empfundene – Nachteil dieses aktivierten Features ist, dass es unabsichtlich ausgelöst werden kann. Quoting mit doppelten Anführungszeichen reicht nicht aus, man braucht einfache Anführungszeichen oder einen Backslash (der innerhalb doppelter Anführungszeichen in dieser Weise nicht funktioniert...), um ein Ausrufezeichen zu entschärfen. Zumeist wird history expansion auch ohne quoting nicht ausgelöst, weil auf das Ausrufezeichen ein Leerzeichen folgt oder es am Ende der Zeile steht.

Ich ziehe es deshalb vor, dieses (standardmäßig aktive) Feature in einer der bash-Konfigurationsdateien zu deaktivieren. Dies kann man mittels set +H tun.

failglob

Ich habe mich über die Einführung von failglob gefreut, musste aber feststellen, dass die Aktivierung dieser Shelloption zur Folge hat(te), dass Teile der Shell-Autovervollständigung unter Debian 12 nicht mehr funktionierten...

nullglob

Auf einem (zugegebenermaßen schon arg veralteten; nicht meine Schuld...) RHEL 7.9 treten Probleme auf, die den unter failglob genannten ähneln, wenn nullglob aktiviert ist.

Begrenzung der Wirkung von Shellfeatures

Häufig ist es so, dass man die Wirkung einer Shelloption nur für einen kleinen Teil des Scripts benötigt. In umfangreichen Scripten ist es nicht damit getan, eine Option zu aktivieren und danach zu deaktivieren. Das könnte einem auf die Füße fallen, wenn man später die scriptweite Voreinstellung ändert. Deshalb hat es seine Vorteile, Optionen so zu setzen, dass sie nur den Bereich betreffen, in dem sie benötigt werden.

Eine Möglichkeit dafür ist die Verlagerung von Codeabschnitten in eine Subshell. Das ist aber nicht immer praktikabel.

Sichern und Wiederherstellen

shopt -p und set +o produzieren eine Liste von Befehlen, die den aktuellen Zustand wiederherstellen. Der Einfachheit halber kann man dies in zwei Funktionen zusammenfassen, die am Anfang bzw. Ende des fraglichen Codeabschnitts aufgerufen werden:

save_options () { set_options="$( set +o )"; shopt_options="$( shopt -p )"; }

restore_options () { eval "$set_options"; eval "$shopt_options"; }

Auf die aktuelle Funktion begrenzen

In Shellfunktionen gibt es eine einfache Möglichkeit, Änderungen der Optionen auf den Code in der Funktion zu begrenzen. Leider gilt dies nur für die set-Optionen: local -