Espacios de nombres de Shell

10

¿Hay alguna forma de sourceun script de shell en un espacio de nombres, preferiblemente un script de shell bash, pero buscaría otros shells si tuvieran esta característica y bash no?

Lo que quiero decir con eso es, por ejemplo, algo como "prefijar todos los símbolos definidos con algo para que no choquen con símbolos ya definidos (nombres de variables, nombres de funciones, alias)" o cualquier otra instalación que evite las colisiones de nombres.

Si hay una solución en la que pueda el espacio de nombres en el sourcemomento ( NodeJSestilo), sería lo mejor.

Código de ejemplo:

$ echo 'hi(){ echo Hello, world; }' > english.sh
$ echo 'hi(){ echo Ahoj, světe; }' > czech.sh
$ . english.sh
$ hi
 #=> Hello, world
$ . czech.sh #bash doesn't even warn me that `hi` is being overwritten here
$ hi
 #=> Ahoj, světe
#Can't use the English hi now
#And sourcing the appropriate file before each invocation wouldn't be very efficient 
PSkocik
fuente
1
Gracias por la aclaración. Espero que la respuesta sea negativa. El paradigma habitual de programación de shell es que cuando quieres aislar los cambios, lo haces en un subshell, que crear uno es solo sobre el ( easiest thing ever ). Pero eso no es exactamente lo que buscas. Supongo que podría hacerlo ( stuff in subshell; exec env ) | sed 's/^/namespace_/'y evalel resultado en el shell principal, pero eso es un poco desagradable.
Celada
3
Si. Obtener ksh93. Los espacios de nombres son fundamentales, y todos sus tipos de nombres (que también se pueden escribir) admiten espacios de nombres. También es mucho más rápido en prácticamente todos los aspectos que bash, por cierto.
mikeserv
@mikeserv Gracias, si lo agrega como respuesta con un ejemplo de código que demuestra la funcionalidad, lo aceptaré.
PSkocik
@michas También necesitaría espacios de nombres, símbolos de función y alias. env | sed ...funcionaría para variables, podría hacer setpara obtener funciones, pero la búsqueda y el reemplazo serían un problema: las funciones pueden llamarse entre sí y usted necesitaría reemplazar todas las invocaciones cruzadas con invocaciones cruzadas prefijadas pero sin reemplazar el mismas palabras en otra parte del código de definición de función, donde no es una invocación. Para eso, necesitaría un analizador de bash, no solo una expresión regular, y todavía funcionaría siempre que las funciones no se llamaran entre sí a través de eval.
PSkocik

Respuestas:

11

Desde man kshun sistema con un ksh93...

  • Espacios de nombre
    • Los comandos y funciones que se ejecutan como parte de la lista de un namespacecomando que modifica variables o crea otras nuevas, crea una nueva variable cuyo nombre es el nombre del espacio de nombres tal como lo proporciona el identificador precedido por .. Cuando se hace referencia a una variable cuyo nombre es nombre, primero se busca usar .identifier.name.
    • Del mismo modo, una función definida por un comando en la lista de espacios de nombres se crea utilizando el nombre del espacio de nombres precedido por a ..
    • Cuando la lista de un comando de espacio de nombres contiene un namespacecomando, los nombres de las variables y funciones que se crean consisten en el nombre de la variable o función precedido por la lista de identificadores precedidos por cada uno .. Fuera de un espacio de nombres, se puede hacer referencia a una variable o función creada dentro de un espacio de nombres precediéndola con el nombre del espacio de nombres.
    • Por defecto, las variables que comienzan con .shestán en el shespacio de nombres.

Y, para demostrar, aquí está el concepto aplicado a un espacio de nombres proporcionado por defecto para cada variable de shell regular asignada en un ksh93shell. En el siguiente ejemplo, definiré una disciplinefunción que actuará como el .getmétodo asignado para la $PS1variable de shell. Cada variable de shell básicamente obtiene su propio espacio de nombres con, al menos, el valor predeterminado get, set, append, y unsetmétodos. Después de definir la siguiente función, cada vez que $PS1se hace referencia a la variable en el shell, la salida de datese dibujará en la parte superior de la pantalla ...

function PS1.get {
    printf "\0337\33[H\33[K%s\0338" "${ date; }"
}

(También tenga en cuenta la falta de la ()subshell en la sustitución del comando anterior)

Técnicamente, los espacios de nombres y las disciplinas no son exactamente lo mismo (porque las disciplinas se pueden definir para aplicarse global o localmente a un espacio de nombres en particular ) , pero son parte integral de la conceptualización de los tipos de datos de shell que es fundamental para ksh93.

Para abordar sus ejemplos particulares:

