Không gian tên Shell


10

Có cách nào để sourcetập lệnh shell vào không gian tên, tốt nhất là tập lệnh shell bash nhưng tôi sẽ xem xét các shell khác nếu chúng có tính năng này và bash thì không.

Điều tôi muốn nói là, ví dụ, một cái gì đó như "tiền tố tất cả các ký hiệu được xác định với một cái gì đó để chúng không va chạm với các ký hiệu đã được xác định (tên biến, tên hàm, bí danh)" hoặc bất kỳ cơ sở nào khác ngăn chặn xung đột tên.

Nếu có một giải pháp mà tôi có thể đặt không gian tên tại sourcethời điểm ( NodeJSkiểu), đó sẽ là giải pháp tốt nhất.

Mã ví dụ:

$ 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
Cảm ơn bạn đã làm rõ. Tôi hy vọng rằng câu trả lời là tiêu cực. Mô hình lập trình shell thông thường là khi bạn muốn cô lập các thay đổi, bạn thực hiện nó trong một khung con, việc tạo ra một cái chỉ là về ( 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_/'evalkết quả trong vỏ cha mẹ nhưng điều đó thật khó chịu.
Celada

3
Đúng. Nhận ksh93. Không gian tên là cơ bản cho nó - và tất cả các loại tên của nó (cũng có thể đánh máy) hỗ trợ không gian tên. Trên thực tế, nó cũng nhanh hơn nhiều so với mọi khía cạnh bash.
mikeerv

@mikeerv Cảm ơn, nếu bạn thêm nó dưới dạng câu trả lời với ví dụ mã thể hiện chức năng, tôi sẽ chấp nhận nó.
PSkocik

@michas Tôi cũng cần phải đặt các ký hiệu hàm và bí danh. env | sed ...Tôi sẽ làm việc với các biến, tôi có thể làm setđể có được các hàm, nhưng việc tìm kiếm và thay thế sẽ là một vấn đề - các hàm có thể gọi nhau và vì vậy bạn cần thay thế tất cả các lệnh gọi chéo bằng các lệnh gọi chéo có tiền tố nhưng không thay thế cùng một từ ở nơi khác trong mã định nghĩa hàm, trong đó nó không phải là một lời gọi. Cho rằng bạn cần một trình phân tích cú pháp bash, không chỉ là biểu thức chính quy và nó sẽ vẫn chỉ hoạt động miễn là các hàm không gọi nhau thông qua eval.
PSkocik

Câu trả lời:


11

Từ man kshtrên một hệ thống có ksh93cài đặt ...

  • Tên không gian
    • Các lệnh và hàm được thực thi như một phần của danh sách namespacelệnh sửa đổi biến hoặc tạo biến mới, tạo một biến mới có tên là tên của không gian tên như được đưa ra bởi mã định danh đi trước .. Khi một biến có tên là tên được tham chiếu, đầu tiên nó được tìm kiếm để sử dụng .identifier.name.
    • Tương tự, một hàm được xác định bởi một lệnh trong danh sách không gian tên được tạo bằng tên không gian tên đứng trước a ..
    • Khi danh sách lệnh không gian tên chứa namespacelệnh, tên của các biến và hàm được tạo bao gồm tên biến hoặc tên hàm đứng trước danh sách các mã định danh được đặt trước .. Bên ngoài một không gian tên, một biến hoặc hàm được tạo bên trong một không gian tên có thể được tham chiếu bằng cách đặt trước nó với tên không gian tên.
    • Theo mặc định, các biến nhìn chằm chằm .shvào trong shkhông gian tên.

Và, để chứng minh, đây là khái niệm được áp dụng cho một không gian tên được cung cấp theo mặc định cho mọi biến shell thông thường được gán trong ksh93shell. Trong ví dụ sau tôi sẽ định nghĩa một disciplinehàm sẽ đóng vai trò là .getphương thức được gán cho $PS1biến shell. Mỗi biến shell cơ bản được không gian tên riêng của mình với, ít nhất, mặc định get, set, append, và unsetphương pháp. Sau khi xác định hàm sau, bất cứ khi nào biến $PS1được tham chiếu trong trình bao, đầu ra của datesẽ được rút ra ở trên cùng của màn hình ...

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

(Cũng lưu ý việc thiếu phần con ()trong phần thay thế lệnh trên)

Về mặt kỹ thuật, không gian tênkỷ luật không hoàn toàn giống nhau (vì các quy tắc có thể được định nghĩa để áp dụng trên toàn cầu hoặc cục bộ cho một không gian tên cụ thể ) , nhưng chúng đều là một phần và là phần để khái niệm hóa các loại dữ liệu vỏ là cơ bản ksh93.

Để giải quyết các ví dụ cụ thể của bạn:

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!

...hoặc là...

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

Ahoj, světe!
Hello, World!

@PSkocik - tại sao bạn không sửa điều ehoj của tôi ? Tôi có thể đã thề đó là những gì nó nói trước đây ... xin lỗi về điều đó. Tôi đã không chấp nhận một câu trả lời được viết bởi một người thậm chí không thèm đánh vần những từ tôi đã sử dụng trong câu hỏi một cách chính xác ... Thành thật mà nói, tôi nghĩ rằng tôi chỉ nhớ sao chép / dán jt ... hmm ...
mikeerv

2

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 yashvà 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 poshmộ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 commandlệ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ả.

commandMặ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 $PATHlệ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 commandlệ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 commandlà 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ở đó, cdchứ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 commandcó 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$var1$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ờ commandkhả 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ự commandkhông gian tên.

Và mặc dù commandkhông thể gọi trực tiếp các hàm shell, nó có thể gọi evalnhư đã 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ừ commandtừ ở 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, $OLDPWDvà một số người khác.

Và có, bằng cách xác định phạm vi cục bộ $PWD$OLDPWDcác biến và sau đó rõ ràng là cding $OLDPWD$PWDnó 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 -$OPTSngay 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 -$OPTStrê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 +$- -$OPTScó 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 returrnra 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 evalsâ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: $PATHtrống vào thời điểm này) thì evaldò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à evalsâ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 cvà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 evalslà 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 $LOCALSbiế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 $LOCALStrừ 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=ftrê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 aliasmở 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
}

Rất thông minh! Một mô hình tương tự được sử dụng để đạt được nhiều điều tương tự ở đây: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.