Простори імен оболонок


10

Чи є шлях до sourceсценарію оболонки в простір імен, бажано скрипта bash shell, але я би заглянув у інші оболонки, якби вони мали цю функцію, а bash - ні.

Я маю на увазі під цим, наприклад, щось на зразок "приставки всіх визначених символів з чимось таким чином, щоб вони не стикалися з уже визначеними символами (назви змінних, назви функцій, псевдоніми)" або будь-яким іншим засобом, що запобігає зіткненням імен.

Якщо є рішення, де я можу sourceвчасно простір імен ( NodeJSстиль), це було б найкраще.

Приклад коду:

$ 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
Дякуємо за роз’яснення. Я очікую, що відповідь негативна. Звичайна парадигма програмування оболонок полягає в тому, що, коли ви хочете ізолювати зміни, ви робите це в нижній частині, створивши яку, саме про ( easiest thing ever ). Але це не зовсім те, що ти шукаєш. Я думаю, ви могли б зробити ( stuff in subshell; exec env ) | sed 's/^/namespace_/'і evalрезультат у батьківській оболонці, але це дуже неприємно.
Селада

3
Так. Отримайте ksh93. Простори імен є основними для нього - і всі його типи імен (які також можна вводити) підтримують простір імен. Це також набагато швидше практично в будь-якому відношенні, ніж bash, до речі.
mikeserv

@mikeserv Дякую, якщо ви додасте його як відповідь із прикладом коду, який демонструє функціональність, я прийму його.
PSkocik

@michas Мені також знадобиться функція простору імен, символи та псевдоніми. env | sed ...може працювати для змінних, я можу зробити, setщоб отримати функції, але пошук і заміна буде проблемою - функції можуть дзвонити один одному, і ви, тому вам потрібно буде замінити всі перехресні виклики на попередньо встановлені перехресні виклики, але без заміни ті ж слова в іншому місці коду визначення функції, де це не виклик. Для цього вам знадобиться аналізатор bash, а не просто регулярний вираз, і він буде працювати лише до тих пір, поки функції не дзвонять один одному через eval.
PSkocik

Відповіді:


11

З man kshсистеми зі ksh93встановленим ...

  • Іменні простори
    • Команди та функції, які виконуються як частина списку namespaceкоманд, що змінюють змінні або створюють нові, створюють нову змінну, ім'я якої - ім'я простору імен, як задано ідентифікатором, якому передує .. Коли посилання на змінну, на ім'я якої є ім'я, спочатку шукається для використання .identifier.name.
    • Аналогічно, функція, визначена командою у списку простору імен, створюється за допомогою імені простору імен, перед яким a ..
    • Коли список команд простору імен містить namespaceкоманду, імена змінних та функцій, що створюються, складаються із змінної або назви функції, передуючий списку ідентифікаторів, кожному з яких передує .. Поза межами простору імен змінна або функція, створена всередині простору імен, може посилатися на неї, передуючи їй імені простору імен.
    • За замовчуванням змінні, з .shякими дивиться, знаходяться в shпросторі імен.

Для демонстрації ось концепція, застосована до простору імен, що надається за замовчуванням для кожної звичайної змінної оболонки, призначеної в ksh93оболонці. У наступному прикладі я визначу disciplineфункцію, яка буде виконувати роль призначеного .getметоду для $PS1змінної оболонки. Кожна змінна оболонки в основному отримує свій власний простір імен, по крайней мере, по замовчуванням get, set, appendі unsetметоди. Визначивши наступну функцію, щоразу, коли $PS1посилається на змінну в оболонці, результат звернення dateбуде намальований у верхній частині екрана ...

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

(Також відзначте відсутність підрозділу ()в наведеній вище заміні команди)

З технічної точки зору просторів імен і дисципліни НЕ точно те ж саме (оскільки дисципліни можуть бути визначені для застосування глобально або локально до певного простору імен ) , але вони є невід'ємною частиною до концептуалізації підставних типів даних , яка є основою ksh93.

Щоб вирішити ваші конкретні приклади:

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!

... або ...

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

Ahoj, světe!
Hello, World!