echo 'function hi { echo Ahoj, světe\!;  }' >  czech.ksh
echo 'function hi { echo Hello, World\!; }' >english.ksh
namespace english { . ./english.ksh; }
namespace czech   { . ./czech.ksh;   }
.english.hi; .czech.hi

Hello, World!
Ahoj, světe!

...o...

for ns in czech english
do  ".$ns.hi"
done

Ahoj, světe!
Hello, World!
mikeserv
fuente
@PSkocik: ¿por qué no arreglaste mi cosa ehoj ? Podría haber jurado que eso es lo que dijo antes ... perdón por eso. No hubiera aceptado una respuesta escrita por alguien que ni siquiera se molestó en deletrear correctamente las palabras que usé en la pregunta ... Sin embargo, honestamente, creo recordar haber copiado / pegado jt ... hmm ...
mikeserv
2

He escrito una función de shell POSIX que podría ser utilizado para espacio de nombres local una orden interna del shell o función en cualquiera de ksh93, dash, mksh, o bash (nombrado específicamente porque he confirmado personalmente al trabajo en todos ellos) . De los proyectiles en los que lo probé, solo no cumplió con mis expectativas yashy nunca esperé que funcionara en absoluto zsh. No hice la prueba posh. Renuncié a cualquier esperanza hace poshun tiempo y no la he instalado en algún tiempo. Tal vez funciona en posh...?

Digo que es POSIX porque, al leer la especificación, aprovecha un comportamiento específico de una utilidad básica, pero, es cierto, la especificación es vaga a este respecto y, al menos, una persona aparentemente no está de acuerdo conmigo. En general, he tenido un desacuerdo con este, eventualmente he encontrado que el error es mío, y posiblemente esta vez también me equivoque con respecto a la especificación, pero cuando lo pregunté más, no respondió.

Sin embargo, como dije, esto definitivamente funciona en los shells mencionados anteriormente, y funciona, básicamente, de la siguiente manera:

some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"

3
empty

El commandcomando se especifica como una utilidad básicamente disponible y una de las $PATHfunciones integradas previamente. Una de sus funciones especificadas es envolver utilidades incorporadas especiales en su propio entorno cuando las llama, y ​​así ...

{       sh -c ' x=5 set --; echo "$x"
                x=6 command set --; echo "$x"
                exec <"";  echo uh_oh'
        sh -c ' command exec <""; echo still here'
}

5
5
sh: 3: cannot open : No such file
sh: 1: cannot open : No such file
still here

... el comportamiento de las dos asignaciones de línea de comandos anteriores es correcto por especificación. El comportamiento de ambas condiciones de error también es correcto y, de hecho, está casi completamente duplicado allí desde la especificación. Las asignaciones prefijadas a las líneas de comando de funciones o componentes incorporados especiales se especifican para afectar el entorno actual del shell. Del mismo modo, los errores de redireccionamiento se especifican como fatales cuando se apunta a cualquiera de ellos. commandse especifica para suprimir el tratamiento especial de las incorporaciones especiales en esos casos, y el caso de redirección se demuestra realmente con un ejemplo en la especificación.

Las construcciones regulares, como command, por otro lado, se especifican para ejecutarse en un entorno de subshell , lo que no necesariamente significa que sea de otro proceso , solo que debería ser fundamentalmente indistinguible de uno. Los resultados de llamar a un builtin regular siempre deben parecerse a lo que se podría obtener de un $PATHcomando 'd' igualmente capaz . Y entonces...

na=not_applicable_to_read
na= read var1 na na var2 <<"" ; echo "$var1" "$na" "$var2"
word1 other words word2

word1 not_applicable_to_read word2

Pero el commandcomando no puede llamar a las funciones de shell y, por lo tanto, no se puede usar para representar su tratamiento especial discutible como se puede hacer para las incorporaciones regulares. Eso también se especifica. De hecho, la especificación dice que una utilidad principal de commandes que puede usarla dentro de una función de envoltura de envoltorio nombrada por otro comando para llamar a ese otro comando sin auto-recursión porque no llamará a la función. Me gusta esto:

cd(){ command cd -- "$1"; }

Si no lo usó commandallí, la cdfunción casi definitivamente sería predeterminada para la autorrecurrencia.

Pero como un builtin normal que puede llamar builtins especiales, commandpuede hacerlo en un entorno de subshell . Y así, si bien el estado actual del shell definido en el interior podría adherirse al shell actual, ciertamente read, $var1y lo $var2hizo, al menos los resultados de las definiciones de la línea de comandos probablemente no deberían ...

Comandos simples

Si no se obtiene el nombre del comando, o si el nombre del comando es una función o función incorporada especial, las asignaciones de variables afectarán el entorno de ejecución actual. De lo contrario, las asignaciones variables se exportarán para el entorno de ejecución del comando y no afectarán el entorno de ejecución actual.

