셸 네임 스페이스 된 모든 기호 앞에 무언가”를

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 


답변

에서 man kshA를 시스템에 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!

답변

나는 로컬의에서 쉘 내장 또는 함수 네임 스페이스에 사용되는 수있는 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명령은 쉘 함수를 호출 할 수 없으므로 일반 내장에서와 같이 특수한 처리 방법을 사용할 수 없습니다. 그것은 또한 spec’d이다. 실제로 스펙의 주요 유틸리티는 command다른 명령에 대해 이름 지정된 랩퍼 쉘 함수 내에서이를 사용하여 함수를 호출하지 않기 때문에 자체 재귀없이 다른 명령을 호출 할 수 있다는 것입니다. 이처럼 :

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

command거기에서 사용하지 않으면 cd함수는 자기 재귀를 위해 거의 확실하게 segfault합니다.

그러나 특수 내장을 호출 할 수있는 일반 내장으로서 서브 쉘 환경command 에서 그렇게 할 수 있습니다 . 확실히 – 현재 쉘 상태가 현재 쉘에 힘 스틱에 정의하면서 그래서 read‘들 $var1$var2한 – 명령 줄을 정의 적어도 결과 아마 안 …

간단한 명령

명령 이름이 없거나 명령 이름이 특수 내장 또는 함수 인 경우 변수 지정은 현재 실행 환경에 영향을줍니다. 그렇지 않으면 명령의 실행 환경에 대해 변수 할당을 내 보내야하며 현재 실행 환경에 영향을 미치지 않습니다.

이제 command일반 내장 기능 특수 내장 기능을 직접 호출 하는 기능 은 내가 모르는 명령 줄 정의와 관련하여 예기치 않은 허점입니다. 그러나 적어도 4 개의 쉘이 이미 있다는 것을 알고 있습니다 언급 한 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명령 행에서 정의하면 랩 대상의 쉘 옵션을 정의 할 수 있습니다. 대상이 반환되면 set +$- -$OPTS자체 복사본 $OPTS (명령 줄 정의의 영향을받지 않음) 을 사용하여 원래 상태로 복원합니다.

물론, returrn랩 대상 또는 인수를 통해 호출자 가 함수 에서 명시 적으로 기능을 종료하는 것을 막을 수는 없습니다 . 그렇게하면 상태 복원 / 정리가 시도되지 않습니다.

모든 것을하기 위해서는 세 개의 eval깊이가 필요합니다 . 먼저 로컬 범위로 랩핑 한 다음 내부 범위에서 인수를 읽고 유효한 쉘 이름을 검증하고 그렇지 않은 경우 오류로 종료합니다. 모든 인수가 유효하고 결국 하나의 원인 인 경우 command -v "$1"true를 반환하는 (리콜 : $PATH이 시점에서 비어) 는 것이다 eval명령 줄 정의하고 랩 대상에 남아있는 모든 인수를 전달할 이의 특별한 경우를 무시하지만 ( ns– 저 같으면 때문에 매우 유용하지 않으며 eval깊이는 3 보다 깊습니다 .

기본적으로 다음과 같이 작동합니다.

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 (???) , 그러나 그것의 모든 보조, 주로 발광에서 저장 만 사용 원치 않는 출력과 가장자리의 경우 유사합니다. 그리고 그것이 작동하는 방식입니다. 랩핑 된 유틸리티를 중첩 된 유틸리티에서 호출하기 전에 자체 로컬 범위를 설정하기 때문에 이러한 작업을 수행 할 수 있습니다.

내가 여기서 매우 조심하려고 노력하기 때문에 길다 – 3 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      

그리고 여기 기능이 있습니다. 확장 \을 피하기 위해 모든 명령 앞에 접두사가 붙습니다 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
}