@PSkocik - чому ти не виправив мій ehoj ? Я міг присягнути, що це було сказано раніше ... вибачте за це. Я б не прийняв відповідь, яку написав хтось, хто навіть не намагався правильно написати слова, які я використав у питанні ... Чесно кажучи, я думаю, що пам’ятаю лише копіювання / вставлення jt ... хм ...
mikeserv

2

Я написав функцію оболонки POSIX , яка може бути використана для локального простору імен вбудованої команди оболонки або функції в будь-якому з ksh93, dash, mkshабо bash (названих саме тому , що я особисто підтвердив його роботу у всіх з них) . З оболонок, в яких я його випробував, він лише не виправдав моїх очікувань yash, і я ніколи не очікував, що він взагалі спрацює zsh. Я не тестував posh. Я відмовився від надії на poshдеякий час тому і не встановив її протягом певного часу. Може, це працює в posh...?

Я кажу, що це POSIX, тому що, читаючи специфікацію, вона використовує певну поведінку основної утиліти, але, правда, специфіка в цьому відношенні невизначена, і, принаймні, одна людина, мабуть, не погоджується зі мною. Як правило, я мав незгоду з цим, я врешті-решт виявив, що помилка є власною, і, можливо, я також помиляюся цього разу щодо специфікації, але коли я допитував його далі, він не відповів.

Як я вже говорив, це, безумовно, працює у вищезгаданих оболонках, і працює, в основному, таким чином:

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

3
empty

commandКоманда визначена як в основному доступна корисність і одного з попередніх $PATH«D вбудованих команд. Однією з його визначених функцій є загортання спеціальних вбудованих утиліт у власне середовище при їх виклику тощо.

