Ruang nama shell

10

Apakah ada cara untuk sourceskrip shell ke namespace, lebih disukai skrip bash shell tapi saya akan melihat ke shell lain jika mereka memiliki fitur ini dan bash tidak.

Yang saya maksud dengan itu adalah, misalnya, sesuatu seperti "awalan semua simbol yang didefinisikan dengan sesuatu sehingga mereka tidak bertabrakan dengan simbol yang sudah didefinisikan (nama variabel, nama fungsi, alias)" atau fasilitas lain yang mencegah tabrakan nama.

Jika ada solusi di mana saya bisa namespace pada sourcewaktu ( NodeJSgaya), itu akan menjadi yang terbaik.

Kode contoh:

$ 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 
PSkocik
sumber
1
Terimakasih atas klarifikasinya. Saya berharap jawabannya negatif. Paradigma pemrograman shell yang biasa adalah bahwa ketika Anda ingin mengisolasi perubahan, Anda melakukannya dalam subkulit, yang membuatnya hanya tentang ( easiest thing ever ). Tapi bukan itu yang Anda cari. Saya kira Anda bisa melakukan ( stuff in subshell; exec env ) | sed 's/^/namespace_/'dan evalhasilnya di shell induk tapi itu agak jahat.
Celada
3
Iya. Dapatkan ksh93. Namespaces sangat penting untuk itu - dan semua jenis namanya (yang juga dapat diketik) mendukung penempatan nama. Ngomong bash-ngomong, ini juga jauh lebih cepat dalam segala hal daripada , omong-omong.
mikeserv
@ mikeserv Terima kasih, jika Anda menambahkannya sebagai jawaban dengan contoh kode yang menunjukkan fungsionalitas, saya akan menerimanya.
PSkocik
@ Michael Saya perlu simbol fungsi dan alias namespace. env | sed ...akan berfungsi untuk variabel, saya bisa lakukan setuntuk mendapatkan fungsi, tetapi pencarian dan penggantian akan menjadi masalah - fungsi dapat memanggil satu sama lain dan Anda sehingga Anda harus mengganti semua doa silang dengan doa silang yang diawali tetapi tanpa mengganti kata yang sama di tempat lain dalam kode definisi fungsi, di mana itu bukan doa. Untuk itu Anda memerlukan bash parser, bukan hanya regex, dan itu masih akan berfungsi selama fungsinya tidak saling memanggil melalui eval.
PSkocik

Jawaban:

11

Dari man kshpada sistem dengan ksh93...

  • Beri Nama Spasi
    • Perintah dan fungsi yang dieksekusi sebagai bagian dari daftar namespaceperintah yang memodifikasi variabel atau membuat yang baru, membuat variabel baru yang namanya adalah nama ruang nama seperti yang diberikan oleh pengenal sebelumnya .. Ketika variabel yang namanya direferensikan, variabel itu pertama kali dicari untuk digunakan .identifier.name.
    • Demikian pula, fungsi yang didefinisikan oleh perintah dalam daftar namespace dibuat menggunakan nama ruang nama yang didahului oleh a ..
    • Ketika daftar perintah namespace berisi namespaceperintah, nama-nama variabel dan fungsi yang dibuat terdiri dari variabel atau nama fungsi yang didahului oleh daftar pengidentifikasi masing-masing didahului oleh .. Di luar ruang nama, variabel atau fungsi yang dibuat di dalam ruang nama dapat dirujuk dengan mendahuluinya dengan nama ruang nama.
    • Secara default, variabel yang menggunakan bintang .shada di shruang nama.

Dan, untuk menunjukkan, inilah konsep yang diterapkan pada namespace yang disediakan secara default untuk setiap variabel shell biasa yang ditetapkan dalam ksh93shell. Dalam contoh berikut ini saya akan mendefinisikan disciplinefungsi yang akan bertindak sebagai .getmetode yang ditugaskan untuk $PS1variabel shell. Setiap variabel shell pada dasarnya mendapat namespace sendiri dengan, setidaknya, default get, set, append, dan unsetmetode. Setelah mendefinisikan fungsi berikut, setiap kali variabel $PS1direferensikan dalam shell, output dateakan ditarik di bagian atas layar ...

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

(Perhatikan juga kurangnya ()subkulit dalam substitusi perintah di atas)

Secara teknis, ruang nama dan disiplin tidak persis sama (karena disiplin dapat didefinisikan untuk diterapkan baik secara global atau lokal ke ruang nama tertentu ) , tetapi keduanya merupakan bagian dan paket untuk konseptualisasi tipe data shell yang mendasar untuk ksh93.

Untuk membahas contoh khusus Anda:

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!

...atau...

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

Ahoj, světe!
Hello, World!
mikeserv
sumber
@PSkocik - mengapa Anda tidak memperbaiki masalah ehoj saya ? Aku bisa bersumpah itu yang dikatakan sebelumnya ... maaf tentang itu. Saya tidak akan menerima jawaban yang ditulis oleh seseorang yang bahkan tidak repot-repot mengeja kata-kata yang saya gunakan dalam pertanyaan dengan benar ... Namun, jujur, saya pikir saya ingat hanya menyalin / menempelkan jt ... hmm ...
mikeserv
2

