Bash에서 읽기 전용 변수 설정 해제


78

Bash에서 읽기 전용 변수를 어떻게 설정 해제합니까?

$ readonly PI=3.14

$ unset PI
bash: PI: readonly variable

아니면 불가능합니까?


아 내 나쁜 tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_01.html 변수를 읽기 전용으로 만듭니다. 이러한 변수는 후속 할당 문에서 값을 할당 할 수 없으며 설정을 해제 할 수도 없습니다.
Kokizzu

일반적으로 변수는 / etc / profile 에 이와 같은 많은 행이 포함되어 있기 때문에 읽기 전용 readonly TMOUT입니다. 나는 그 줄에 주석을 달고 그 리눅스 시스템에 대한 새로운 연결을 여는 것을 선호합니다.
ROMANIA_engineer

1
@ROMANIA_engineer 또는 간단히 bash --norc를 실행 한 다음 원하는 항목을 수동으로 설정하거나 자신의 rc 파일에 설정합니다. 예 : source ~ / .gnbashrc
Graham Nicholls

답변:


108

실제로 읽기 전용 변수를 설정 해제 할 수 있습니다 . 그러나 나는 이것이 해키 방법이라는 것을 경고해야한다. 이 답변을 추천이 아닌 정보로만 추가하십시오. 자신의 책임하에 사용하십시오. 우분투 13.04, bash 4.2.45에서 테스트되었습니다.

이 방법은 약간의 bash 소스 코드를 아는 것과 관련 있으며이 답변 에서 상속됩니다 .

$ readonly PI=3.14
$ unset PI
-bash: unset: PI: cannot unset: readonly variable
$ cat << EOF| sudo gdb
attach $$
call unbind_variable("PI")
detach
EOF
$ echo $PI

$

Oneliner 대답은 F. Hauri의 대답에 제공된대로 배치 모드 및 기타 명령 줄 플래그를 사용하는 것입니다 .

$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

sudo커널의 ptrace_scope 설정에 따라 필요할 수도 있고 필요하지 않을 수도 있습니다. 자세한 내용은 vip9937의 답변에 대한 의견을 확인하십시오.


53
이제 이것이 내가 redneck bash 프로그래밍이라고 부르는 것입니다;)
Floyd

6
참고 : 마십시오 변화에 유혹되지 cat << EOF| sudo gdbsudo gdb << EOF. 그것은 할 수 없는 작업의 리디렉션 입력 제공하기 때문에 - bash로 인해 중단되고 gdb첨부.
anishsane

생각 는 sudo를 필요로하지 않기 때문에 응답이 좀 더 정확하고 적절하게 gdb를 종료한다
Rafareino

1
^^ stdin & explicit quit의 EOF는 모두 gdb를 깨끗하게 종료합니다.
anishsane 2015 년

2
나는 하나의 라이너를 좋아합니다 :echo -e "attach $$\n call unbind_variable(\"PI\")\n detach" | gdb
Satya Mishra

48

TMOUT을 설정 해제 (자동 로그 아웃 비활성화)하고 싶기 때문에 위의 gdb 해킹을 시도했지만 TMOUT이 읽기 전용으로 설정된 컴퓨터에서는 sudo를 사용할 수 없습니다. 하지만 bash 프로세스를 소유하고 있기 때문에 sudo가 필요하지 않습니다. 그러나 구문은 내가 사용하는 컴퓨터에서 제대로 작동하지 않았습니다.

그래도 작동했습니다 (내 .bashrc 파일에 넣었습니다).

# Disable the stupid auto-logout
unset TMOUT > /dev/null 2>&1
if [ $? -ne 0 ]; then
    gdb <<EOF > /dev/null 2>&1
 attach $$
 call unbind_variable("TMOUT")
 detach
 quit
EOF
fi

3
안전에 따라 .gdbinit 파일을 로드하지 않는 -q -n옵션을 사용하는 것이 좋습니다 . gdb
Lucas Cimon 2014

