Spazi dei nomi della shell


10

C'è un modo per sourceuno script di shell in uno spazio dei nomi, preferibilmente uno script di shell bash ma guarderei in altre shell se avessero questa funzionalità e bash no.

Ciò che intendo con ciò è, ad esempio, qualcosa come "prefisso tutti i simboli definiti con qualcosa in modo che non si scontrino con simboli già definiti (nomi di variabili, nomi di funzioni, alias)" o qualsiasi altra funzione che impedisce le collisioni di nomi.

Se c'è una soluzione in cui posso namespace al sourcemomento ( NodeJSstile), sarebbe la migliore.

Codice di esempio:

$ 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 

1
Grazie per il chiarimento. Mi aspetto che la risposta sia negativa. Il consueto paradigma di programmazione della shell è che quando si desidera isolare le modifiche, lo si fa in una subshell, la cui creazione è solo una questione di ( easiest thing ever ). Ma non è proprio quello che stai cercando. Immagino che tu possa fare ( stuff in subshell; exec env ) | sed 's/^/namespace_/'e evalil risultato nella shell genitore, ma è un po 'brutto.
Celada,

3
Sì. Ottieni ksh93. I namespace sono fondamentali per questo - e tutti i suoi tipi di nomi (che sono anche tipizzabili) supportano il namespace. È anche molto più veloce praticamente sotto ogni aspetto rispetto bash, tra l'altro.
Mikeserv,

@mikeserv Grazie, se lo aggiungi come risposta con un esempio di codice che dimostra la funzionalità, lo accetterò.
PSkocik,

@michas Avrei bisogno anche dei simboli e degli alias della funzione namespace. env | sed ...funzionerebbe per le variabili, potrei fare setper ottenere le funzioni, ma la ricerca e la sostituzione sarebbero un problema: le funzioni possono chiamarsi a vicenda e tu quindi dovrai sostituire tutte le invocazioni incrociate con invocazioni incrociate prefissate ma senza sostituire il stesse parole altrove nel codice di definizione della funzione, dove non è una chiamata. Per questo avresti bisogno di un parser bash, non solo di una regex, e funzionerebbe comunque solo fino a quando le funzioni non si chiameranno tramite eval.
PSkocik,

Risposte:


11

Da man kshun sistema con un ksh93installato ...

  • Spazi dei nomi
    • Comandi e funzioni che vengono eseguiti come parte dell'elenco di un namespacecomando che modifica le variabili o ne crea di nuove, crea una nuova variabile il cui nome è il nome dello spazio dei nomi come indicato dall'identificatore preceduto da .. Quando si fa riferimento a una variabile il cui nome è name, viene prima cercata per l'utilizzo .identifier.name.
    • Allo stesso modo, una funzione definita da un comando nell'elenco dello spazio dei nomi viene creata utilizzando il nome dello spazio dei nomi preceduto da a ..
    • Quando l'elenco di un comando namespace contiene un namespacecomando, i nomi delle variabili e delle funzioni create sono costituiti dalla variabile o dal nome della funzione preceduto dall'elenco di identificatori preceduto da ciascuno .. Al di fuori di uno spazio dei nomi, è possibile fare riferimento a una variabile o funzione creata all'interno di uno spazio dei nomi precedendolo con il nome dello spazio dei nomi.
    • Per impostazione predefinita, le variabili che fissano .shsono nello shspazio dei nomi.

E, per dimostrarlo, ecco il concetto applicato a uno spazio dei nomi fornito di default per ogni variabile di shell normale assegnata in una ksh93shell. Nel seguente esempio definirò una disciplinefunzione che fungerà da .getmetodo assegnato per la $PS1variabile shell. Ogni variabile di shell ottiene fondamentalmente un proprio spazio dei nomi con, almeno, il default get, set, append, e unsetmetodi. Dopo aver definito la seguente funzione, ogni volta che si $PS1fa riferimento alla variabile nella shell, l'output di dateverrà disegnato nella parte superiore dello schermo ...

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

(Notare anche la mancanza della ()subshell nella sostituzione del comando precedente)

Tecnicamente, gli spazi dei nomi e le discipline non sono esattamente la stessa cosa (perché le discipline possono essere definite per essere applicate globalmente o localmente a un determinato spazio dei nomi ) , ma sono parte integrante della concettualizzazione dei tipi di dati della shell a cui è fondamentale ksh93.

Per affrontare i tuoi esempi particolari:

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!