Saya telah menulis fungsi shell POSIX yang dapat digunakan untuk lokal namespace sebuah builtin shell atau fungsi dalam salah ksh93, dash, mksh, atau bash (bernama khusus karena saya secara pribadi telah dikonfirmasi untuk bekerja di semua ini) . Kerang tempat saya mengujinya, hanya gagal memenuhi harapan saya yash, dan saya tidak pernah berharap itu bekerja sama sekali zsh. Saya tidak menguji posh. Saya memberikan harapan untuk poshbeberapa waktu yang lalu dan belum menginstalnya dalam beberapa waktu. Mungkin berhasil di posh...?

Saya mengatakan itu POSIX karena, dengan membaca spesifikasi saya, ia mengambil keuntungan dari perilaku tertentu dari utilitas dasar, tetapi, diakui, spesifikasinya tidak jelas dalam hal ini, dan, setidaknya satu orang tampaknya tidak setuju dengan saya. Secara umum saya memiliki ketidaksepakatan dengan yang satu ini. Saya akhirnya menemukan kesalahan itu menjadi kesalahan saya sendiri, dan mungkin saya juga salah saat ini mengenai spesifikasinya, tetapi ketika saya menanyainya lebih lanjut, dia tidak menjawab.

Namun, seperti yang saya katakan, ini benar-benar bekerja pada cangkang yang disebutkan di atas, dan pada dasarnya bekerja dengan cara berikut:

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

3
empty

The commandperintah ditetapkan sebagai utilitas pada dasarnya tersedia dan salah satu pra $PATH'd builtin. Salah satu fungsi yang ditentukan adalah untuk membungkus utilitas builtin khusus di lingkungannya sendiri ketika memanggil mereka, dan ...

