先日一緒に作業をしていた若者が~(tilde)を知らなかったので、~(tilde) ってのはHOMEディレクトリを指すんだよー。と教えてあげていたのですが、ふと実装が気になったのでソースをのぞいてみました。
まずはググってみる
なぜUnix&Linuxではホームディレクトリを「~」文字で表現するのか。 - [モ]Modern Syntax
なるほど昔のキーボード由来なんですね。
本題
ソースを見てみます
GNU Project Archives から該当バージョンのソースをダウンロード。
$ bash --version GNU bash, version 3.2.53(1)-release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.
環境変数Home を返すところ
それっぽい名前のファイルがあるのでtilde.hを見ながらそれっぽいのを探してみると tilde_expand や tilde_expand_word という function がありました。
// bash-3.2/lib/tilde/tilde.c char * tilde_expand_word (filename) const char *filename; { char *dirname, *expansion, *username; int user_len; struct passwd *user_entry; if (filename == 0) return ((char *)NULL); if (*filename != '~') return (savestring (filename)); /* A leading `~/' or a bare `~' is *always* translated to the value of $HOME or the home directory of the current user, regardless of any preexpansion hook. */ if (filename[1] == '\0' || filename[1] == '/') { /* Prefix $HOME to the rest of the string. */ expansion = sh_get_env_value ("HOME"); /* If there is no HOME variable, look up the directory in the password database. */ if (expansion == 0) expansion = sh_get_home_dir (); return (glue_prefix_and_suffix (expansion, filename, 1)); }
まさにここですね。~(tilde) であれば環境変数の HOME を返しています。
~+と~-
tilde.cの続き。
// bash-3.2/lib/tilde/tilde.c username = isolate_tilde_prefix (filename, &user_len); if (tilde_expansion_preexpansion_hook) { expansion = (*tilde_expansion_preexpansion_hook) (username); if (expansion) { dirname = glue_prefix_and_suffix (expansion, filename, user_len); free (username); free (expansion); return (dirname); } }
tilde_expansion_preexpansion_hook というのを見てみます。
// bash-3.2/general.c void tilde_initialize () { static int times_called = 0; /* Tell the tilde expander that we want a crack first. */ tilde_expansion_preexpansion_hook = bash_special_tilde_expansions;
// bash-3.2/general.c static char * bash_special_tilde_expansions (text) char *text; { char *result; result = (char *)NULL; if (text[0] == '+' && text[1] == '\0') result = get_string_value ("PWD"); else if (text[0] == '-' && text[1] == '\0') result = get_string_value ("OLDPWD"); #if defined (PUSHD_AND_POPD) else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) result = get_dirstack_from_string (text); #endif return (result ? savestring (result) : (char *)NULL); }
なんだこれは。
~+ だと カレントディレクトリ、 ~- だと OLDPWD を返しています。
というかOLDPWD知らんかった。
さらに +~2 とかもできるようです。地味に便利。
user名に応じたホームディレクトリを返す
さらにtilde.cの続き。
/* No preexpansion hook, or the preexpansion hook failed. Look in the password database. */ dirname = (char *)NULL; #if defined (HAVE_GETPWNAM) user_entry = getpwnam (username); #else user_entry = 0; #endif if (user_entry == 0) { /* If the calling program has a special syntax for expanding tildes, and we couldn't find a standard expansion, then let them try. */ if (tilde_expansion_failure_hook) { expansion = (*tilde_expansion_failure_hook) (username); if (expansion) { dirname = glue_prefix_and_suffix (expansion, filename, user_len); free (expansion); } } /* If we don't have a failure hook, or if the failure hook did not expand the tilde, return a copy of what we were passed. */ if (dirname == 0) dirname = savestring (filename); } #if defined (HAVE_GETPWENT) else dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len); #endif
ここは一見分かりにくのですが、最後のところで getpwnamで得たpw_dir(ユーザ名に応じた home directory) を返しているようです。(当初この実装をどうやっているのか知りたかった。)
(e.g.: ~foo -> /home/foo)
感想
bashのソースを初めて読んだのですが、以外と読めるという印象を持ちました。
知らない機能を見つけたりするのも楽しいです。
ただhardtabとsofttab(しかもspace2つと4つ)が混在しているのはイラッとします。