Пространства имен оболочки


10

Есть ли способ sourceдобавить сценарий оболочки в пространство имен, предпочтительно сценарий оболочки bash, но я бы посмотрел другие оболочки, если бы они имели эту функцию, а 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команды, которая изменяет переменные или создает новые, создает новую переменную, имя которой является именем пространства имен, заданным идентификатором, которому предшествует .. Когда указывается переменная, имя которой name, она сначала ищется для использования .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 - почему ты не исправил мою вещь с эхо ? Я мог бы поклясться, что это было сказано раньше ... извините за это. Я бы не принял ответ, написанный кем-то, кто даже не удосужился правильно написать слова, которые я использовал в вопросе ... Честно говоря, хотя, я думаю, я помню, просто скопировать / вставить 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предустановленных. Одна из указанных функций заключается в переносе специальных встроенных утилит в собственную среду при их вызове и т. Д.

{       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. И так...

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команда не может вызывать функции оболочки, и поэтому не может использоваться для отображения их особого отношения к лечению, как для обычных встроенных функций. Это тоже спец. Фактически, спецификация говорит, что основная утилита commandсостоит в том, что вы можете использовать ее в функции оболочки оболочки, названной для другой команды, чтобы вызывать эту другую команду без саморекурсии, потому что она не будет вызывать функцию. Как это:

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

Если бы вы не использовали commandэту cdфункцию, она почти наверняка стала бы segfault для самостоятельной рекурсии.

Но как обычные встроенные функции, которые могут вызывать специальные встроенные функции, 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()в промежуточный период, он все равно должен хотя бы вернуться в него, но в этом случае выдаст ужасную ошибку. И поскольку он поддерживает дескриптор, я не думаю, что здравомыслящее ядро ​​должно позволять размонтировать его корневое устройство (???) .

Он также локально ограничивает параметры оболочки и восстанавливает их до состояния, в котором он их обнаружил, когда возвращается его служебная программа. Это $OPTSособенно относится к тому, что он поддерживает копию в своей области видимости, которой он изначально присваивает значение $-. После обработки всех назначений в командной строке он выполнит это set -$OPTSнепосредственно перед вызовом своей цели переноса. Таким образом, если вы определили -$OPTSв командной строке, вы можете определить параметры оболочки цели wrap. Когда цель возвращается, она будет set +$- -$OPTSсо своей собственной копией $OPTS (на которую не влияет определение командной строки) и восстановит все в исходное состояние.

Конечно, ничто не мешает вызывающей стороне каким-либо образом явно returrnвыйти из функции посредством цели wrap или ее аргументов. Это предотвратит любое восстановление / очистку состояния, которое он в противном случае предпримет.

Чтобы сделать все это, нужно пройти три evalглубины. Сначала он оборачивается в локальную область видимости, затем изнутри читает аргументы, проверяет их на наличие допустимых имен оболочки и завершает работу с ошибкой, если находит то, чего нет. Если все аргументы являются действительными и в конечном итоге один из них command -v "$1"возвращает true ( $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если вы сами не зададите его в командной строке. И даже если в разделенной переменной могут быть символы glob, вы можете также указать 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
Зак Б.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.