{       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

... perilaku kedua penugasan baris perintah di atas sudah benar menurut spesifikasi. Perilaku kedua kondisi kesalahan ini juga benar, dan sebenarnya hampir sepenuhnya diduplikasi dari spesifikasi. Penugasan yang diawali dengan baris perintah fungsi atau bawaan khusus ditentukan untuk mempengaruhi lingkungan shell saat ini. Demikian juga, kesalahan pengalihan ditentukan sebagai fatal ketika menunjuk salah satu dari mereka. commanddispesifikasikan untuk menekan perlakuan khusus dari builtin khusus dalam kasus tersebut, dan kasus redirection sebenarnya ditunjukkan dengan contoh dalam spesifikasi.

Dibangun secara teratur, seperti command, di sisi lain, ditetapkan untuk berjalan di lingkungan subkulit - yang tidak berarti bahwa proses lain , hanya saja itu harus secara fundamental tidak dapat dibedakan dari satu. Hasil memanggil builtin biasa harus selalu menyerupai apa yang mungkin diperoleh dari perintah yang sama mampu $PATH. Dan sebagainya...

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

Tetapi commandperintah tidak dapat memanggil fungsi shell, dan karena itu tidak dapat digunakan untuk membuat file perlakuan khusus mereka seperti untuk builtin biasa. Itu juga spec'd. Bahkan, spek mengatakan bahwa utilitas utama commandadalah bahwa Anda dapat menggunakannya dalam fungsi shell wrapper bernama untuk perintah lain untuk memanggil perintah lain tanpa rekursi diri karena tidak akan memanggil fungsi. Seperti ini:

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

Jika Anda tidak menggunakan di commandsana cdfungsi hampir pasti akan segfault untuk rekursi diri.

Tetapi sebagai builtin biasa yang dapat memanggil builtin khusus commanddapat melakukannya dalam lingkungan subkulit . Dan, sementara saat ini shell-negara didefinisikan dalam kekuatan tongkat ke shell saat ini - tentu read's $var1dan $var2melakukan - setidaknya hasil mendefinisikan baris perintah mungkin tidak harus ...

Perintah Sederhana

Jika tidak ada nama perintah yang dihasilkan, atau jika nama perintah adalah fungsi atau built-in khusus, penugasan variabel akan memengaruhi lingkungan eksekusi saat ini. Jika tidak, tugas variabel harus diekspor untuk lingkungan eksekusi dari perintah dan tidak akan mempengaruhi lingkungan eksekusi saat ini.

Sekarang apakah atau tidak commandkemampuan untuk menjadi builtin biasa dan langsung memanggil builtin khusus hanyalah semacam celah tak terduga berkenaan dengan baris perintah mendefinisikan saya tidak tahu, tapi saya tahu bahwa setidaknya empat shell sudah disebutkan untuk menghormati commandnamespace.

Dan meskipun commandtidak dapat secara langsung memanggil fungsi shell, ia dapat memanggil evalseperti yang ditunjukkan, dan dapat melakukannya secara tidak langsung. Jadi saya membangun pembungkus namespace pada konsep ini. Dibutuhkan daftar argumen seperti:

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

... kecuali bahwa commandkata di atas hanya dikenali sebagai satu jika dapat ditemukan dengan kosong $PATH. Selain lokal scoping variabel shell bernama pada baris perintah, juga secara lokal-scopes semua variabel dengan single-huruf kecil nama abjad dan daftar yang standar lainnya, seperti $PS3, $PS4, $OPTARG, $OPTIND, $IFS, $PATH, $PWD, $OLDPWDdan beberapa orang lain.

Dan ya, dengan melakukan scoping $PWDdan $OLDPWDvariabel secara lokal dan kemudian secara eksplisit cdke $OLDPWDdan $PWDitu bisa cukup andal lingkup direktori kerja saat ini juga. Ini tidak dijamin, meskipun itu berusaha sangat keras. Itu mempertahankan deskriptor untuk 7<.dan ketika target bungkusnya mengembalikannya cd -P /dev/fd/7/. Jika direktori kerja saat ini telah unlink()di sementara, itu masih harus setidaknya berhasil mengubah kembali ke dalamnya tetapi akan memunculkan kesalahan buruk dalam kasus itu. Dan karena ia mempertahankan deskriptor, saya tidak berpikir kernel waras harus membiarkan perangkat root-nya di-unmount juga (???) .

Itu juga secara lokal cakupan opsi shell, dan mengembalikan ini ke keadaan di mana ia menemukan mereka ketika utilitas dibungkus kembali. Ini memperlakukan $OPTSsecara khusus karena ia memelihara salinan dalam ruang lingkupnya sendiri yang pada awalnya memberikan nilai $-. Setelah juga menangani semua tugas pada command-line, ia akan melakukan set -$OPTStepat sebelum memanggil target wrap-nya. Dengan cara ini jika Anda mendefinisikan -$OPTSpada command-line Anda dapat menentukan opsi shell target wrap Anda. Ketika target kembali, ia akan set +$- -$OPTSdengan salinannya sendiri $OPTS (yang tidak terpengaruh oleh baris perintah yang ditentukan) dan mengembalikan semua ke keadaan semula.

Tentu saja, tidak ada yang menghentikan penelepon dari entah bagaimana secara eksplisit returrnkeluar dari fungsi dengan cara membungkus target atau argumennya. Melakukan hal itu akan mencegah pemulihan / pembersihan negara yang akan dicoba.

Untuk melakukan semua itu perlu evalmendalam tiga . Pertama ia membungkus dirinya sendiri dalam lingkup lokal, kemudian, dari dalam, ia membaca dalam argumen, memvalidasi mereka untuk nama shell yang valid, dan berhenti dengan kesalahan jika menemukan yang tidak. Jika semua argumen valid dan akhirnya salah satu penyebab command -v "$1"untuk mengembalikan true (recall: $PATHis kosong pada titik ini) itu akan evalbaris perintah mendefinisikan dan meneruskan semua argumen yang tersisa ke target wrap (meskipun mengabaikan kasus khusus untuk ns- karena itu tidak akan akan sangat berguna, dan evalkedalaman tiga lebih dari cukup dalam) .

Ini pada dasarnya bekerja seperti ini:

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

Ada beberapa pengalihan lain dan, dan beberapa tes yang aneh dilakukan dengan cara beberapa kerang dimasukkan cdalam $-dan kemudian menolak untuk menerimanya sebagai pilihan untuk set (???) , tapi semua pendukung, dan terutama digunakan hanya untuk menyelamatkan dari memancarkan output yang tidak diinginkan dan serupa dalam kasus tepi. Dan begitulah cara kerjanya. Ia dapat melakukan hal-hal itu karena ia mengatur ruang lingkup lokalnya sendiri sebelum memanggil utilitas terbungkusnya dalam bentuk bersarang.

Ini lama, karena saya mencoba untuk sangat berhati-hati di sini - tiga evalssulit. Tetapi dengan itu Anda bisa melakukannya:

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

still_local global
 global

Melangkah lebih jauh dan terus-menerus menamakan ruang lingkup lokal dari utilitas yang dibungkus seharusnya tidak terlalu sulit. Dan bahkan seperti yang tertulis itu sudah mendefinisikan $LOCALSvariabel ke utilitas terbungkus yang hanya terdiri dari daftar semua nama yang dipisahkan ruang yang didefinisikan dalam lingkungan utilitas terbungkus.

Suka:

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

... yang benar-benar aman - $IFStelah disanitasi ke nilai standarnya dan hanya nama shell yang valid yang membuatnya $LOCALSkecuali Anda menyetelnya sendiri di baris perintah. Dan bahkan jika mungkin ada karakter gumpalan dalam variabel split, Anda bisa mengatur OPTS=fpada command-line juga untuk utilitas yang dibungkus untuk melarang ekspansi mereka. Bagaimanapun:

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      

Dan inilah fungsinya. Semua perintah diawali dengan / \untuk menghindari aliasekspansi:

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
}
mikeserv
sumber
Sangat pintar! Pola serupa digunakan untuk mencapai banyak hal yang sama di sini: github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B