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
}
( easiest thing ever ). Ma non è proprio quello che stai cercando. Immagino che tu possa fare( stuff in subshell; exec env ) | sed 's/^/namespace_/'eevalil risultato nella shell genitore, ma è un po 'brutto.