@PSkocik - perché non hai risolto la mia cosa ehoj ? Avrei potuto giurare che era quello che diceva prima ... mi dispiace per quello. Non avrei accettato una risposta scritta da qualcuno che non si è nemmeno preso la briga di scrivere correttamente le parole che ho usato nella domanda ... Onestamente, però, penso di ricordare di aver semplicemente copiato / incollato jt ... hmm ...
Mikeserv,

2

Ho scritto una funzione di shell POSIX che potrebbero essere utilizzate per localmente namespace un builtin di shell o una funzione in una qualsiasi delle ksh93, dash, mksh, o bash (chiamato proprio perché ho personalmente confermato di lavorare in tutti questi) . Delle shell in cui l'ho provato, non è riuscito a soddisfare le mie aspettative yashe non mi sarei mai aspettato che funzionasse zsh. Non ho provato posh. Ho rinunciato a qualche speranza poshqualche tempo fa e non l'ho installato da un po 'di tempo. Forse funziona in posh...?

Dico che è POSIX perché, dalla mia lettura della specifica, sfrutta un comportamento specificato di un'utilità di base, ma, certamente, la specifica è vaga in questo senso e, almeno una persona apparentemente non è d'accordo con me. Generalmente ho avuto un disaccordo con questo, alla fine ho scoperto che l'errore era mio, e forse stavolta mi sbaglio anche riguardo alle specifiche, ma quando lo ho interrogato ulteriormente non ha risposto.

Come ho detto, tuttavia, questo funziona sicuramente nelle shell sopra menzionate e funziona, sostanzialmente, nel modo seguente:

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

3
empty

Il commandcomando è specificato come un'utilità sostanzialmente disponibile e uno dei $PATHpredefiniti predefiniti. Una delle sue funzioni specificate è quella di avvolgere speciali utility integrate nel proprio ambiente quando le si chiama, e quindi ...

