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