Eu escrevi uma função shell POSIX que pode ser usado para localmente namespace A builtin shell ou função em qualquer um ksh93, dash, mksh, ou bash (nomeado especificamente porque eu, pessoalmente, confirmou ao trabalho em todos estes) . Das conchas em que o testei, ele apenas falhou em atender às minhas expectativas yashe nunca esperei que funcionasse zsh. Eu não testei posh. Desisti de qualquer esperança há poshalgum tempo e não o instalo há algum tempo. Talvez funcione em posh...?
Eu digo que é POSIX porque, pela minha leitura da especificação, tira proveito de um comportamento especificado de um utilitário básico, mas, reconhecidamente, a especificação é vaga a esse respeito e, pelo menos uma pessoa aparentemente discorda de mim. Geralmente, eu tive uma discordância com essa, e finalmente encontrei o erro como meu, e possivelmente também estou errado desta vez com a especificação, mas quando o questionei mais, ele não respondeu.
Como eu disse, no entanto, isso definitivamente funciona nas conchas acima mencionadas, e funciona, basicamente, da seguinte maneira:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
O commandcomando é especificado como um utilitário basicamente disponível e um dos pré- $PATHinstalados. Uma de suas funções especificadas é agrupar utilitários embutidos especiais em seu próprio ambiente ao chamá-los, e assim ...
{ 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
... o comportamento das duas atribuições de linha de comando acima está correto por especificação. O comportamento de ambas as condições de erro também está correto e, de fato, é quase completamente duplicado na especificação. As atribuições prefixadas às linhas de comando de funções ou recursos especiais especiais são especificadas para afetar o ambiente atual do shell. Da mesma forma, os erros de redirecionamento são especificados como fatais quando apontados para qualquer um deles. commandé especificado para suprimir o tratamento especial de recursos internos especiais nesses casos, e o caso de redirecionamento é realmente demonstrado por exemplo na especificação.
Construções regulares, como command, por outro lado, são especificadas para serem executadas em um ambiente de subcamadas - o que não significa necessariamente o de outro processo , apenas que ele deve ser fundamentalmente indistinguível de um. Os resultados de chamar um built-in regular sempre devem se parecer com o que pode ser obtido de um $PATHcomando com capacidade semelhante . E entao...
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
Mas o commandcomando não pode chamar funções de shell e, portanto, não pode ser usado para tornar seu tratamento especial discutível, como é possível para os componentes internos regulares. Isso também é especificado. De fato, a especificação diz que um utilitário primário commandé que você pode usá-lo em uma função shell do invólucro chamada para outro comando para chamar esse outro comando sem auto-recursão, porque não chamará a função. Como isso:
cd(){ command cd -- "$1"; }
Se você não usasse commandlá, a cdfunção quase definitivamente segfault para auto-recursão.
Porém, como um embutido regular que pode chamar de embutidos especiais, commandpode fazê-lo em um ambiente de subcamação . E assim, embora o estado atual do shell definido dentro possa se ater ao shell atual - certamente reado fez $var1e o $var2fez - pelo menos os resultados da linha de comando definem provavelmente não deveriam ...
Comandos simples
Se nenhum nome de comando resultar, ou se o nome do comando for uma função ou built-in especial, as atribuições de variáveis afetarão o ambiente de execução atual. Caso contrário, as atribuições de variáveis serão exportadas para o ambiente de execução do comando e não afetarão o ambiente de execução atual.
Agora, se commanda capacidade de ser ou não um construtor regular e chamar diretamente de construtores especiais é apenas algum tipo de brecha inesperada no que diz respeito às definições da linha de comando, eu não sei, mas sei que pelo menos os quatro escudos já mencionado honra o commandespaço para nome.
E embora commandnão possa chamar diretamente funções de shell, pode chamar evalcomo demonstrado, e pode fazê-lo indiretamente. Então, eu criei um wrapper de espaço para nome nesse conceito. É preciso uma lista de argumentos como:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... exceto que a commandpalavra acima é reconhecida apenas como uma, se puder ser encontrada com um vazio $PATH. Além localmente escopo variáveis do shell nomeadas na linha de comando, ele também localmente escopos todos variável com minúsculas nomes alfabéticos individuais e uma lista de outras padrão, como $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDe alguns outros.
E sim, por escopo localmente o $PWDe $OLDPWDvariáveis e depois explicitamente cding para $OLDPWDe $PWDele pode razoavelmente confiável âmbito do diretório de trabalho atual também. Isso não é garantido, embora tente bastante. Ele mantém um descritor para 7<.e quando seu destino de quebra retorna cd -P /dev/fd/7/. Se o diretório de trabalho atual estiver unlink()temporário, ele ainda deve pelo menos conseguir voltar para ele, mas emitirá um erro feio nesse caso. E como ele mantém o descritor, também não acho que um kernel sadio permita que seu dispositivo raiz seja desmontado (???) .
Ele também escopo localmente as opções de shell e as restaura no estado em que as encontrou quando o utilitário empacotado retorna. Trata $OPTSespecialmente na medida em que mantém uma cópia em seu próprio escopo à qual atribui inicialmente o valor $-. Depois de também manipular todas as atribuições na linha de comando, ele fará um set -$OPTSpouco antes de chamar seu destino de quebra automática. Dessa forma, se você definir -$OPTSna linha de comando, poderá definir as opções de shell do seu destino de quebra automática. Quando o destino retornar, ele irá set +$- -$OPTScom sua própria cópia de $OPTS (que não é afetada pela linha de comando define) e restaurará tudo para o estado original.
Obviamente, não há nada que impeça o chamador de sair de alguma forma explicitamente returrnda função por meio do alvo de quebra ou de seus argumentos. Isso impedirá qualquer restauração / limpeza do estado que, de outra forma, seria tentada.
Para fazer tudo o que é necessário, é preciso evalaprofundar três . Primeiro, ele se envolve em um escopo local, depois, de dentro, lê argumentos, valida-os para nomes de shell válidos e sai com erro se encontrar um que não é. Se todos os argumentos forem válidos e, eventualmente, uma causa command -v "$1"retornar true (lembre-se: $PATHestá vazio neste momento) , evala linha de comando definirá e transmitirá todos os argumentos restantes para o destino de quebra de linha (embora ignore o caso especial de ns- porque isso não seja muito útil, e três evalsegundos de profundidade é mais do que suficiente) .
Basicamente, funciona assim:
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<.
Há alguns outros redirecionamentos e, e alguns testes estranhas a ver com a forma como algumas conchas colocar cem $-e, em seguida, recusar-se a aceitá-la como uma opção para set (???) , mas todos os seus auxiliares, e principalmente usado apenas para salvar a partir emitindo saída indesejada e similar em casos extremos. E é assim que funciona. Ele pode fazer essas coisas porque configura seu próprio escopo local antes de chamar seu utilitário agrupado em um tal aninhado.
É longo, porque eu tento ter muito cuidado aqui - três evalsé difícil. Mas com isso você pode fazer:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
Indo um passo adiante e persistindo no namespace do escopo local do utilitário agrupado, não deve ser muito difícil. E mesmo como está escrito, ele já define uma $LOCALSvariável para o utilitário agrupado, que é composta apenas por uma lista separada por espaço de todos os nomes que definiu no ambiente do utilitário agrupado.
Gostar:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... o que é perfeitamente seguro - $IFSfoi higienizado com seu valor padrão e somente nomes válidos de shell o fazem, a $LOCALSmenos que você o defina na linha de comando. E mesmo que possa haver caracteres glob em uma variável dividida, você também pode definir OPTS=fna linha de comando o utilitário agrupado para proibir sua expansão. Em qualquer 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
E aqui está a função. Todos os comandos são prefixados com / \para evitar aliasexpansões:
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 ). Mas não é exatamente isso que você procura. Eu acho que você poderia fazer( stuff in subshell; exec env ) | sed 's/^/namespace_/'eevalo resultado no shell pai, mas isso é meio desagradável.