3
"내가 bash 프로세스를 소유하고 있기 때문에 sudo가 필요하지 않습니다." 이것은 사용중인 운영 체제와 구성 방법에 따라 다릅니다. 리눅스의 가장 현재 사용 버전의 커널 이됩니다 통해 제어/proc/sys/kernel/yama/ptrace_scope . 가장 일반적인 값은 0이며,이 경우이를 수행 할 수 있으며 1,이 경우 디버깅 되는 프로세스 의 직접적인 부모가 아닌 것처럼 할 수 없습니다gdbbash .
Eliah Kagan

비록 -q-n도움이되는 (즉, 그들은 -q) 침묵하지 않는 gdb소위, /dev/null리디렉션이 여전히 필요하다. 좋은 제안하지만, @LucasCimon
fbicknel

gdb가없는 컴퓨터에서 비슷한 작업을 수행하는 방법에 대한 아이디어가 있습니까?
lightswitch05

1
@ lightswitch05 : 내 ctypes.sh 답변 참조
Wil

6

GDB를 사용하는 것은 매우 느립니다. 대신 ctypes.sh를 시도하십시오. libffi를 사용하여 bash의 unbind_variable ()을 직접 호출하는 방식으로 작동하며, 이는 다른 bash 내장을 사용하는 것만 큼 빠릅니다.

$ readonly PI=3.14
$ unset PI
bash: unset: PI: cannot unset: readonly variable

$ source ctypes.sh
$ dlcall unbind_variable string:PI

$ declare -p PI
bash: declare: PI: not found

먼저 ctypes.sh를 설치해야합니다.

$ git clone https://github.com/taviso/ctypes.sh.git
$ cd ctypes.sh
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

전체 설명 및 문서는 https://github.com/taviso/ctypes.sh 를 참조 하십시오 .

궁금한 사람을 위해 예, 이것은 bash 내의 모든 함수 또는 bash에 연결된 라이브러리의 모든 함수 또는 원하는 경우 동적으로로드 된 외부 라이브러리를 호출 할 수 있습니다. Bash는 이제 perl만큼 위험합니다 ... ;-)


1
당신이 말하는 어디 있으리라 믿고있어 include ctypes.shsource ctypes.sh. ctypes.sh.
Dennis Williamson


5

man 페이지에 따르면 :

   unset [-fv] [name ...]
          ...   Read-only  variables  may  not  be
          unset. ...

아직 변수를 내 보내지 않은 경우을 사용 exec "$0" "$@"하여 셸을 다시 시작할 수 있습니다. 물론 내 보내지 않은 다른 모든 변수도 손실됩니다. 를 사용하지 않고 새 셸을 시작하면 exec해당 셸에 대한 읽기 전용 속성이 손실 되는 것 같습니다 .


쉘을 다시 시작하는 것은 기껏해야 스케치입니다
Eric

5

곧 : anishsane의 답변에서 영감을 얻음

그러나 단순한 구문으로 :

gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch

기능으로 약간의 개선이 있습니다.

destroy기능 :

또는 가변 메타 데이터로 재생하는 방법 . 희귀 한 bashisms의 사용법에 유의하십시오 : local -n VARIABLE=$1그리고 ${VARIABLE@a}...

