sourceコマンドが.**rcをリフレッシュするコマンドじゃないと知る

こんにちは、5日ぶり2度目の[LIGアドベントカレンダー](https://qiita.com/advent-calendar/2019/lig)出場、まさくにです。おいおいお前ら12月が終わりゆくことが悲しいのは分かるけど、もっとアドベントカレンダー書いていけよ。（懇願）

さて、みなさん、 `source` コマンド使っていますよね。

使い方はこんな感じ。

```shell
$ source ~/.zshrc
```

こいつ、ずっとドットファイルをリフレッシュするためのものかと思って脳死だったんですけれど、どうやら違うようだと思って調べた結果を書きます。

## とりあえずマニュアル見る

```shell
$ man source
BUILTIN(1)                BSD General Commands Manual               BUILTIN(1)

NAME
     builtin, !, %, ., :, @, {, }, alias, alloc, bg, bind, bindkey, break, breaksw, builtins, case, cd, chdir, command, complete, continue, default, dirs, do, done, echo, echotc, elif,
     else, end, endif, endsw, esac, eval, exec, exit, export, false, fc, fg, filetest, fi, for, foreach, getopts, glob, goto, hash, hashstat, history, hup, if, jobid, jobs, kill, limit,
     local, log, login, logout, ls-F, nice, nohup, notify, onintr, popd, printenv, pushd, pwd, read, readonly, rehash, repeat, return, sched, set, setenv, settc, setty, setvar, shift,
     source, stop, suspend, switch, telltc, test, then, time, times, trap, true, type, ulimit, umask, unalias, uncomplete, unhash, unlimit, unset, unsetenv, until, wait, where, which,
     while -- shell built-in commands

SYNOPSIS
     builtin [-options] [args ...]

DESCRIPTION
     Shell builtin commands are commands that can be executed within the running shell's process.  Note that, in the case of csh(1) builtin commands, the command is executed in a subshell
     if it occurs as any component of a pipeline except the last.

     If a command specified to the shell contains a slash ``/'', the shell will not execute a builtin command, even if the last component of the specified command matches the name of a
     builtin command.  Thus, while specifying ``echo'' causes a builtin command to be executed under shells that support the echo builtin command, specifying ``/bin/echo'' or ``./echo''
     does not.

     While some builtin commands may exist in more than one shell, their operation may be different under each shell which supports them.  Below is a table which lists shell builtin com-
     mands, the standard shells that support them and whether they exist as standalone utilities.

     Only builtin commands for the csh(1) and sh(1) shells are listed here.  Consult a shell's manual page for details on the operation of its builtin commands.  Beware that the sh(1)
     manual page, at least, calls some of these commands ``built-in commands'' and some of them ``reserved words''.  Users of other shells may need to consult an info(1) page or other
     sources of documentation.
```

意訳

1. sourceコマンドは実行中のシェルプロセスと同一プロセスでコマンドを実行する組み込みコマンド。
2. sourceのオプションに `/` が含まれると、 `$PATH` が通っているコマンド（ファイル）より、指定された方を優先する。（逆にパラメタに `/` が含まれなければ、 `$PATH` から一致するコマンド（ファイル）を探し出す）

というようなことが書かれていますね。どこにもリフレッシュするとは書かれていないし、2は知らなかった。コマンドを実行するコマンドなんですね。名前の意図が汲み取れない。なるほど、.zshrcを見る時、下記のようなことを書いていました。

```shell
$ source ~/.zshrc
```

つまりこれでできるのか。

```shell
$ PATH=~:$PATH && source .zshrc
```

まぁパス通してる分、長くなってるんですけど……。PATHが通ってればいいんですね。へー。

## 本当に同一プロセスで実行されているか

```shell
$ cat readable_script1.sh ## こんな感じのスクリプト書いとく
#!/bin/zsh

ME=$0
echo "## Start: "$ME

ps | grep -v iTerm2

echo "## END: "$ME
```

上記のようなスクリプト作って

```shell
$ source readable_script1.sh  # sourceで実行
source readable_script1.sh
## Start: readable_script1.sh
  PID TTY           TIME CMD
61784 ttys001    0:03.16 -zsh # 今のzshだけが動いている
## END: readable_script1.sh
```

ちなみにこれをzshで動かすと以下のように

```shell
$ /bin/zsh ./readable_script1.sh # zshで実行しちゃうよ
## Start: ./readable_script1.sh
  PID TTY           TIME CMD
61784 ttys001    0:03.24 -zsh
71968 ttys001    0:00.01 /bin/zsh ./readable_script1.sh # 別のプロセスが立ち上がってる！
## END: ./readable_script1.sh
```

ので、 `source` コマンドは同一プロセス内でコマンドを実行する、というのは分かりました。

## .zshrcとかがリフレッシュされたように見えるのはなんで？

同一プロセス内で上書きされてるからなんですよね。きっと。

```shell
$ cat readable_script1.sh # こう書いて
#!/bin/zsh

ME=$0
echo "## Start: "$ME

LOVER=Emacs

echo "## END: "$ME
```

下記のように実行。

```shell
LOVER=Vim && source readable_script1.sh && echo $LOVER
## Start: readable_script1.sh
## END: readable_script1.sh
Emacs # VimがEmacsに変わっちゃったよ！
```

これがzshで実行されると

```shell
LOVER=Vim && /bin/zsh readable_script1.sh && echo $LOVER
## Start: readable_script1.sh
## END: readable_script1.sh
Vim # Vimのままだ！
```

っていうことなんで、各設定が上書きされるんですね。へー。そういうことだったのか。

## sourceの中でsource呼ばれるとどうなるの？

気になりますね。
下記のようなスクリプト書いて

```shell:readable_script1.sh
#!/bin/zsh

ME=$0
echo "## Start: "$ME

source readable_script2.sh

echo "## END: "$ME
```

```shell:readable_script2.sh
#!/bin/zsh

ME=$0
echo "## Start: "$ME

LOVER=Emacs

echo "## END: "$ME
```

スクリプトの1つ目から2つ目をsourceで実行するようにしています。
で、これをsourceで実行すると

```
LOVER=Vim && source readable_script1.sh && echo $LOVER
## Start: readable_script1.sh
## Start: readable_script2.sh
## END: readable_script2.sh
## END: readable_script2.sh
Emacs # 上の上まで伝播する〜！というか同一空間内〜！
```

ということなんですね。へー。

## おまけ1

`.` はsourceコマンドとして動く。

```shell
LOVER=Vim && . ./readable_script1.sh && echo $LOVER
## Start: ./readable_script1.sh
## Start: readable_script2.sh
## END: readable_script2.sh
## END: readable_script2.sh
Emacs
```

## おまけ2

.zshrc内でどうしてもそのマシン固有の変数とか、環境変数を入れときたいことありますよね。でも.zshrcで管理したくないですよね。僕はこうしています。

```shell:.zshrc
source $HOME/.env
```

これで `$HOME/.env` に固有の設定を書いて、 .zshrc から切り離すことにしています。何かもっといい方法あんのかな。