{       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

... il comportamento di entrambe le assegnazioni della riga di comando sopra è corretto dalle specifiche. Anche il comportamento di entrambe le condizioni di errore è corretto e in realtà è quasi completamente duplicato lì dalle specifiche. Le assegnazioni con prefisso alle righe di comando di funzioni o builtin speciali vengono specificate per influenzare l'ambiente di shell corrente. Allo stesso modo, gli errori di reindirizzamento sono indicati come fatali se puntati su uno di questi. commandè specificato per sopprimere il trattamento speciale di builtin speciali in quei casi e il caso di reindirizzamento è effettivamente dimostrato dall'esempio nella specifica.

I builtin regolari, come command, d'altra parte, sono specificati per essere eseguiti in un ambiente subshell - il che non significa necessariamente quello di un altro processo , solo che dovrebbe essere fondamentalmente indistinguibile da uno. I risultati della chiamata a un normale built-in dovrebbero sempre assomigliare a ciò che potrebbe essere ottenuto da un $PATHcomando altrettanto capace . E così...

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

Ma il commandcomando non può chiamare le funzioni della shell e quindi non può essere usato per rendere il loro moot di trattamento speciale come può fare per i builtin regolari. Anche questo è specificato. In effetti, la specifica afferma che un'utilità primaria di commandè che è possibile utilizzarla all'interno di una funzione shell wrapper denominata per un altro comando per chiamare quell'altro comando senza ricorsione automatica perché non chiamerà la funzione. Come questo:

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

Se non la utilizzassi commandlì, la cdfunzione sarebbe quasi sicuramente segfault per l'auto-ricorsione.

Ma come un normale builtin che può chiamare builtin speciali commandpuò farlo in un ambiente subshell . E così, mentre la corrente di shell-stato definito all'interno bastone forze per la shell corrente - certamente reads' $var1e $var2ha fatto - almeno i risultati di definisce riga di comando probabilmente non dovrebbe ...

Comandi semplici

Se non risulta alcun nome di comando o se il nome del comando è un built-in o una funzione speciale, le assegnazioni di variabili influiscono sull'ambiente di esecuzione corrente. In caso contrario, le assegnazioni delle variabili devono essere esportate per l'ambiente di esecuzione del comando e non devono influire sull'ambiente di esecuzione corrente.

Ora, se commandla capacità di essere sia un normale builtin sia di chiamare direttamente builtin speciali è solo una sorta di scappatoia inaspettata per quanto riguarda le definizioni della riga di comando, non lo so, ma so che almeno le quattro shell sono già menzionato onora lo commandspazio dei nomi.

E sebbene commandnon possa chiamare direttamente le funzioni di shell, può chiamare evalcome dimostrato, e quindi può farlo indirettamente. Quindi ho creato un wrapper dello spazio dei nomi su questo concetto. Prende un elenco di argomenti come:

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

... tranne che la commandparola sopra è riconosciuta come una solo se può essere trovata con un vuoto $PATH. Oltre localmente-scoping variabili di shell nome sulla riga di comando, ma anche a livello locale-ambiti tutte le variabili con i nomi di singoli minuscoli alfabetici e un elenco di altri quelli standard, come $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDe alcuni altri.

E sì, dal scoping localmente la $PWDe $OLDPWDvariabili e poi esplicitamente cding per $OLDPWDe $PWDsi può ragionevolmente affidabile portata la directory di lavoro corrente pure. Questo non è garantito, anche se ci prova molto. Conserva un descrittore per 7<.e quando restituisce il suo target a capo automatico cd -P /dev/fd/7/. Se la directory di lavoro corrente è stata unlink()nel frattempo, dovrebbe comunque riuscire a cambiarla di nuovo, ma in tal caso emetterà un brutto errore. E poiché mantiene il descrittore, non penso che un kernel sano dovrebbe consentire nemmeno il montaggio del suo dispositivo root (???) .

Inoltre individua localmente le opzioni della shell e le ripristina allo stato in cui le ha trovate quando ritorna la sua utility wrapped. Tratta $OPTSspecialmente in quanto mantiene una copia nel proprio ambito di cui inizialmente assegna il valore $-. Dopo aver gestito anche tutti i compiti sulla riga di comando, lo farà set -$OPTSappena prima di invocare il suo obiettivo di a capo. In questo modo, se si definisce -$OPTSdalla riga di comando, è possibile definire le opzioni di shell di un target a capo. Quando il target ritorna, lo farà set +$- -$OPTScon la propria copia di $OPTS (che non è influenzata dalla riga di comando definita) e ripristinerà tutto allo stato originale.

Naturalmente, non c'è nulla che impedisce al chiamante di returrnuscire in qualche modo esplicitamente dalla funzione tramite il target a capo o i suoi argomenti. Ciò eviterà qualsiasi ripristino / pulizia dello stato che altrimenti tenterebbe.

Per fare tutto ciò che serve per andare in evalprofondità tre . Prima si avvolge in un ambito locale, quindi, dall'interno, legge gli argomenti, li convalida per nomi di shell validi e si chiude con errori se ne trova uno che non lo è. Se tutti gli argomenti sono validi e alla fine uno causa la command -v "$1"restituzione di true (richiamo: $PATHè vuoto a questo punto) , evalla riga di comando definisce e passa tutti gli argomenti rimanenti al target a capo (anche se ignora il caso speciale di ns- perché sarebbe molto utile, e tre di evalprofondità è più che abbastanza profondo) .

In pratica funziona così:

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<.

Ci sono alcuni altri redirezioni e, e un paio di prove strane a che fare con il modo in cui alcune conchiglie messo cin $-e poi si rifiutano di accettarla come opzione a set (???) , ma tutti i suoi accessori, e in primo luogo utilizzati solo per salvare dalla emettono output indesiderato e simili in casi limite. Ed è così che funziona. Può fare queste cose perché imposta il proprio ambito locale prima di chiamare la sua utilità wrapped in un tale nidificato.

È lungo, perché provo a stare molto attento qui - tre evalsè difficile. Ma con esso puoi fare:

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

still_local global
 global

Fare un ulteriore passo avanti e persistentemente nello spazio dei nomi dell'ambito locale dell'utilità spostata non dovrebbe essere molto difficile. E anche come scritto definisce già una $LOCALSvariabile per l'utilità wrapped che comprende solo un elenco separato da spazi di tutti i nomi che ha definito nell'ambiente dell'utility wrapped.

Piace:

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

... che è perfettamente sicuro - $IFSè stato sterilizzato al suo valore predefinito e solo i nomi di shell validi lo fanno a $LOCALSmeno che non lo imposti tu stesso sulla riga di comando. E anche se potrebbero esserci caratteri glob in una variabile divisa, è possibile impostare anche OPTS=fla riga di comando affinché l'utilità wrapped ne proibisca l'espansione. In ogni 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      

Ed ecco la funzione. Tutti i comandi hanno il prefisso w / \per evitare aliasespansioni:

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
}

Molto intelligente! Un modello simile viene utilizzato per ottenere più o meno la stessa cosa qui: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.