Ahora, no commandsé si la capacidad de ser tanto un constructor regular como llamar directamente a construcciones especiales es solo una especie de escapatoria inesperada con respecto a la línea de comandos, no sé, pero sí sé que al menos las cuatro conchas ya mencionó honrar el commandespacio de nombres.

Y aunque commandno puede llamar directamente a las funciones de shell, puede llamar evalcomo se demostró, y puede hacerlo indirectamente. Así que construí un contenedor de espacio de nombres sobre este concepto. Toma una lista de argumentos como:

ns any=assignments or otherwise=valid names which are not a command then all of its args

... excepto que la commandpalabra anterior solo se reconoce como una si se puede encontrar con un vacío $PATH. Además de la determinación del alcance localmente variables de shell nombradas en la línea de comandos, sino que también a nivel local-ámbitos toda variables con nombres individuales minúsculas del alfabeto y una lista de otros estándares, tales como $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDy algunos otros.

Y sí, por la determinación del alcance localmente el $PWDy $OLDPWDlas variables y después explícitamente cding a $OLDPWDy $PWDque puede con bastante fiabilidad alcance el directorio de trabajo actual también. Esto no está garantizado, aunque se esfuerza bastante. Conserva un descriptor para 7<.y cuando su objetivo de ajuste vuelve, lo hace cd -P /dev/fd/7/. Si el directorio de trabajo actual ha estado unlink()en el ínterin, al menos aún debería volver a cambiar, pero emitirá un error feo en ese caso. Y debido a que mantiene el descriptor, tampoco creo que un núcleo cuerdo deba permitir que su dispositivo raíz se desmonte (???) .

También localiza las opciones de shell y las restaura al estado en el que las encontró cuando regresa su utilidad envuelta. Se trata $OPTSespecialmente porque mantiene una copia en su propio alcance al que inicialmente asigna el valor $-. Después de manejar también todas las asignaciones en la línea de comandos, lo hará set -$OPTSjusto antes de invocar su objetivo de ajuste. De esta manera, si define -$OPTSen la línea de comandos, puede definir las opciones de shell de su objetivo de ajuste. Cuando el objetivo regrese, lo hará set +$- -$OPTScon su propia copia $OPTS (que no se ve afectada por la línea de comandos que define) y restaurará todo al estado original.

Por supuesto, no hay nada que returrnimpida que la persona que llama de alguna manera salga explícitamente de la función a través del objetivo de ajuste o sus argumentos. Si lo hace, evitará cualquier restauración / limpieza de estado que de lo contrario intentaría.

Para hacer todo lo que necesita, debe ir a tres evalde profundidad. Primero se envuelve en un ámbito local, luego, desde adentro, lee argumentos, los valida para nombres de shell válidos y se cierra con error si encuentra uno que no lo es. Si todos los argumentos son válidos y eventualmente uno hace command -v "$1"que devuelva verdadero (recordar: $PATHestá vacío en este punto) , evalla línea de comandos define y pasa todos los argumentos restantes al objetivo de ajuste (aunque ignora el caso especial para ns, porque eso no No sea muy útil, y tres evals de profundidad es más que suficiente) .

Básicamente funciona así:

case $- in (*c*) ... # because set -c doesnt work
esac
_PATH=$PATH PATH= OPTS=$- some=vars \
    command eval LOCALS=${list_of_LOCALS}'
        for a do  i=$((i+1))          # arg ref
              if  [ "$a" != ns ]  &&  # ns ns would be silly
                  command -v "$a" &&
              !   alias "$a"          # aliases are hard to run quoted
        then  eval " PATH=\$_PATH OTHERS=$DEFAULTS $v \
                     command eval '\''
                             shift $((i-1))         # leave only tgt in @
                             case $OPTS in (*different*)
                                  set \"-\${OPTS}\" # init shell opts 
                             esac
                             \"\$@\"                # run simple command
                             set +$- -$OPTS "$?"    # save return, restore opts
                     '\''"
              cd -P /dev/fd/7/        # go whence we came
              return  "$(($??$?:$1))" # return >0 for cd else $1
        else  case $a in (*badname*) : get mad;;
              # rest of arg sa${v}es
              esac
        fi;   done
    ' 7<.

Hay algunas otras redirecciones y, y algunas pruebas extrañas que ver con la forma cen que se colocan algunos proyectiles $-y luego se niegan a aceptarlo como una opción para set (???) , pero todo es auxiliar, y se usa principalmente solo para evitar la emisión salida no deseada y similar en casos extremos. Y así es como funciona. Puede hacer esas cosas porque establece su propio ámbito local antes de llamar a su utilidad envuelta en un anidado.

