1 2 3 4 5 6 7 $ SSH_CONF="~/.ssh/config" $ grep -Eq "Host" "${SSH_CONF} " && echo ok grep: ~/.ssh/config: No such file or directory $ SSH_CONF="${HOME} /.ssh/config" $ grep -Eq "Host" "${SSH_CONF} " && echo ok ok
这是为什么,这个文件手动检查过,不应该不存在的,而且用 ${HOME} 变量也确实访问到了这个文件
然后我就去找了 chatgpt,果然这类问题它足以解答,感觉以后的记录中会有相当一部分以 chatgpt 的解答作为引子,「这个问题是由于 ~(表示用户的 home 目录)在引号内没有被正确展开为完整的路径所引起的」
~ 是一个 Shell 特殊字符,它在被 Shell 解释时会展开为当前用户的 home 目录,但是当 ~ 被放在双引号或单引号内时,它不会自动展开为路径,这是因为引号影响了 Shell 的词法分析规则,防止其中的内容被进一步解释或展开
看到这,我立刻尝试去掉了变量的引号,果然
1 2 3 $ SSH_CONF=~/.ssh/config $ grep -Eq "Host" "${SSH_CONF} " && echo ok ok
什么是波浪号扩展
波浪号扩展(Tilde Expansion)是由 Shell 解释器在处理命令行输入时进行的一种路径扩展操作
Bash 手册页 (man bash)
与 ~ 展开相关的部分如下:
Tilde Expansion 部分提到:
If a word begins with an unquoted tilde character (~), all of the characters preceding the first unquoted slash (or all characters, if there is no slash) are considered a tilde-prefix.
意思是:一个单词(word)如果以未被转义的波浪号开头,那么这个未被转义的波浪号(~)到第一个未被转义的斜杠(/)之间的这一段字符串,叫做波浪号前缀(tilde-prefix)
⚠️注意,这个波浪号前缀包括波浪号,但是不包括斜杠
如果后续没有未被转义的斜杠(/),那么后续的字符串都是波浪号前缀的一部分
例如:~root/test 中 ~root 是波浪号前缀,不包括后面的 /test
POSIX 标准
POSIX 1003.1 Shell and Utilities 标准也有类似描述,虽然语言上稍有不同,但核心规则一致
POSIX 标准中有提到:
A tilde-prefix consists of an unquoted tilde character at the beginning of a word, followed by all the characters preceding the first unquoted slash in the word, or all the characters in the word if there is no slash.
POSIX 标准确保了在遵循这一标准的 Shell 中(如 Bash、Zsh、Ksh),都会有一致的行为
波浪号在字符串开头扩展的结果
可以归纳为以下三种特殊的目录:
1. 宿主目录(home directory)
结论:当波浪号前缀中不存在转义字符时,bash 会认为 ~ 后的字符串是一个登陆名(login name)
波浪号(~)后的字符串如果为空
bash 默认会将波浪号(~)扩展为环境变量 HOME 的值
如果变量 HOME 没有设置,则默认扩展为当前用户的主目录
HOME 值为空时,bash 会通过 getpwnam 读取 /etc/passwd 这个文件获取到当前用户的根目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ whoami test $ pwd /tmp $ echo $HOME /Users/test $ echo ~ /Users/test $ cd ~ $ pwd /Users/test $ cd /tmp/ $ unset HOME $ echo $HOME $ cd ~ $ pwd /Users/test $ HOME=/dev $ cd ~ $ pwd /dev
波浪号(~)后的字符串如果不为空
bash 会去读取 /etc/passwd 文件获取该用户根目录
如果用户不存在,则不进行任何扩展,原样输出
1 2 3 4 5 6 7 $ echo ~ /Users/test $ echo ~test /Users/test $ echo ~abc ~abc $
2. 当前工作目录(current working directory)和上一次工作目录(previous working directory)
如果波浪号前缀是 ~+
bash 会用环境变量 PWD 的值来扩展
如果 PWD 的值为空,则原样输出 ~+
1 2 3 4 5 6 7 8 9 10 $ pwd /tmp $ echo ~+ /tmp $ cd /etc $ echo ~+ /etc $ unset PWD $ echo ~+ ~+
如果波浪号前缀是 ~-
bash 会用环境变量 OLDPWD 的值来扩展
如果 OLDPWD 的值为空,则原样输出 ~-
1 2 3 4 5 6 7 8 9 10 11 $ cd /etc $ pwd /etc $ cd /tmp $ pwd /tmp $ echo ~- /etc $ unset OLDPWD $ echo ~- ~-
3. 目录栈(directory stack)里的目录
通过配合 bash 的 pushd 和 popd 功能可以轻松实现多目录之间的切换
波浪号扩展提供了快捷的方式让我们引用当前目录栈中的目录名
~+N 扩展为目录栈中第 N 个目录名
~-N 扩展为目录栈中逆序第 N 个目录名
检索目录栈中的目录是从标号 0 开始
如果 N 大于目录栈中的目录数,则 bash 原样输出,不做任何扩展
如果省略 ~+ 和 ~- 中间的 +/-,等价于 ~+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 $ dirs -v 0 ~ $ pushd /home/test/test/1 ~/test/1 ~ $ pushd /home/test/test/2 ~/test/2 ~/test/1 ~ $ pushd /home/test/test/3 ~/test/3 ~/test/2 ~/test/1 ~ $ pushd /home/test/test/4 ~/test/4 ~/test/3 ~/test/2 ~/test/1 ~ $ pushd /home/test/test/5 ~/test/5 ~/test/4 ~/test/3 ~/test/2 ~/test/1 ~ $ pushd /home/test/test/6 ~/test/6 ~/test/5 ~/test/4 ~/test/3 ~/test/2 ~/test/1 ~ $ pushd /home/test/test/7 ~/test/7 ~/test/6 ~/test/5 ~/test/4 ~/test/3 ~/test/2 ~/test/1 ~ $ dirs -v 0 ~/test/7 1 ~/test/6 2 ~/test/5 3 ~/test/4 4 ~/test/3 5 ~/test/2 6 ~/test/1 7 ~ $ echo ~+0 /home/test/test/7 $ echo ~+1 /home/test/test/6 $ echo ~+2 /home/test/test/5 $ echo ~+3 /home/test/test/4 $ echo ~+4 /home/test/test/3 $ echo ~+5 /home/test/test/2 $ echo ~+6 /home/test/test/1 $ echo ~+7 /home/test $ echo ~+8 ~+8 $ echo ~-0 /home/test $ echo ~-1 /home/test/test/1 $ echo ~-2 /home/test/test/2 $ echo ~-3 /home/test/test/3 $ echo ~-4 /home/test/test/4 $ echo ~-5 /home/test/test/5 $ echo ~-6 /home/test/test/6 $ echo ~-7 /home/test/test/7 $ echo ~-8 ~-8 $ echo ~0 /home/test/test/7 $ echo ~1 /home/test/test/6 $ echo ~2 /home/test/test/5 $ echo ~3 /home/test/test/4 $ echo ~4 /home/test/test/3 $ echo ~5 /home/test/test/2 $ echo ~6 /home/test/test/1 $ echo ~7 /home/test $ echo ~8 ~8
波浪号在变量赋值时扩展的结果
在变量赋值时,bash 也支持波浪号扩展
归纳为以下两个情形:
1. 波浪号(~)是第一个 = 之后的第一个字符
所以,回到开头的问题,去掉 " 的 ~ 作为在 = 后第一个字符,可以正常进行扩浪号扩展
1 2 3 $ SSH_CONF=~/.ssh/config $ grep -Eq "Host" "${SSH_CONF} " && echo ok ok
2. 波浪号(~)是每一个 :之后的第一个字符
这个情形则常见于 PATH 的赋值
1 2 3 4 5 $ echo $PATH /home/test/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin $ PATH=$PATH :~test $ echo $PATH /home/test/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/test
be slow to promise and quick to perform.