J'ai écrit une fonction shell POSIX qui pourrait être utilisé pour l' espace de noms localement une commande du shell ou une fonction dans l' un des ksh93, dash, mkshou bash (nommé spécifiquement parce que je l' ai personnellement confirmé au travail dans tous ces) . Parmi les coques dans lesquelles je l'ai testé, il n'a pas répondu à mes attentes yashet je ne m'attendais pas du tout à ce qu'il fonctionne zsh. Je n'ai pas testé posh. J'ai abandonné tout espoir il y a poshun moment et je ne l'ai pas installé depuis un certain temps. Peut-être que cela fonctionne en posh...?
Je dis que c'est POSIX parce que, par ma lecture de la spécification, il profite d'un comportement spécifié d'un utilitaire de base, mais, certes, la spécification est vague à cet égard, et, au moins une personne est apparemment en désaccord avec moi. En général, j'ai eu un désaccord avec celui-ci, j'ai finalement trouvé que l'erreur était la mienne, et peut-être que je me trompe également cette fois sur les spécifications, mais quand je l'ai interrogé davantage, il n'a pas répondu.
Comme je l'ai dit, cependant, cela fonctionne certainement dans les coquilles susmentionnées, et cela fonctionne, essentiellement, de la manière suivante:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
La commandcommande est spécifiée comme un utilitaire fondamentalement disponible et l'un des pré $PATH-intégrés. L'une de ses fonctions spécifiées consiste à envelopper des utilitaires intégrés spéciaux dans son propre environnement lors de leur appel, et ainsi de suite ...
{ 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
... le comportement des deux affectations de ligne de commande ci-dessus est correct selon les spécifications. Le comportement des deux conditions d'erreur est également correct et y est en fait presque entièrement reproduit à partir de la spécification. Les affectations préfixées aux lignes de commande des fonctions ou des fonctions spéciales sont spécifiées pour affecter l'environnement shell actuel. De même, les erreurs de redirection sont spécifiées comme fatales lorsqu'elles sont pointées vers l'une ou l'autre. commandest spécifié pour supprimer le traitement spécial des constructions spéciales dans ces cas, et le cas de redirection est réellement démontré par l'exemple dans la spécification.
D'un commandautre côté, les buildins réguliers sont spécifiés pour s'exécuter dans un environnement de sous - shell - ce qui ne signifie pas nécessairement celui d' un autre processus , juste qu'il devrait être fondamentalement indiscernable d'un. Les résultats de l'appel d'un buildin normal devraient toujours ressembler à ce qui pourrait être obtenu à partir d'une $PATHcommande de capacité similaire . Et donc...
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
Mais la commandcommande ne peut pas appeler les fonctions shell, et ne peut donc pas être utilisée pour rendre leur traitement spécial sans objet comme cela peut être le cas pour les fonctions intégrées normales. C'est également spécifié. En fait, la spécification indique qu'un utilitaire principal de commandest que vous pouvez l'utiliser dans une fonction shell wrapper nommée pour une autre commande pour appeler cette autre commande sans auto-récursivité car elle n'appellera pas la fonction. Comme ça:
cd(){ command cd -- "$1"; }
Si vous ne l'utilisiez pas command, la cdfonction serait presque définitivement un défaut d'auto-récursivité.
Mais en tant que fonction interne standard qui peut appeler des fonctions spéciales, cela commandpeut être fait dans un environnement de sous - shell . Ainsi, alors que shell état actuel défini dans bâton pourrait à la coquille actuelle - certainement read« s $var1et $var2a - au moins les résultats de la ligne de commande définit probablement ne devrait pas ...
Commandes simples
Si aucun nom de commande ne résulte, ou si le nom de commande est une fonction ou une fonction intégrée spéciale, les affectations de variables affecteront l'environnement d'exécution actuel. Sinon, les affectations de variables doivent être exportées pour l'environnement d'exécution de la commande et ne doivent pas affecter l'environnement d'exécution actuel.
Maintenant, que commandla capacité de ou non d'être à la fois un buildin standard et d'appeler directement des builds spéciaux soit juste une sorte d'échappatoire inattendue en ce qui concerne la ligne de commande, je ne sais pas, mais je sais qu'au moins les quatre shells sont déjà mentionné honorer l' commandespace de noms.
Et bien commandqu'il ne puisse pas appeler directement les fonctions shell, il peut appeler evalcomme démontré, et peut donc le faire indirectement. J'ai donc construit un wrapper d'espace de noms sur ce concept. Il prend une liste d'arguments comme:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... sauf que le commandmot ci-dessus n'est reconnu comme un que s'il peut être trouvé avec un vide $PATH. En plus des variables shell-cadrage appelé localement sur la ligne de commande, localement-champs d' application également toutes variables avec des noms simples alphabétiques minuscules et une liste d'autres les standards, tels que $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDet quelques autres.
Et oui, en limitant la portée localement la $PWDet les $OLDPWDvariables et ensuite explicitement cdà accepter d' $OLDPWDet $PWDil peut assez fiable portée le répertoire de travail actuel ainsi. Ce n'est pas garanti, bien qu'il essaie assez fort. Il conserve un descripteur pour 7<.et lorsque sa cible de retour revient, il le fait cd -P /dev/fd/7/. Si le répertoire de travail actuel a été unlink()dans l'intervalle, il devrait au moins réussir à y revenir mais émettra une erreur laide dans ce cas. Et parce qu'il maintient le descripteur, je ne pense pas qu'un noyau sain d'esprit ne devrait pas non plus pouvoir démonter son périphérique racine (???) .
Il étend également localement les options du shell et les restaure dans l'état dans lequel il les a trouvées lorsque son utilitaire encapsulé revient. Il traite $OPTSspécialement en ce qu'il conserve une copie dans sa propre portée dont il attribue initialement la valeur $-. Après avoir également géré toutes les affectations sur la ligne de commande, il le fera set -$OPTSjuste avant d'appeler sa cible de bouclage. De cette façon, si vous définissez -$OPTSsur la ligne de commande, vous pouvez définir les options de shell de votre cible d'habillage. Lorsque la cible revient, elle va set +$- -$OPTSavec sa propre copie de $OPTS (qui n'est pas affectée par la ligne de commande définie) et tout restaurer à l'état d'origine.
Bien sûr, rien n'empêche l'appelant de returrnsortir explicitement de la fonction d'une manière ou d'une autre par le biais de la cible de bouclage ou de ses arguments. Cela empêchera toute restauration / nettoyage d'état qu'il tenterait autrement.
Pour faire tout ce qu'il faut, il faut aller à trois eval. D'abord, il s'enveloppe dans une portée locale, puis, de l'intérieur, il lit les arguments, les valide pour les noms de shell valides et quitte avec erreur s'il en trouve un qui ne l'est pas. Si tous les arguments sont valides et finalement une cause command -v "$1"renvoie true (rappel: $PATHest vide à ce stade) , evalla ligne de commande définit et transmet tous les arguments restants à la cible de bouclage (bien qu'elle ignore le cas spécial de ns- car cela ne pas très utile, et trois evals de profondeur est plus que suffisant) .
Cela fonctionne essentiellement comme ceci:
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<.
Il y a quelques autres, et redirections et quelques tests étranges à faire avec la façon dont certains obus mis cen $-puis refusent de l' accepter comme une option set (???) , mais son tout accessoire, et principalement utilisés pour économiser d'émettre sortie indésirable et similaire dans les cas de bord. Et c'est comme ça que ça marche. Il peut faire ces choses car il configure sa propre portée locale avant d'appeler son utilitaire encapsulé dans un tel imbriqué.
C'est long, car j'essaie d'être très prudent ici - trois, evalsc'est dur. Mais avec cela, vous pouvez faire:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
Aller plus loin et espacer de manière persistante la portée locale de l'utilitaire encapsulé ne devrait pas être très difficile. Et même tel qu'il est écrit, il définit déjà une $LOCALSvariable pour l'utilitaire encapsulé qui se compose uniquement d'une liste séparée par des espaces de tous les noms qu'il a définis dans l'environnement de l'utilitaire encapsulé.
Comme:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... qui est parfaitement sûr - $IFSa été nettoyé à sa valeur par défaut et seuls les noms de shell valides en font partie, $LOCALSsauf si vous le définissez vous-même sur la ligne de commande. Et même s'il peut y avoir des caractères globaux dans une variable divisée, vous pouvez également définir OPTS=fsur la ligne de commande pour que l'utilitaire encapsulé interdise leur expansion. Dans tout les cas:
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
Et voici la fonction. Toutes les commandes sont préfixées w / \pour éviter les aliasextensions:
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 ). Mais ce n'est pas tout à fait ce que vous recherchez. Je suppose que vous pourriez faire( stuff in subshell; exec env ) | sed 's/^/namespace_/'etevalle résultat dans le shell parent, mais c'est un peu méchant.