Es largo, porque trato de tener mucho cuidado aquí: tres evalses difícil. Pero con eso puedes hacer:

ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"

still_local global
 global

Ir un paso más allá y espaciar de forma persistente el alcance local de la utilidad envuelta no debería ser muy difícil. E incluso como está escrito, ya define una $LOCALSvariable para la utilidad envuelta que se compone de solo una lista separada por espacios de todos los nombres que definió en el entorno de la utilidad envuelta.

Me gusta:

ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '

... que es perfectamente seguro: $IFSse ha desinfectado a su valor predeterminado y solo los nombres de shell válidos lo hacen a $LOCALSmenos que lo configure usted mismo en la línea de comando. E incluso si puede haber caracteres globales en una variable dividida, también puede establecer OPTS=fen la línea de comandos que la utilidad envuelta prohíba su expansión. En todo caso:

LOCALS    ARG0      ARGC      HOME
IFS       OLDPWD    OPTARG    OPTIND
OPTS      PATH      PS3       PS4
PWD       a         b         c
d         e         f         g
h         i         j         k
l         m         n         o
p         q         r         s
t         u         v         w
x         y         z         _
bel       bs        cr        esc
ht        ff        lf        vt
lb        dq        ds        rb
sq        var1      var2      

Y aquí está la función. Todos los comandos tienen el prefijo w / \para evitar aliasexpansiones:

ns(){  ${1+":"} return
       case  $- in
       (c|"") ! set "OPTS=" "$@"
;;     (*c*)  ! set "OPTS=${-%c*}${-#*c}" "$@"
;;     (*)      set "OPTS=$-" "$@"
;;     esac
       OPTS=${1#*=} _PATH=$PATH PATH= LOCALS=     lf='
'      rb=\} sq=\' l= a= i=0 v= __=$_ IFS="       ""
"      command eval  LOCALS=\"LOCALS \
                     ARG0 ARGC HOME IFS OLDPWD OPTARG OPTIND OPTS     \
                     PATH PS3 PS4 PWD a b c d e f g h i j k l m n     \
                     o p q r s t u v w x y z _ bel bs cr esc ht ff    \
                     lf vt lb dq ds rb sq'"
       for a  do     i=$((i+1))
              if     \[ ns != "$a" ]         &&
                     \command -v "$a"  >&9   &&
              !      \alias "${a%%=*}" >&9 2>&9
              then   \eval 7>&- '\'    \
                     'ARGC=$((-i+$#))  ARG0=$a      HOME=~'           \
                     'OLDPWD=$OLDPWD   PATH=$_PATH  IFS=$IFS'         \
                     'OPTARG=$OPTARG   PWD=$PWD     OPTIND=1'         \
                     'PS3=$PS3 _=$__   PS4=$PS4     LOCALS=$LOCALS'   \
                     'a= b= c= d= e= f= g= i=0 j= k= l= m= n= o='     \
                     'p= q= r= s= t= u= v= w= x=0 y= z= ht=\   '      \
                     'cr=^M bs=^H ff=^L vt=^K esc=^[ bel=^G lf=$lf'   \
                     'dq=\" sq=$sq ds=$ lb=\{ rb=\}' \''"$v'          \
                            '\command eval       9>&2 2>&- '\'        \
                                   '\shift $((i-1));'                 \
                                   'case \${OPTS##*[!A-Za-z]*} in'    \
                                   '(*[!c$OPTS]*) >&- 2>&9"'\'        \
                                   '\set -"${OPTS%c*}${OPTS#*c}"'     \
                                   ';;esac; "$@" 2>&9 9>&-; PS4= '    \
                                   '\set  +"${-%c*}${-#*c}"'\'\"      \
                                          -'$OPTS \"\$?\"$sq";'       \
              '             \cd -- "${OLDPWD:-$PWD}"
                            \cd -P  ${ANDROID_SYSTEM+"/proc/self/fd/7"} /dev/fd/7/
                            \return "$(($??$?:$1))"
              else   case   ${a%%=*}      in
                     ([0-9]*|""|*[!_[:alnum:]]*)
                            \printf "%s: \${$i}: Invalid name: %s\n" \
                            >&2    "$0: ns()"   "'\''${a%%=*}'\''"
                            \return 2
              ;;     ("$a") v="$v $a=\$$a"
              ;;     (*)    v="$v ${a%%=*}=\${$i#*=}"
              ;;     esac
                     case " $LOCALS " in (*" ${a%%=*} "*)
              ;;     (*)    LOCALS=$LOCALS" ${a%%=*}"
              ;;     esac
              fi
       done'  7<.    9<>/dev/null
}
mikeserv
fuente
¡Muy inteligente! Aquí se usa un patrón similar para lograr lo mismo: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B