Bash で readonly した変数を再度編集可能にする

# readonly とは

* 変数を編集禁止にする
* 他言語で言うところの `const` っぽい使い方ができる
  * グローバル変数を作りがちなシェルスクリプトにおいて有益なコマンド。[コーディング規約](https://qiita.com/mashumashu/items/f5b5ff62fef8af0859c5#%E5%AE%9A%E6%95%B0%E3%81%AFreadonly%E5%AE%A3%E8%A8%80%E3%81%99%E3%82%8B) にもよく出てくる

```bash:使用例
$ MY_MSG='OK Google'

$ readonly MY_MSG

$ MY_MSG='Hey Alexa'
-bash: MY_MSG: readonly variable
```

書き換えできないことがわかる。

# OLDPWD を readonly したら?

`OLDPWD`: `cd` する直前のディレクトリが保存される変数

```bash
$ pwd
/var

$ cd /opt

$ echo $OLDPWD
/var
```

こいつに readonly をつけると…

```bash:書き換えできない
$ readonly OLDPWD

$ cd ~
-bash: OLDPWD: readonly variable

$ cd /tmp
-bash: OLDPWD: readonly variable
```

`cd` 時に `OLDPWD` を更新しようとするが、readonly でガードされているために `cd` するたびにエラーメッセージが表示されるようになる。これは困った。

# readonly を外すことはできる?

一度セットされた readonly は、基本的にシェルにログインし直すまで外すことができない。
しかし、[Stackoverflow](https://stackoverflow.com/questions/17397069/unset-readonly-variable-in-bash) にて強引に外す方法が紹介されていたので、上記 OLDPWD の readonly を外せるかどうか試してみた。

```bash:強引に外す
# OLDPWD に readonly がついていることを確認
$ cd ~
-bash: OLDPWD: readonly variable

# gdb で Bash の組み込み関数を直接呼ぶ
$ sudo gdb << EOF
attach $$
call unbind_variable("OLDPWD")
detach
EOF
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
(gdb) Attaching to process 5224
Reading symbols from /bin/bash...(no debugging symbols found)...done.
Reading symbols from /lib64/libtinfo.so.5...(no debugging symbols found)...done.
Loaded symbols for /lib64/libtinfo.so.5
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/libdl.so.2
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
Reading symbols from /lib64/libnss_files.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/libnss_files.so.2
0x00007f99ba2d165e in waitpid () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install bash-4.1.2-33.el6_7.1.x86_64
(gdb) $1 = 0
(gdb) Detaching from program: /bin/bash, process 5224
(gdb) quit
```

gdb で行っていることは以下の3つ。

* `attach $$`: 今動いている Bash を gdb の操作対象にする。 `$$` はシェルの PID
* `call unbind_variable("OLDPWD")`: [Bash のソース](https://github.com/gitGNU/gnu_bash/blob/master/variables.c#L3341) によると「シェル変数 (OLDPWD) をアンセットする」
* `detach`: gdb から開放する

以上を行うことで、 `cd` しても怒られなくなった。

```Bash
# OLDPWD を確認
$ echo $OLDPWD

# cd しても怒られない
$ cd /tmp

$
```

readonly を外すというよりは、変数そのものを消し去ったようだ。

# 備考

上記 Stackoverflow にも書かれているが、変数のアンセットに関しては `unset` というコマンドが用意されている。
一般的な変数に関しては `unset 変数名` で事足りるが、readonly された変数に対してはやはり使えない。

```Bash
# readonly なし
$ NO_READONLY='foo'
# 消せる
$ unset NO_READONLY
$ echo $NO_READONLY

$

# readonly あり
$ READONLY='bar'
$ readonly READONLY
# 消せない
$ unset READONLY
-bash: unset: READONLY: cannot unset: readonly variable
```

ということで、readonly 変数に関しては gdb を使った方法しかなさそう。

# まとめ

* 変数に readonly をつけると書き換えられなくなる
* readonly を一度つけると原則取り消すことができない
* gdb で Bash の `unbind_variable` を呼び、強制的に変数を消去することで readonly を取り消すことができる