Bash: Exportierte Funktion nicht sichtbar, Variablen jedoch

Sep 17 2020

Im Laufe der Jahre habe ich eine Art Bibliothek mit Bash-Funktionen gesammelt, auf die sich die Shell und die Skripte beziehen. Um die Import-Boilerplate zu verringern, untersuche ich Optionen, wie die Bibliothek angemessen in Skripte aufgenommen werden kann.

Meine Lösung besteht aus zwei Teilen: dem ersten Importieren der Konfiguration (env vars) und dem anschließenden Sourcing der Funktionsbibliothek.

~ / bash_envs : (die Konfiguration)

export SOME_VAR=VALUE
export SHELL_LIB=/path/to/library.sh

# convenience funtion, so scripts who source env_vars file (ie this file) can
# simply call it, instead of including the same block in each file themselves.
function _load_common() {
    # import common library/functions:
    source $SHELL_LIB
}
export -f _load_common

# marker var used to detect whether env vars (ie this file) have been loaded:
export __ENV_VARS_LOADED_MARKER_VAR=loaded

Nun wird folgender Code aus Skripten ausgeführt:

if [[ "$__ENV_VARS_LOADED_MARKER_VAR" != loaded ]]; then  # in our case $__ENV_VARS_LOADED_MARKER_VAR=loaded, ie this block is not executed USER_ENVS=/home/laur/bash_envs if [[ -r "$USER_ENVS" ]]; then
        source "$USER_ENVS" else echo -e "\n ERROR: env vars file [$USER_ENVS] not found! Abort."
        exit 1
    fi
fi

_load_common

Dies erzeugt eine _load_common: command not foundAusnahme. Warum ist das so? Note __ENV_VARS_LOADED_MARKER_VAR=loadedist gut exportiert und sichtbar, weshalb es keinen Grund gibt, eine Quelle zu finden $USER_ENVS. Die _load_common()Funktion wurde jedoch nicht gefunden, obwohl sie vom selben Ort wie __ENV_VARS_LOADED_MARKER_VAR exportiert wird.

Antworten

1 Gilles'SO-stopbeingevil' Sep 17 2020 at 05:34

Das Problem

Beobachten:

$ bash -c 'foobar () { :; }; export -f foobar; dash -c env' |grep foobar $ bash -c 'foobar () { :; }; export -f foobar; bash -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :
$ bash -c 'foobar () { :; }; export -f foobar; ksh93 -c env' |grep foobar BASH_FUNC_foobar%%=() { : $ bash -c 'foobar () { :; }; export -f foobar; mksh -c env' |grep foobar
$ bash -c 'foobar () { :; }; export -f foobar; zsh -c env' |grep foobar BASH_FUNC_foobar%%=() { : $ bash -c 'foobar () { :; }; export -f foobar; busybox sh -c env' |grep foobar
BASH_FUNC_foobar%%=() {  :

Umgebungsvariablen sind eine Funktion des Unix-Betriebssystems. Die Unterstützung für sie reicht bis zum Kernel: Wenn ein Programm ein anderes Programm aufruft (mit dem execveSystemaufruf ), ist einer der Parameter des Aufrufs die Umgebung des neuen Programms.

Der exportin sh-Shells integrierte Befehl (dash, bash, ksh,…) bewirkt, dass eine Shell-Variable als Umgebungsvariable verwendet wird, die an Prozesse übertragen wird, die die Shell aufruft. Umgekehrt werden beim Aufruf einer Shell alle Umgebungsvariablen zu Shell-Variablen in dieser Shell-Instanz.

Exportierte Funktionen sind eine Bash-Funktion. Bash "exportiert" eine Funktion, indem eine Umgebungsvariable erstellt wird, deren Name vom Namen der Funktion abgeleitet ist und deren Wert der Hauptteil der Funktion ist (plus einen Header und einen Trailer). Sie können oben sehen, wie der Name der Umgebungsvariablen aufgebaut ist: BASH_FUNC_dann der Name der Funktion %%.

Dieser Name ist kein gültiger Name für eine Shell-Variable. Denken Sie daran, dass Shells beim Start Umgebungsvariablen als Shell-Variablen importieren. Unterschiedliche Shells verhalten sich unterschiedlich, wenn der Name einer Umgebungsvariablen keine gültige Shell-Variable ist. Einige übergeben die Variable an ihre Unterprozesse (oben: bash, ksh93, zsh, BusyBox), während andere nur die exportierten Shell-Variablen an ihre Unterprozesse weitergeben (oben: dash, mksh), wodurch die Umgebungsvariablen, deren Name nicht a ist, effektiv entfernt werden gültige Shell-Variable (nicht leere Folge von ASCII-Buchstaben, Ziffern und _).

Ursprünglich verwendete bash eine Umgebungsvariable mit demselben Namen wie die Funktion, wodurch dieses Problem größtenteils vermieden worden wäre. (Nur meistens: Funktionsnamen können Zeichen enthalten, die in Shell-Variablennamen nicht zulässig sind, z. B. -) Dies hatte jedoch andere Nachteile, z. B. das Nicht-Exportieren einer Shell-Variablen und einer Funktion mit demselben Namen (je nachdem, welches zuletzt exportiert wurde) würde den anderen in der Umgebung überschreiben). Kritisch gesehen änderte sich Bash, als festgestellt wurde, dass die ursprüngliche Implementierung eine große Sicherheitslücke verursachte . (Siehe auch Was bedeutet env x = ‚() {:;}; Befehl‘ Bash tun und warum ist es unsicher? , Wenn die Shellshock war (CVE-2.014-6.271 / 7169) Fehler eingeführt, und was ist der Patch , dass vollständig Fixes es? , Wie wurde gefunden , die Shellshock Bash Verwundbarkeit? ) Ein Nachteil dieser Änderung ist , dass exportierten Funktionen nicht mehr einige Programme durchlaufen, einschließlich Bindestrich und mksh.

Ihr System hat wahrscheinlich Strich als /bin/sh. Es ist eine sehr beliebte Wahl. /bin/shwird häufig verwendet, daher ist die Wahrscheinlichkeit hoch, dass shirgendwo im Aufrufpfad ein Aufruf von der ursprünglichen Bash-Instanz, die ausgeführt wurde, export -f _load_commonzu der Bash-Instanz ausgeführt wurde, die versucht hat, die Funktion zu verwenden. __ENV_VARS_LOADED_MARKER_VARübergeben, weil es einen gültigen Variablennamen hat, aber BASH_FUNC__load_common%%nicht durchlaufen hat.

Die Lösung

Verwenden Sie keine exportierten Funktionen. Sie haben in erster Linie wenig Nutzen, und für Sie sind sie völlig nutzlos. Der einzige Vorteil des Exportierens von Funktionen besteht darin, bash aufzurufen, ohne dass diese Bash-Instanz die Definition der Funktion von irgendwoher lesen muss, um beispielsweise eine Funktion in einem Skript zu definieren und sie an eine von find -execoder xargsoder aufgerufene Bash-Instanz zu übergeben parallel. In Ihrem Fall haben Sie jedoch bereits Code zum Lesen der Funktionsdefinition. Lesen Sie also einfach die Funktionsdefinition unbedingt. Entfernen export -f _load_common, entfernen __ENV_VARS_LOADED_MARKER_VARund einfach anrufen source "$USER_ENVS".