Pisałem funkcję powłoki POSIX, które mogą być używane do lokalnego obszaru nazw wbudowanym poleceniem powłoki lub funkcji w dowolnym z ksh93
, dash
, mksh
, lub bash
(o nazwie specjalnie bo osobiście potwierdził go do pracy w każdym z nich) . Spośród powłok, w których go testowałem, nie spełnił on moich oczekiwań yash
i nigdy nie spodziewałem się, że zadziała zsh
. Nie testowałem posh
. Dawno temu zrezygnowałem z jakiejkolwiek nadziei posh
i od jakiegoś czasu jej nie instalowałem. Może to działa w posh
...?
Mówię, że jest to POSIX, ponieważ czytając specyfikację, korzysta ona z określonego zachowania podstawowego narzędzia, ale, co prawda, specyfikacja jest pod tym względem niejasna i przynajmniej jedna osoba najwyraźniej się ze mną nie zgadza. Ogólnie nie zgadzałem się z tym, w końcu uznałem, że błąd jest mój, i być może również tym razem się mylę co do specyfikacji, ale kiedy go przesłuchałem, nie odpowiedział.
Jak już powiedziałem, to zdecydowanie działa w wyżej wymienionych powłokach i działa w zasadzie w następujący sposób:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
command
Komenda jest określony jako w zasadzie dostępne narzędzia oraz z poprzednich $PATH
„d poleceń wbudowanych. Jedną z jego określonych funkcji jest zawijanie specjalnych wbudowanych narzędzi we własnym środowisku podczas ich wywoływania, a więc ...
{ 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
... zachowanie obu powyższych przypisań wiersza poleceń jest poprawne według specyfikacji. Zachowanie obu warunków błędu jest również poprawne i jest w rzeczywistości bardzo prawie całkowicie skopiowane ze specyfikacji. Przypisania poprzedzone wierszami poleceń funkcji lub specjalnych wbudowanych funkcji mają wpływ na bieżące środowisko powłoki. Podobnie błędy przekierowania są określane jako krytyczne, gdy zostaną wskazane na jeden z nich. command
jest określony, aby wykluczyć specjalne traktowanie specjalnych funkcji wbudowanych w tych przypadkach, a przypadek przekierowania jest faktycznie zademonstrowany przez przykład w specyfikacji.
command
Z drugiej strony, zwykłe wbudowane opcje działają w środowisku podpowłoki - co niekoniecznie oznacza proces innego procesu , po prostu powinno być zasadniczo nierozróżnialne. Rezultaty wywołania zwykłego wbudowanego powinny zawsze przypominać to, co można uzyskać z podobnie zdolnego $PATH
polecenia. A więc...
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
Ale command
polecenie nie może wywoływać funkcji powłoki i dlatego nie może być użyte do wywołania ich specjalnego traktowania, jak to ma miejsce w przypadku zwykłych poleceń wbudowanych. To również zostało określone. W rzeczywistości specyfikacja mówi, że podstawowym narzędziem command
jest to, że można jej użyć w ramach funkcji powłoki opakowania o nazwie innej komendy, aby wywołać tę inną komendę bez samoregulacji, ponieważ nie wywoła tej funkcji. Lubię to:
cd(){ command cd -- "$1"; }
Jeśli nie użyjesz command
go, cd
funkcja prawie na pewno ulegnie awarii dla samoregulacji.
Ale jako zwykłe narzędzie wbudowane, które może wywoływać funkcje specjalne, command
może to robić w środowisku podpowłoki . I tak, podczas gdy aktualny stan powłoki zdefiniowane wewnątrz może przykleić się do bieżącej powłoki - na pewno read
„s $var1
, a $var2
nie - przynajmniej wyniki Definiuje wiersza polecenia prawdopodobnie nie powinna ...
Proste polecenia
Jeśli nie pojawi się żadna nazwa polecenia lub jeśli nazwa polecenia jest specjalną funkcją wbudowaną lub funkcją, przypisania zmiennych będą miały wpływ na bieżące środowisko wykonywania. W przeciwnym razie przypisania zmiennych zostaną wyeksportowane do środowiska wykonywania polecenia i nie będą miały wpływu na bieżące środowisko wykonywania.
Teraz, czy command
zdolność do bycia zarówno zwykłym wbudowanym, jak i bezpośrednim wywoływaniem specjalnych wbudowanych jest tylko pewnego rodzaju nieoczekiwaną luką w odniesieniu do definicji wiersza poleceń, nie wiem, ale wiem, że przynajmniej cztery powłoki już wspomniane honorować command
przestrzeń nazw.
I chociaż command
nie może bezpośrednio wywoływać funkcji powłoki, może wywoływać eval
tak, jak pokazano, i może to robić pośrednio. Więc zbudowałem opakowanie przestrzeni nazw na tej koncepcji. Wymaga listy argumentów takich jak:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... z wyjątkiem tego, że command
powyższe słowo jest rozpoznawane jako jedno tylko wtedy, gdy można je znaleźć z pustym $PATH
. Poza lokalnie scoping zmienne powłoki nazwane w linii poleceń, ale również lokalnie celownicze wszystkie zmienne z pojedynczymi małymi alfabetycznych nazwisk oraz listę innych standardowych, takich jak $PS3
, $PS4
, $OPTARG
, $OPTIND
, $IFS
, $PATH
, $PWD
, $OLDPWD
i kilka innych.
I tak, przez lokalnie Scoping $PWD
i $OLDPWD
zmienne, a potem wyraźnie cd
ING $OLDPWD
i $PWD
może to dość wiarygodnie zakres bieżący katalog roboczy, jak również. Nie jest to gwarantowane, choć bardzo się stara. Zachowuje deskryptor, 7<.
a gdy zwróci cel zawijania, robi to cd -P /dev/fd/7/
. Jeśli bieżący katalog roboczy znajdował się unlink()
w międzyczasie, powinien przynajmniej przynajmniej zdążyć wrócić do niego, ale w tym przypadku wyda brzydki błąd. A ponieważ utrzymuje deskryptor, nie sądzę, że rozsądne jądro powinno pozwolić na odmontowanie urządzenia głównego (???) .
Lokalnie wykrywa również opcje powłoki i przywraca je do stanu, w którym je znalazł, gdy wraca zapakowane narzędzie. Traktuje $OPTS
specjalnie, ponieważ zachowuje kopię we własnym zakresie, który początkowo przypisuje wartość $-
. Po obsłudze wszystkich przypisań w wierszu poleceń zrobi to set -$OPTS
tuż przed wywołaniem celu zawijania. W ten sposób, jeśli zdefiniujesz -$OPTS
w wierszu poleceń, możesz zdefiniować opcje powłoki celu zawijania. Kiedy cel powróci, będzie set +$- -$OPTS
posiadał własną kopię $OPTS
(która nie ma wpływu na definicję wiersza poleceń) i przywróci wszystko do pierwotnego stanu.
Oczywiście nic nie powstrzymuje dzwoniącego przed jawnym returrn
wyjściem z funkcji poprzez cel zawijania lub jego argumenty. Takie postępowanie zapobiegnie przywróceniu / oczyszczeniu stanu, które w innym przypadku byłoby podejmowane.
Aby zrobić wszystko, co trzeba, należy przejść trzy eval
. Najpierw opakowuje się w zasięg lokalny, a następnie od wewnątrz wczytuje argumenty, weryfikuje je pod kątem poprawnych nazw powłok i kończy działanie z błędem, jeśli znajdzie taki, który nie jest. Jeśli wszystkie argumenty są poprawne i ostatecznie jedna spowoduje command -v "$1"
zwrócenie wartości true (przypomnij: $PATH
w tym momencie jest pusta), to eval
wiersz poleceń zdefiniuje i przekaże wszystkie pozostałe argumenty do celu zawinięcia (choć ignoruje to specjalny przypadek ns
- ponieważ to nie będą bardzo przydatne, a eval
głębokość trzech s jest więcej niż wystarczająco głęboka) .
Zasadniczo działa tak:
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<.
Istnieje kilka innych przekierowania i, a kilka testów dziwne ze sposobem niektóre muszle umieścić c
w $-
, a następnie odmówić przyjęcia go jako opcja set
(???) , ale jej wszystko pomocniczy, a przede wszystkim wykorzystywane tylko ocalić od emitującego niechciane wyjście i podobne w przypadkach krawędziowych. I tak to działa. Może to robić, ponieważ ustawia swój własny zasięg lokalny przed wywołaniem owiniętego narzędzia w zagnieżdżonym.
Długo, bo staram się tutaj bardzo uważać - trzy evals
są trudne. Ale dzięki niemu możesz zrobić:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
Posunięcie się o krok dalej i uporczywe umieszczanie nazw w lokalnym zasięgu zapakowanego narzędzia nie powinno być bardzo trudne. I nawet jak napisano, już definiuje $LOCALS
zmienną dla opakowanego narzędzia, która składa się tylko z oddzielonej spacjami listy wszystkich nazw, które zdefiniowała w środowisku opakowanego narzędzia.
Lubić:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... co jest całkowicie bezpieczne - $IFS
zostało oczyszczone do wartości domyślnej i wprowadzane są tylko prawidłowe nazwy powłok, $LOCALS
chyba że ustawisz je samodzielnie w wierszu poleceń. I nawet jeśli w zmiennej podzielonej mogą znajdować się znaki globu, można również ustawić OPTS=f
w wierszu polecenia, aby zapakowane narzędzie zabroniło ich ekspansji. W każdym przypadku:
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
A oto funkcja. Wszystkie polecenia mają prefiks w /, \
aby uniknąć alias
rozszerzeń:
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 )
. Ale nie do końca tego szukasz. Myślę, że możesz to zrobić,( stuff in subshell; exec env ) | sed 's/^/namespace_/'
aeval
wynik w powłoce nadrzędnej, ale to trochę nieprzyjemne.