Tôi đã viết một chức năng vỏ POSIX mà có thể được sử dụng để địa phương không gian tên một BUILTIN vỏ hoặc chức năng trong bất kỳ ksh93
, dash
, mksh
, hoặc bash
(tên đặc biệt bởi vì tôi đã đích thân khẳng định nó để làm việc trong tất cả các) . Trong số các vỏ mà tôi đã thử nghiệm nó, nó chỉ không đáp ứng được kỳ vọng của tôi yash
và tôi không bao giờ mong đợi nó hoạt động được zsh
. Tôi đã không kiểm tra posh
. Tôi đã từ bỏ mọi hy vọng trong posh
một thời gian trước đây và đã không cài đặt nó trong một thời gian. Có lẽ nó hoạt động trong posh
...?
Tôi nói đó là POSIX bởi vì, khi tôi đọc thông số kỹ thuật, nó lợi dụng một hành vi được chỉ định của một tiện ích cơ bản, nhưng, thừa nhận, đặc tả này rất mơ hồ về vấn đề này, và, ít nhất một người rõ ràng không đồng ý với tôi. Nói chung tôi đã có một sự bất đồng với vấn đề này, cuối cùng tôi đã tìm thấy lỗi là của riêng tôi và có lẽ tôi cũng sai lần này về thông số kỹ thuật, nhưng khi tôi hỏi anh ta thêm thì anh ta không trả lời.
Như tôi đã nói, mặc dù, điều này chắc chắn hoạt động trong các vỏ đã nói ở trên, và về cơ bản, nó hoạt động theo cách sau:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
Các command
lệnh được quy định như một tiện ích về cơ bản có sẵn và một trong những trước $PATH
'd builtins. Một trong những chức năng được chỉ định của nó là bọc các tiện ích dựng sẵn đặc biệt trong môi trường riêng của nó khi gọi chúng, và vì vậy ...
{ 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
... hành vi của cả hai bài tập dòng lệnh ở trên là chính xác bởi spec. Hành vi của cả hai điều kiện lỗi cũng đúng, và trên thực tế gần như hoàn toàn trùng lặp ở đó từ đặc tả. Việc gán tiền tố cho các dòng lệnh của hàm hoặc hàm dựng đặc biệt được chỉ định để ảnh hưởng đến môi trường shell hiện tại. Tương tự, các lỗi chuyển hướng được chỉ định là nghiêm trọng khi chỉ vào một trong hai lỗi đó. command
được chỉ định để loại bỏ việc xử lý đặc biệt các nội dung đặc biệt trong các trường hợp đó và trường hợp chuyển hướng thực sự được thể hiện bằng ví dụ trong đặc tả.
command
Mặt khác, các phần tử thông thường, như , được chỉ định để chạy trong môi trường lớp con - điều này không nhất thiết có nghĩa là của một quá trình khác , chỉ là nó không thể phân biệt về cơ bản với một quy trình. Kết quả của việc gọi một nội dung thông thường phải luôn giống với những gì có thể thu được từ một $PATH
lệnh có khả năng tương tự . Và vì thế...
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
Nhưng command
lệnh không thể gọi các hàm shell và vì vậy không thể được sử dụng để hiển thị mô hình xử lý đặc biệt của chúng như có thể cho các nội trang thông thường. Đó cũng là thông số kỹ thuật. Trong thực tế, thông số kỹ thuật nói rằng một tiện ích chính command
là bạn có thể sử dụng nó trong hàm shell shell được đặt tên cho một lệnh khác để gọi lệnh đó mà không tự đệ quy vì nó sẽ không gọi hàm. Như thế này:
cd(){ command cd -- "$1"; }
Nếu bạn không sử dụng command
ở đó, cd
chức năng gần như chắc chắn sẽ phân tách để tự đệ quy.
Nhưng như một nội dung thông thường có thể gọi các nội dung đặc biệt command
có thể làm như vậy trong môi trường phụ . Và vì vậy, trong khi trạng thái shell hiện tại được xác định bên trong có thể dính vào shell hiện tại - chắc chắn read
là $var1
và $var2
đã làm - ít nhất là kết quả của định nghĩa dòng lệnh có lẽ không nên ...
Lệnh đơn giản
Nếu không có kết quả tên lệnh hoặc nếu tên lệnh là hàm hoặc hàm tích hợp đặc biệt, các phép gán biến sẽ ảnh hưởng đến môi trường thực thi hiện tại. Mặt khác, các phép gán biến sẽ được xuất cho môi trường thực thi của lệnh và sẽ không ảnh hưởng đến môi trường thực thi hiện tại.
Bây giờ command
khả năng có phải là một nội dung thông thường hay gọi trực tiếp các nội dung đặc biệt hay không chỉ là một lỗ hổng bất ngờ liên quan đến định nghĩa dòng lệnh mà tôi không biết, nhưng tôi biết rằng ít nhất bốn vỏ đã đề cập đến danh dự command
không gian tên.
Và mặc dù command
không thể gọi trực tiếp các hàm shell, nó có thể gọi eval
như đã trình bày và do đó có thể thực hiện một cách gián tiếp. Vì vậy, tôi đã xây dựng một trình bao bọc không gian tên trên khái niệm này. Nó nhận một danh sách các đối số như:
ns any=assignments or otherwise=valid names which are not a command then all of its args
... ngoại trừ command
từ ở trên chỉ được công nhận là một nếu nó có thể được tìm thấy với một khoảng trống $PATH
. Bên cạnh đó do địa phương xác định phạm vi biến vỏ tên trên dòng lệnh, nó cũng do địa phương phạm vi tất cả các biến với thấp hơn hợp cụ thể tên chữ cái duy nhất và một danh sách những tiêu chuẩn khác, chẳng hạn như $PS3
, $PS4
, $OPTARG
, $OPTIND
, $IFS
, $PATH
, $PWD
, $OLDPWD
và một số người khác.
Và có, bằng cách xác định phạm vi cục bộ $PWD
và $OLDPWD
các biến và sau đó rõ ràng là cd
ing $OLDPWD
và $PWD
nó hoàn toàn có thể xác định phạm vi thư mục làm việc hiện tại. Điều này không được đảm bảo, mặc dù nó rất cố gắng. Nó giữ lại một mô tả cho 7<.
và khi mục tiêu bọc của nó trả về nó cd -P /dev/fd/7/
. Nếu thư mục làm việc hiện tại đã bị unlink()
tạm thời, thì ít nhất nó vẫn có thể xoay sở để thay đổi trở lại nhưng sẽ phát ra một lỗi xấu trong trường hợp đó. Và bởi vì nó duy trì bộ mô tả, tôi không nghĩ rằng một nhân lành mạnh sẽ cho phép thiết bị gốc của nó không bị ngắt kết nối (???) .
Nó cũng phạm vi các tùy chọn vỏ phạm vi cục bộ và khôi phục các tùy chọn này về trạng thái mà nó tìm thấy chúng khi tiện ích được bao bọc của nó trả về. Nó xử lý $OPTS
đặc biệt ở chỗ nó duy trì một bản sao trong phạm vi riêng mà ban đầu nó gán giá trị của $-
. Sau khi xử lý tất cả các nhiệm vụ trên dòng lệnh, nó sẽ thực hiện set -$OPTS
ngay trước khi gọi mục tiêu gói của nó. Theo cách này nếu bạn xác định -$OPTS
trên dòng lệnh, bạn có thể xác định các tùy chọn vỏ của mục tiêu bọc của mình. Khi mục tiêu trả về, nó sẽ set +$- -$OPTS
có bản sao của chính nó $OPTS
(không bị ảnh hưởng bởi dòng lệnh xác định) và khôi phục tất cả về trạng thái ban đầu.
Tất nhiên, không có gì ngăn người gọi từ một cách rõ ràng returrn
ra khỏi chức năng bằng cách bao bọc mục tiêu hoặc các đối số của nó. Làm như vậy sẽ ngăn chặn bất kỳ sự phục hồi / dọn dẹp nhà nước nào nếu không nó sẽ cố gắng.
Để làm tất cả những gì nó cần phải đi eval
sâu ba . Đầu tiên, nó tự bọc trong một phạm vi cục bộ, sau đó, từ bên trong, nó đọc các đối số, xác thực chúng cho các tên shell hợp lệ và thoát khỏi lỗi nếu nó tìm thấy một cái không có. Nếu tất cả các đối số là hợp lệ và cuối cùng một nguyên nhân command -v "$1"
sẽ trả về true (hồi tưởng: $PATH
trống vào thời điểm này) thì eval
dòng lệnh sẽ xác định và chuyển tất cả các đối số còn lại sang mục tiêu bao bọc (mặc dù nó bỏ qua trường hợp đặc biệt cho ns
- vì điều đó sẽ không Sẽ rất hữu ích và eval
sâu ba giây là đủ sâu) .
Về cơ bản nó hoạt động như thế này:
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ó một số chuyển hướng khác và, và một vài thử nghiệm kỳ lạ để thực hiện theo cách một số vỏ được đưa c
vào $-
và sau đó từ chối chấp nhận nó như một tùy chọn cho set
(???) , nhưng tất cả đều phụ trợ, và chủ yếu được sử dụng chỉ để tiết kiệm từ việc phát ra đầu ra không mong muốn và tương tự trong các trường hợp cạnh. Và đó là cách nó hoạt động. Nó có thể làm những điều đó bởi vì nó thiết lập phạm vi cục bộ của riêng mình trước khi gọi tiện ích được bao bọc của nó trong một cái lồng như vậy.
Nó dài lắm, vì tôi cố gắng hết sức cẩn thận ở đây - ba evals
là khó. Nhưng với nó bạn có thể làm:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
Thực hiện một bước xa hơn và liên tục đặt tên cho phạm vi cục bộ của tiện ích được bao bọc không phải là rất khó khăn. Và ngay cả khi được viết, nó đã định nghĩa một $LOCALS
biến cho tiện ích được bao bọc, chỉ bao gồm một danh sách được phân tách bằng dấu cách của tất cả các tên được xác định trong môi trường của tiện ích được bao bọc.
Giống:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
... Điều này hoàn toàn an toàn - $IFS
đã được khử trùng theo giá trị mặc định của nó và chỉ các tên shell hợp lệ mới thực hiện được $LOCALS
trừ khi bạn tự đặt nó trên dòng lệnh. Và ngay cả khi có thể có các ký tự toàn cầu trong một biến phân tách, bạn cũng có thể đặt OPTS=f
trên dòng lệnh cũng như tiện ích được bao bọc để cấm mở rộng chúng. Trong bất kỳ trường hợp nào:
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
Và đây là chức năng. Tất cả các lệnh đều có tiền tố w / \
để tránh alias
mở rộng:
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 )
. Nhưng đó không hoàn toàn là những gì bạn đang theo đuổi. Tôi đoán bạn có thể làm( stuff in subshell; exec env ) | sed 's/^/namespace_/'
vàeval
kết quả trong vỏ cha mẹ nhưng điều đó thật khó chịu.