destroy () { 
    local -n variable=$1
    declare -p $1 &>/dev/null || return -1 # Return if variable not exist
    local reslne result flags=${variable@a}
    [ -z "$flags" ] || [ "${flags//*r*}" ] && { 
        unset $1    # Don't run gdb if variable is not readonly.
        return $?
    }
    while read resline; do
        [ "$resline" ] && [ -z "${resline%\$1 = *}" ] &&
            result=${resline##*1 = }
    done < <(
        gdb 2>&1 -ex 'call unbind_variable("'$1'")' --pid=$$ --batch
    )
    return $result
}

샘플을 위해 이것을라는 bash 소스 파일에 복사 할 수 destroy.bash있습니다.

설명:

 1  destroy () { 
 2      local -n variable=$1
 3      declare -p $1 &>/dev/null || return -1 # Return if variable not exist
 4      local reslne result flags=${variable@a}
 5      [ -z "$flags" ] || [ "${flags//*r*}" ] && { 
 6          unset $1    # Don't run gdb if variable is not readonly.
 7          return $?
 8      }
 9      while read resline; do
10          [ "$resline" ] && [ -z "${resline%\$1 = *}" ] &&
11                result=${resline##*1 = }
12      done < <(
13          gdb 2>&1 -ex 'call unbind_variable("'$1'")' --pid=$$ --batch
14      )
15      return $result
16  }
  • 2 행 은 제출 된 변수에 대한 로컬 참조 를 만듭니다 .
  • 줄 3 존재하지 않는 변수에서 실행 방지
  • 4 행은 매개 변수의 속성 (메타)을 $flags.
  • 8 라인 5가 실행됩니다 unset대신의 gdb경우 읽기 전용 플래그 없는
  • 9-12 행 은 출력 에서 while read ... result= ... done리턴 코드를 얻습니다.call unbindgdb
  • 13 라인 gdb의 사용과 구문 --pid--ex(참조 gdb --help).
  • 15 리턴 라인 $resultcall unbind명령을 사용합니다.

사용:

source destroy.bash 

# 1st with any regular (read-write) variable: 
declare PI=$(bc -l <<<'4*a(1)')
echo $PI
3.14159265358979323844
echo ${PI@a} # flags

declare -p PI
declare -- PI="3.14159265358979323844"
destroy PI
echo $?
0
declare -p PI
bash: declare: PI: not found

# now with read only variable:
declare -r PI=$(bc -l <<<'4*a(1)')
declare -p PI
declare -r PI="3.14159265358979323844"
echo ${PI@a} # flags
r
unset PI
bash: unset: PI: cannot unset: readonly variable

destroy PI
echo $?
0
declare -p PI
bash: declare: PI: not found

# and with non existant variable
destroy PI
echo $?
255

4

특히 TMOUT 변수에 대한 wrt. gdb를 사용할 수없는 경우 또 다른 옵션은 bash를 홈 디렉토리에 복사하고 바이너리의 TMOUT 문자열을 XMOUX와 같은 다른 것으로 패치하는 것입니다. 그런 다음이 추가 셸 레이어를 실행하면 시간이 초과되지 않습니다.


2
gdb 해킹보다 훨씬 더 사악합니다. 그래서 ... +1!
Alois Mahdal

3

readonly 명령은 쉘 프로세스가 종료 될 때까지 최종적이고 영구적입니다. 변수를 변경해야하는 경우 읽기 전용으로 표시하지 마십시오.


2

아니요, 현재 셸에는 없습니다. 새 값을 할당하려면 새로운 의미를 가질 새 쉘을 포크해야하며 read only.

$ { ( readonly pi=3.14; echo $pi ); pi=400; echo $pi; unset pi; echo [$pi]; }
3.14
400
[]

2
$ PI=3.17
$ export PI
$ readonly PI
$ echo $PI
3.17
$ PI=3.14
-bash: PI: readonly variable
$ echo $PI
3.17

이제 무엇을해야합니까?

$ exec $BASH
$ echo $PI
3.17
$ PI=3.14
$ echo $PI
3.14
$

서브 쉘은 부모의 변수를 상속 할 수 있지만 보호 상태는 상속하지 않습니다.


2

gdb를 사용할 수없는 경우 대안 : 다음을 사용할 수 있습니다. enable 명령을 로드 내장 사용자 지정을 그건 당신이 읽기 전용 특성 설정을 해제하게됩니다. 이를 수행하는 코드의 요점 :

SETVARATTR (find_variable ("TMOUT"), att_readonly, 1);

당연히 당신 TMOUT은 당신이 관심있는 변수로 대체 할 것 입니다.

직접 내장으로 바꾸고 싶지 않다면 GitHub에서 bash를 포크하고 완전히 작성되고 컴파일 준비가 완료된로드 가능한 내장 내장 readwrite. 커밋은 https://github.com/josephcsible/bash/commit/bcec716f4ca958e9c55a976050947d2327bcc195에 있습니다. 사용하려면 내 커밋과 함께 Bash 소스를 가져 와서 실행 ./configure && make loadables하여 빌드 한 다음 enable -f examples/loadables/readwrite readwrite실행중인 세션에 추가 한 다음 readwrite TMOUT사용하십시오.


1

의 매뉴얼 페이지에서 할 수 없습니다 unset.

각 이름에 대해 해당 변수 또는 함수를 제거하십시오. 옵션이 제공되지 않거나 -v 옵션이 제공되면 각 이름은 쉘 변수를 참조합니다. 읽기 전용 변수는 설정 해제 할 수 없습니다. -f가 지정되면 각 이름은 쉘 기능을 참조하고 기능 정의가 제거됩니다. 설정되지 않은 각 변수 또는 함수는 후속 명령에 전달되는 환경에서 제거됩니다. RANDOM, SECONDS, LINENO, HISTCMD, FUNCNAME, GROUPS 또는 DIRSTACK 중 하나가 설정되지 않은 경우 이후에 재설정 되더라도 특수 속성이 손실됩니다. 이름이 읽기 전용이 아닌 경우 종료 상태는 true입니다.


2
왜 내가 이해하지 못하는 것은 typeset +r VAR또한 man 페이지에 따르면, 이후 일을하지 않습니다Using '+' instead of '-' turns off the attribute instead, with the exception that +a may not be used to destroy an array variable.
Trebor 무례

1

Bash에서 읽기 전용 변수를 "설정 해제"하는 또 다른 방법은 일회용 컨텍스트에서 해당 변수를 읽기 전용으로 선언하는 것입니다.

foo(){ declare -r PI=3.14; baz; }
bar(){ local PI=3.14; baz; }

baz(){ PI=3.1415927; echo PI=$PI; }

foo;

bash : PI : 읽기 전용 변수

bar; 

PI = 3.1415927

이것은 아마도 원래 저자의 의도 인 범위 내에서 "설정 해제"되지는 않지만 확실히 baz () 관점에서 변수를 읽기 전용으로 설정 한 다음 나중에 시점에서 읽기-쓰기로 만드는 것입니다. baz ()보기에서는 약간의 사전 고려를 통해 스크립트를 작성하기 만하면됩니다.


1

GDB 또는 외부 바이너리가 없는 또 다른 솔루션 (사실 Graham Nicholls 주석 에 대한 강조 )은 exec.

제 경우에는 성가신 읽기 전용 변수가 /etc/profile.d/xxx.

bash 매뉴얼 인용 :

"bash가 대화 형 로그인 쉘로 호출되면 [...] 먼저 / etc / profile 파일에서 명령을 읽고 실행합니다."[...]

로그인 셸이 아닌 대화식 셸이 시작되면 bash는 /etc/bash.bashrc [...]에서 명령을 읽고 실행합니다.

내 해결 방법의 요점은 ~/.bash_profile다음과 같습니다.

if [ -n "$annoying_variable" ]
then exec env annoying_variable='' /bin/bash
# or: then exec env -i /bin/bash
fi

경고 : 재귀 (SSH를 통해서만 계정에 액세스 할 수있는 경우 잠김)를 방지하려면 "성가신 변수"가 bashrc에 의해 자동으로 설정되지 않도록하거나 검사에서 다른 변수를 설정해야합니다. 예를 들어 :

if [ -n "$annoying_variable" ] && [ "${SHLVL:-1}" = 1 ]
then exec env annoying_variable='' SHLVL=$((SHLVL+1)) ${SHELL:-/bin/bash}
fi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.