Przestrzenie nazw powłoki

10

Czy istnieje sposób na sourceskrypt powłoki w przestrzeń nazw, najlepiej skrypt powłoki bash, ale sprawdziłbym inne powłoki, gdyby miały tę funkcję, a bash nie.

Rozumiem przez to, na przykład, coś w rodzaju „przedrostek wszystkich zdefiniowanych symboli czymś, aby nie kolidowały z już zdefiniowanymi symbolami (nazwy zmiennych, nazwy funkcji, aliasy)” lub jakiekolwiek inne narzędzie, które zapobiega kolizji nazw.

Jeśli istnieje rozwiązanie, w którym mogę jednocześnie używać przestrzeni nazw source( NodeJSstyl), byłoby to najlepsze.

Przykładowy kod:

$ 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
źródło
1
Dziękuję za wyjaśnienie. Oczekuję, że odpowiedź jest przecząca. Zwykłym paradygmatem programowania powłoki jest to, że gdy chcesz izolować zmiany, robisz to w podpowłoce, której utworzenie jest właśnie o ( 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_/'a evalwynik w powłoce nadrzędnej, ale to trochę nieprzyjemne.
Celada,
3
Tak. Dostać ksh93. Przestrzenie nazw są dla niego fundamentalne - a wszystkie typy nazw (które można również wpisać) obsługują przestrzeń nazw. Jest także znacznie szybszy praktycznie pod każdym względem niż bash, nawiasem mówiąc,.
mikeserv
@mikeserv Dzięki, jeśli dodasz go jako odpowiedź z przykładem kodu, który demonstruje funkcjonalność, zaakceptuję.
PSkocik,
@ michas Potrzebowałbym także symboli funkcji i aliasów przestrzeni nazw. env | sed ...działałoby dla zmiennych, mógłbym zrobić, setaby uzyskać funkcje, ale wyszukiwanie i zamiana byłyby problemem - funkcje mogą się nawzajem wywoływać, więc trzeba zastąpić wszystkie wywołania krzyżowe prefiksami krzyżowymi, ale bez zamiany te same słowa w innym miejscu w kodzie definicji funkcji, gdzie nie jest to wywołanie. Do tego potrzebny byłby parser bash, a nie tylko wyrażenie regularne, i nadal działałby tylko tak długo, jak długo funkcje nie wywoływałyby się przez eval.
PSkocik,

Odpowiedzi:

11

Z man kshsystemu z ksh93zainstalowanym ...

  • Przestrzenie nazw
    • Polecenia i funkcje, które są wykonywane jako część listy namespacepolecenia, które modyfikują zmienne lub tworzą nowe, tworzą nową zmienną, której nazwą jest nazwa przestrzeni nazw podana przez identyfikator poprzedzony .. Kiedy przywoływana jest zmienna, której nazwa to name, najpierw szuka się jej za pomocą .identifier.name.
    • Podobnie funkcja zdefiniowana przez polecenie na liście przestrzeni nazw jest tworzona przy użyciu nazwy przestrzeni nazw poprzedzonej znakiem ..
    • Gdy lista poleceń przestrzeni nazw zawiera namespacepolecenie, nazwy tworzonych zmiennych i funkcji składają się z nazwy zmiennej lub funkcji poprzedzonej listą identyfikatorów poprzedzonych .. Poza przestrzenią nazw do zmiennej lub funkcji utworzonej w przestrzeni nazw można się odwoływać, poprzedzając ją nazwą przestrzeni nazw.
    • Domyślnie zmienne oznaczone gwiazdką .shznajdują się w shprzestrzeni nazw.

Aby zademonstrować, oto koncepcja zastosowana do przestrzeni nazw zapewnionej domyślnie dla każdej zwykłej zmiennej powłoki przypisanej w ksh93powłoce. W poniższym przykładzie zdefiniuję disciplinefunkcję, która będzie działać jako przypisana .getmetoda dla $PS1zmiennej powłoki. Każda zmienna powłoki zasadzie dostaje swoją własną przestrzeń nazw z, co najmniej, domyślny get, set, appendoraz unsetmetody. Po zdefiniowaniu następującej funkcji, za każdym razem, gdy zmienna $PS1jest przywoływana w powłoce, dane wyjściowe datezostaną narysowane u góry ekranu ...

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

(Zwróć również uwagę na brak ()podpowłoki w powyższym zastąpieniu polecenia)

Technicznie, przestrzenie nazw i dyscypliny nie są dokładnie tym samym (ponieważ dyscypliny można zdefiniować tak, aby stosowały się globalnie lub lokalnie do konkretnej przestrzeni nazw ) , ale są one nieodłączną częścią konceptualizacji podstawowych typów danych powłoki ksh93.

Aby zająć się konkretnymi przykładami:

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!

...lub...

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

Ahoj, světe!
Hello, World!
mikeserv
źródło
@PSkocik - dlaczego nie naprawiłeś mojej ehoj ? Mógłbym przysiąc, że tak już wcześniej powiedziałem ... przepraszam za to. Nie przyjąłbym odpowiedzi napisanej przez kogoś, kto nawet nie zadał sobie trudu, aby poprawnie przeliterować słowa, których użyłem w pytaniu ... Szczerze mówiąc, myślę, że pamiętam tylko kopiowanie / wklejanie jt ... hmm ...
mikeserv
2

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ń yashi nigdy nie spodziewałem się, że zadziała zsh. Nie testowałem posh. Dawno temu zrezygnowałem z jakiejkolwiek nadziei poshi 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

commandKomenda 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. commandjest określony, aby wykluczyć specjalne traktowanie specjalnych funkcji wbudowanych w tych przypadkach, a przypadek przekierowania jest faktycznie zademonstrowany przez przykład w specyfikacji.

commandZ 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 $PATHpolecenia. 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 commandpolecenie 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 commandjest 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 commandgo, cdfunkcja prawie na pewno ulegnie awarii dla samoregulacji.

Ale jako zwykłe narzędzie wbudowane, które może wywoływać funkcje specjalne, commandmoż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 $var2nie - 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 commandzdolność 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ć commandprzestrzeń nazw.

I chociaż commandnie może bezpośrednio wywoływać funkcji powłoki, może wywoływać evaltak, 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 commandpowyż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, $OLDPWDi kilka innych.

I tak, przez lokalnie Scoping $PWDi $OLDPWDzmienne, a potem wyraźnie cdING $OLDPWDi $PWDmoż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 $OPTSspecjalnie, ponieważ zachowuje kopię we własnym zakresie, który początkowo przypisuje wartość $-. Po obsłudze wszystkich przypisań w wierszu poleceń zrobi to set -$OPTStuż przed wywołaniem celu zawijania. W ten sposób, jeśli zdefiniujesz -$OPTSw wierszu poleceń, możesz zdefiniować opcje powłoki celu zawijania. Kiedy cel powróci, będzie set +$- -$OPTSposiadał 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 returrnwyjś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: $PATHw tym momencie jest pusta), to evalwiersz 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 evalgłę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ć cw $-, 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 evalssą 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 $LOCALSzmienną 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 - $IFSzostało oczyszczone do wartości domyślnej i wprowadzane są tylko prawidłowe nazwy powłok, $LOCALSchyba ż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=fw 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ąć aliasrozszerzeń:

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
źródło
Bardzo mądry! Podobny wzór stosuje się tutaj, aby osiągnąć to samo: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B