Zshの謎コマンド「 - 」(ダッシュコマンド, ハイフンコマンド)

# 簡単に言うと...
- Zshには`-`というコマンドがある
- `-`コマンドは、一般的なコマンドの**前に記述することができる**precommand modifierの一種 (イメージ : `% - cd`, `% - ls`)
- `zsh`コマンドの前に`-`を書いて実行すると、起動するZ shellはログインシェルとなる

# `-`コマンドの存在に気が付いたきっかけ

きっかけは`% cd -`について遊んでいたときでした。

https://qiita.com/kmraven/items/7b2c7ea53ae55bf4e5e7

シェルをいじる中で間違えて`-`単体でエンターしてしまったのですが、よく見たらなんと**正常終了**していました。
```
% -
% 
```
！？！？
慌てて`% which -`してみますが...何も出てこない。
いや、オプションとして認識されてるのか！
```
# `--`で引数がオプションでないことを指定
% which -- -  
-: shell built-in command
```
なんと`-`(ハイフン)というコマンドがあるようです！

# 公式ドキュメント
`-`コマンドはどんな機能なのでしょうか？
まずはZshの[公式ページ](https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Precommand-Modifiers)を見てみましょう。
> ### 6.2 Precommand Modifiers
> A simple command may be preceded by a precommand modifier, which will alter how the command is interpreted. These modifiers are shell builtin commands with the exception of nocorrect which is a reserved word.
> 
> `-`
The command is executed with a `-` prepended to its argv[0] string.
> 
> ...

公式曰く、`-`コマンドはPrecommand Modifiers(コマンド前修飾子)の一種で、「`-`コマンドの後ろに渡されたコマンドは、`argv[0]`の文字列の頭に`-`を付加した上で実行される」ようです...

**はて、サッパリ意味がわかりません:thinking_face:**

# ググってみる
検索してみると、世界のどこかで私と同じく`-`コマンドに疑問を抱いた民が質問をしていました。
- [その1](https://superuser.com/questions/1797860/what-is-the-dash-command-in-zsh)
- [その2](https://unix.stackexchange.com/questions/128936/why-we-can-not-see-a-single-hyphen-from-0-on-a-login-shell-invoked-with-logi/128983#128983)
- [その3](https://stackoverflow.com/questions/36314521/what-is-the-use-case-for-this-zsh-precommand-modifier/36388165#36388165)
- [その4](https://unix.stackexchange.com/questions/669488/whats-an-example-usage-of-the-zsh-precommand-modifier)

回答者たち曰く多くのシェルでは、各シェルの起動コマンドの`argv[0]`が`-`で始まる場合は「ログインシェル」モードでシェルを立ち上げるそうです。
>  Some shells check their argv[0], and if there's a leading dash there, they go into "login shell" mode. 

自分のZshにおいても`.zlogin`ファイルを使って検証してみました。確かに`-`コマンドに`zsh`コマンドを渡すと、ログインシェルが立ち上がりました。また、環境変数`ARGV0`に`-zsh`を入れても同様の挙動をしました。
```
% cat > .zlogin
echo "this is a login shell"

% zsh
zsh$ exit

% - zsh
this is a login shell
zsh$ exit

% ARGV0='-zsh' zsh
this is a login shell
zsh$ exit
```
ただ、「`argv[0]`が`-`で始まる場合はログインシェルモードでシェルを立ち上げる」というのは古い慣習の一つであり、現代において具体的な実用例はないようです。
そして`-`コマンドは、このような挙動を引き起こすためにわざわざ用意されたzsh特有のコマンドみたいです[^1]。

[^1]:自分のMacに入っているシェル(bash,csh,dash,ksh,sh,tcsh,zsh)の中だとzshにしか入っていませんでした

# ソースコードを見てみる
Zshのソースコードは[github](https://github.com/zsh-users/zsh)にて公開されています。コードを探索したところ、今回の話題に該当していそうな部分が見つかりました。
- `-`コマンドが`argv[0]`に`-`を入れる & 環境変数`ARGV0`を`argv[0]`にする様子 in [exec.c](https://github.com/zsh-users/zsh/blob/86d5f24a3d28541f242eb3807379301ea976de87/Src/exec.c)
```C
static void
execute(LinkList args, int flags, int defpath)
{
    ...

    /* If ARGV0 is in the commands environment, we use *
     * that as argv[0] for this external command       */
    if (unset(RESTRICTED) && (z = zgetenv("ARGV0"))) {
	setdata(firstnode(args), (void *) ztrdup(z));
	/*
	 * Note we don't do anything with the parameter structure
	 * for ARGV0: that's OK since we're about to exec or exit
	 * on failure.
	 */
#ifdef USE_SET_UNSET_ENV
	unsetenv("ARGV0");
#else
	delenvvalue(z - 6);
#endif
    } else if (flags & BINF_DASH) {
    /* Else if the pre-command `-' was given, we add `-' *
     * to the front of argv[0] for this command.         */
	sprintf(buf2, "-%s", arg0);
	setdata(firstnode(args), (void *) ztrdup(buf2));
    }

    ...

}
```

- `zsh`が立ち上がるときに`argv[0]`をチェックし、ログインシェルにする様子 in [init.c](https://github.com/zsh-users/zsh/blob/86d5f24a3d28541f242eb3807379301ea976de87/Src/init.c)
```C
static void
parseargs(char *zsh_name, char **argv, char **runscript, char **cmdptr,
	  int *needkeymap)
{
    ...
    
    if (**argv == '-')
	flags |= PARSEARGS_LOGIN;

    ...
}

...

static void parseopts_setemulate(char *nam, int flags)
{
    emulate(nam, 1, &emulation, opts);   /* initialises most options */
    opts[LOGINSHELL] = ((flags & PARSEARGS_LOGIN) != 0);

    ...
}
```

以上、めちゃめちゃニッチな豆知識でした