bash: la función exportada no es visible, pero las variables son

Sep 17 2020

A lo largo de los años, he recopilado una especie de biblioteca de funciones bash a las que se refieren el shell y los scripts. Para disminuir el texto estándar de importación, estoy explorando opciones sobre cómo incluir razonablemente la biblioteca en los scripts.

Mi solución tiene dos partes: primero la configuración de importación (env vars), seguida de la fuente de la biblioteca de funciones.

~ / bash_envs : (la configuración)

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

Ahora el siguiente código se ejecuta desde scripts:

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

Esto produce una _load_common: command not foundexcepción. ¿Porqué es eso? La nota __ENV_VARS_LOADED_MARKER_VAR=loadedse exporta muy bien y es visible, por lo que no hay razón para la fuente $USER_ENVS; sin embargo _load_common(), no se encuentra la función, aunque se exporta desde el mismo lugar que __ENV_VARS_LOADED_MARKER_VAR.

Respuestas

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

El problema

Observar:

$ 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%%=() {  :

Las variables de entorno son una característica del sistema operativo Unix. El soporte para ellos llega hasta el núcleo: cuando un programa llama a otro programa (con la execvellamada al sistema ), uno de los parámetros de la llamada es el entorno del nuevo programa.

El comando incorporado en los exportshells de estilo sh (dash, bash, ksh,…) hace que se use una variable de shell como una variable de entorno que se transmite a los procesos que llama el shell. Por el contrario, cuando se llama a un shell, todas las variables de entorno se convierten en variables de shell en esa instancia de shell.

Las funciones exportadas son una característica de bash. Bash "exporta" una función mediante la creación de una variable de entorno cuyo nombre se deriva del nombre de la función y cuyo valor es el cuerpo de la función (más un encabezado y un avance). Puede ver arriba cómo se construye el nombre de la variable de entorno: BASH_FUNC_luego el nombre de la función luego %%.

Este nombre no es un nombre válido para una variable de shell. Recuerde que los shells importan variables de entorno como variables de shell cuando se inician. Los distintos shells tienen comportamientos diferentes cuando el nombre de una variable de entorno no es una variable de shell válida. Algunos pasan la variable a sus subprocesos (arriba: bash, ksh93, zsh, BusyBox), mientras que otros solo pasan las variables de shell exportadas a sus subprocesos (arriba: dash, mksh), lo que efectivamente elimina las variables de entorno cuyo nombre no es un variable de shell válida (secuencia no vacía de letras ASCII, dígitos y _).

Originalmente, bash usaba una variable de entorno con el mismo nombre que la función, lo que en su mayoría habría evitado este problema. (Solo en su mayoría: los nombres de funciones pueden contener caracteres que no están permitidos en los nombres de variables de shell, como -.) Pero esto tenía otras desventajas, como no permitir exportar una variable de shell y una función con el mismo nombre (la que se exportó en último lugar sobrescribiría el otro en el entorno). Fundamentalmente, bash cambió cuando se descubrió que la implementación original causó un gran agujero de seguridad . (Véase también ¿Qué env = x '() {:;}; comandos bash hacer y por qué es inseguro? , ¿Cuándo fue la neurosis (CVE-2014 a 6.271 / 7169) introdujo error, y lo que es el parche que plenamente ¿Lo soluciona ? , ¿Cómo se encontró la vulnerabilidad Shellshock Bash? ) Una desventaja de este cambio es que las funciones exportadas ya no pasan por algunos programas, incluidos dash y mksh.

Su sistema probablemente tenga un guión como /bin/sh. Es una opción muy popular. /bin/shse usa mucho, por lo que hay muchas posibilidades de que haya una llamada a shalgún lugar de la ruta de la llamada desde la instancia original de bash que se ejecutó export -f _load_commonhasta la instancia de bash que intentó usar la función. __ENV_VARS_LOADED_MARKER_VARpasó porque tiene un nombre de variable válido, pero BASH_FUNC__load_common%%no pasó.

La solución

No utilice funciones exportadas. Tienen poca utilidad en primer lugar, y para ti son completamente inútiles. La única ventaja de exportar funciones es llamar a bash sin requerir que esa instancia de bash lea la definición de la función desde algún lugar, por ejemplo, para definir una función en un script y pasarla a una instancia de bash invocada desde find -execo xargso parallel. Pero en su caso, ya tiene código para leer la definición de la función. Así que solo lea la definición de la función incondicionalmente. Eliminar export -f _load_common, eliminar __ENV_VARS_LOADED_MARKER_VARy simplemente llamar source "$USER_ENVS".