Espaces de noms de shell

10

Existe-t-il un moyen pour sourceun script shell dans un espace de noms, de préférence un script shell bash mais je chercherais dans d'autres shells s'ils avaient cette fonctionnalité et bash n'en avait pas.

Ce que je veux dire par là, par exemple, quelque chose comme "préfixe tous les symboles définis avec quelque chose afin qu'ils n'entrent pas en collision avec des symboles déjà définis (noms de variables, noms de fonction, alias)" ou toute autre fonction qui empêche les collisions de noms.

S'il y a une solution où je peux nommer l' espace de sourcetemps ( NodeJSstyle), ce serait le meilleur.

Exemple de code:

$ 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
la source
1
Merci pour la clarification. J'attends que la réponse soit négative. Le paradigme de programmation shell habituel est que lorsque vous voulez isoler les modifications, vous le faites dans un sous-shell, dont la création est à peu près le ( 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_/'et evalle résultat dans le shell parent, mais c'est un peu méchant.
Celada
3
Oui. Obtenez ksh93. Les espaces de noms sont fondamentaux pour lui - et tous ses types de noms (qui sont également typables) prennent en charge l'espace de noms. C'est aussi beaucoup plus rapide à pratiquement tous égards que bash, soit dit en passant.
mikeserv
@mikeserv Merci, si vous l'ajoutez comme réponse avec un exemple de code qui illustre la fonctionnalité, je l'accepterai.
PSkocik
@michas J'aurais aussi besoin de nommer des symboles de fonction et des alias. env | sed ...fonctionnerait pour les variables, je pourrais faire setpour obtenir des fonctions, mais la recherche et le remplacement seraient un problème - les fonctions peuvent s'appeler et vous devez donc remplacer toutes les invocations croisées par des invocations croisées préfixées mais sans remplacer le mêmes mots ailleurs dans le code de définition de fonction, où ce n'est pas une invocation. Pour cela, vous auriez besoin d'un analyseur bash, pas seulement d'une expression régulière, et cela ne fonctionnerait que tant que les fonctions ne s'appelleraient pas via eval.
PSkocik

Réponses:

11

Depuis man kshun système sur lequel est ksh93installé ...

  • Espaces de noms
    • Les commandes et fonctions qui sont exécutées dans le cadre de la liste d'une namespacecommande qui modifient des variables ou en créent de nouvelles, créent une nouvelle variable dont le nom est le nom de l'espace de noms tel que donné par l'identifiant précédé de .. Lorsqu'une variable dont le nom est nom est référencée, elle est d'abord recherchée à l'aide de .identifier.name.
    • De même, une fonction définie par une commande dans la liste des espaces de noms est créée à l'aide du nom de l'espace de noms précédé d'un ..
    • Lorsque la liste d'une commande namespace contient une namespacecommande, les noms des variables et des fonctions créées sont constitués du nom de la variable ou de la fonction précédé de la liste des identifiants précédés chacun de .. En dehors d'un espace de noms, une variable ou une fonction créée à l'intérieur d'un espace de noms peut être référencée en la précédant du nom de l'espace de noms.
    • Par défaut, les variables commençant par se .shtrouvent dans l' shespace de nom.

Et, pour le démontrer, voici le concept appliqué à un espace de noms fourni par défaut pour chaque variable shell régulière affectée dans un ksh93shell. Dans l'exemple suivant, je définirai une disciplinefonction qui agira comme la .getméthode affectée à la $PS1variable shell. Chaque variable shell obtient essentiellement son propre espace de noms avec, au moins, la valeur par défaut get, set, appendet unsetméthodes. Après avoir défini la fonction suivante, à chaque fois que la variable $PS1est référencée dans le shell, la sortie de datesera dessinée en haut de l'écran ...

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

(Notez également l'absence de ()sous - shell dans la substitution de commandes ci-dessus)

Techniquement, les espaces de noms et les disciplines ne sont pas exactement la même chose (car les disciplines peuvent être définies pour s'appliquer globalement ou localement à un espace de noms particulier ) , mais elles font à la fois partie intégrante de la conceptualisation des types de données shell qui est fondamentale pour ksh93.

Pour répondre à vos exemples particuliers:

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!

...ou...

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

Ahoj, světe!
Hello, World!
mikeserv
la source
@PSkocik - pourquoi n'avez-vous pas réparé mon truc ehoj ? J'aurais pu jurer que c'était ce qu'il disait avant ... désolé. Je n'aurais pas accepté une réponse écrite par quelqu'un qui n'a même pas pris la peine d'épeler correctement les mots que j'ai utilisés dans la question ... Honnêtement, cependant, je pense que je me souviens juste de copier / coller jt ... hmm ...
mikeserv
2

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
}
mikeserv
la source
Très intelligent! Un modèle similaire est utilisé pour obtenir à peu près la même chose ici: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B