{       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

... поведінка обох присвоєнь командного рядка вище правильно. Поведінка обох умов помилок також правильна, і насправді вона майже повністю дублюється із специфікації. Призначення з префіксом командних рядків функцій або спеціальних вбудованих задаються для впливу на поточне середовище оболонки. Аналогічно, помилки перенаправлення визначаються як фатальні, коли вказуються на будь-яку з них. commandзадається для придушення спеціальної обробки спеціальних вбудованих у цих випадках, і випадок перенаправлення фактично демонструється прикладом у специфікації.

Регулярні вбудовані модулі, як command, з іншого боку, визначені для запуску в середовищі нижньої оболонки - що не обов'язково означає, що інший процес , лише те, що він повинен бути принципово невідрізним від одного. Результати виклику звичайного вбудованого повинні завжди нагадувати те, що може бути отримано з аналогічно здатної $PATHкоманди d. І так ...

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

Але commandкоманда не може викликати функції оболонки, і тому її не можна використовувати для відображення їх спеціального інтервалу обробки, як це можливо для звичайних вбудованих файлів. Це теж spec'd. Насправді, специфікація говорить про те, що головна утиліта програми commandполягає в тому, що ви можете використовувати її в рамках функції оболонки обгортки, названої для іншої команди, щоб викликати цю іншу команду без самостійної рекурсії, оскільки вона не буде викликати функцію. Подобається це:

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

Якби ви не використовували commandтам, ця cdфункція була б майже визначена по умолчанню для саморекурсії.

Але як звичайний вбудований модуль, який може викликати спеціальні вбудовані елементи, commandможе робити це в умовах нижньої оболонки . І так, хоча поточний стан оболонки, визначений всередині, може дотримуватися поточної оболонки - звичайно read, так $var1і $var2було - принаймні результати визначених командним рядком, ймовірно, не повинні ...

Прості команди

Якщо ніяких імен команд немає, або якщо ім'я команди є спеціальною вбудованою або функцією, змінні призначення повинні впливати на поточне середовище виконання. В іншому випадку змінні призначення повинні бути експортовані для середовища виконання команди та не повинні впливати на поточне середовище виконання.

Тепер, чи є commandздатність бути як звичайним вбудованим, так і безпосередньо викликати спеціальні вбудовані, - це лише якась несподівана лазівка ​​щодо визначення командного рядка, я не знаю, але я знаю, що принаймні чотири оболонки вже згадана честь commandпростору імен.

І хоча він commandне може безпосередньо викликати функції оболонки, він може дзвонити, evalяк показано, і це може зробити це опосередковано. Тому я побудував обгортку простору імен на цій концепції. Він бере список аргументів, таких як:

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

... за винятком того, що commandслово вище визнається лише одним, якщо його можна знайти з порожнім $PATH. Крім того , локально-оглядові оболонки змінних , названі в командному рядку, він також локально-приціли все змінний з поодинокими малими літерними іменами і списком інших стандартних, такі як $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDі деякими інші.

І так, локально оцінюючи змінні $PWDта $OLDPWDзмінні, а потім явно cdпосилаючись на це, $OLDPWDі $PWDце може досить надійно охоплювати поточний робочий каталог. Це не гарантується, хоча воно дуже старається. Він зберігає дескриптор для, 7<.і коли його ціль обертання повертає, це робить cd -P /dev/fd/7/. Якщо поточний робочий каталог був unlink()'d у проміжку часу, він все одно повинен принаймні встигнути змінити його назад, але в цьому випадку буде видаватися негарна помилка. І тому, що він підтримує дескриптор, я не думаю, що здорове ядро ​​не повинно дозволяти кореневому пристрою також відключати (???) .

Він також локально охоплює параметри оболонки і відновлює їх у тому стані, у якому він їх знайшов, коли повертається його обгорнута утиліта. Це стосується $OPTSспеціально тим, що він зберігає копію у власному обсязі, якому вона спочатку присвоює значення $-. Після обробки всіх завдань у командному рядку це буде виконуватися set -$OPTSбезпосередньо перед викликом його цілі обгортання. Таким чином, якщо ви визначаєте -$OPTSв командному рядку, ви можете визначити параметри оболонки цілі обгортання. Коли ціль повернеться, вона виконає set +$- -$OPTSвласну копію $OPTS (на яку не впливає визначений командний рядок) і відновить усе до початкового стану.

Звичайно, ніщо не заважає абоненту якимось чином явно returrnвийти з функції за допомогою цілі обгортання або її аргументів. Це призведе до запобігання будь-якого відновлення / очищення стану, яке б інакше було здійснено.

Щоб зробити все, що потрібно, щоб пройти три evalглибини. Спочатку він загортається в локальну область, потім зсередини він читає аргументи, перевіряє їх на дійсні імена оболонок і закриває з помилкою, якщо знаходить ту, яка не є. Якщо всі аргументи є дійсними і врешті-решт один з причин command -v "$1"повертає істину (нагадуємо: $PATHв цьому пункті порожній), він evalвизначає командний рядок і передає всі аргументи, що залишилися, до цілі обгортання (хоча він ігнорує особливий випадок для ns- тому що це не Не дуже корисно, а три evalглибини - більш ніж досить глибокі) .

Це в основному працює так:

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

Є деякі інші перепризначення і, і кілька дивних тести , щоб зробити з тим , як деякі оболонками покласти cв , $-а потім відмовляються приймати його в якості опції set (???) , але все підпорядкованості, і в основному використовуються тільки для збереження від випромінюючих небажаний вихід та подібне у крайових випадках. І ось так це працює. Він може робити це, тому що встановлює власну локальну область дії, перш ніж викликати свою обгорнуту утиліту в вкладену таку.

Це довго, бо я намагаюся бути тут дуже обережним - три evalsважко. Але з ним можна зробити:

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

still_local global
 global

Якщо зробити крок далі і наполегливо змінювати місцевий обсяг обгорнутої утиліти, не повинно бути дуже складно. І навіть як написано, він вже визначає $LOCALSзмінну для обгорнутої утиліти, яка складається лише з розділеного пробілом списку всіх імен, визначених у середовищі обгорнутої утиліти.

Подібно до:

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

... що цілком безпечно - $IFSбуло зафіксовано його значення за замовчуванням, і лише допустимі назви оболонок вносять його, $LOCALSякщо ви не встановите його самостійно в командному рядку. І навіть якщо в розділеній змінній можуть бути глобальні символи, ви можете встановити і OPTS=fв командному рядку, щоб загорнута утиліта заборонила їх розширення. У будь-якому випадку:

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      

І ось функція. Усі команди мають префікс w / \щоб уникнути aliasрозширень:

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
}

Дуже розумний! Подібний малюнок використовується для досягнення майже того ж самого тут: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.