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
}
( easiest thing ever ). Pero eso no es exactamente lo que buscas. Supongo que podría hacerlo( stuff in subshell; exec env ) | sed 's/^/namespace_/'yevalel resultado en el shell principal, pero eso es un poco desagradable.