昨天笔者有个应用要用到 nodejs,在安装 fnm[1] 这个 node 版本管理器时查看了一下它的安装脚本。里面有一段代码引起了笔者的注意。
check_dependencies() {echo "Checking dependencies for the installation script..."echo -n "Checking availability of curl... "if hash curl 2>/dev/null; thenecho "OK!"elseecho "Missing!" SHOULD_EXIT="true"fi# 其它略...}
这里检查命令是否存在是通过 Bash 内建命令 hash 来完成的,笔者用的是 command -v,所以对这个 hash 命令比较好奇。
bash 维护一个内部哈希表,用于缓存已找到的命令的完整路径。当输入一个命令时:
当执行命令时,bash 会自动更新哈希表:
# 第一次执行 ls 会搜索 PATH$ ls# bash 自动执行:hash ls# 后续执行 ls 使用缓存路径$ ls
hash 命令就是用来手动管理这个哈希表的命令。它用于缓存和查看哈希表中命令的完整路径。 bash 通过哈希表记住命令的完整路径名,避免在后续调用时重复进行 $PATH 搜索,从而提高命令执行效率。
hash [-r] [-p 文件名] [-dt] [命令名]
例如:
# 搜索 PATH, 并缓存找到的命令 lshash ls# 命令不存在hash cmd_not_existbash: hash: cmd_not_exist: 未找到
选项:
| |
|---|
-r | |
| |
-p 文件名 | 抑制路径搜索,直接将指定的 文件名 作为 命令名 的位置。 |
| 这会强制将 文件名 与 命令名 关联,而不检查它是否真的可执行。 |
-d | 使 shell 忘记每个指定 命令名 的已记住位置。 |
| |
-t | |
| 如果与多个 命令名 一起使用,会在每个完整路径前打印对应的命令名。 |
-l | 以可重新用作输入的格式显示输出。这对于保存和恢复哈希表很有用。 |
-t、-d 和 -p 这三个选项(作用于 命令名 参数的选项)是互斥的,一次只能有一个生效。如果提供了多个,优先级如下:
返回码:
所有它可以用于判断命令是否存在。
1. 选项示例
1:查看当前哈希表
# 显示所有已缓存的命令及其命中次数bash-5.3$ hash命中 命令 2 /bin/cp 3 /usr/bin/man 1 /bin/ls 2 /Users/xxx/miniconda3/envs/notebook/bin/python 1 /usr/bin/vim
2:显示特定命令的完整路径
bash-5.3$ hash -t ls/bin/ls# 显示多个命令bash-5.3$ hash -t ls grep findls /bin/lsgrep /usr/bin/grepfind /usr/bin/find
如果命令从未被 hash 过, 则:
bash-5.3$ hash -t grepbash: hash: grep: 未找到# 缓存 grepbash-5.3$ grepbash-5.3$ hash -t grep/usr/bin/grep
3:强制指定命令路径
# 强制将 /usr/local/bin/myls 与 ls 关联$ hash -p /usr/local/bin/myls ls# 现在执行 ls 会调用 /usr/local/bin/myls$ ls
4:删除特定命令缓存
# 删除 ls 的缓存bash-5.3$ hash -d ls# 验证 ls 已被删除bash-5.3$ hash命中 命令 2 /usr/bin/grep 2 /bin/cp 2 /usr/bin/find 3 /usr/bin/man 2 /Users/xxx/miniconda3/envs/notebook/bin/python 1 /usr/bin/vim
5:清空整个哈希表
# 清空所有缓存bash-5.3$ hash -r# 哈希表已空bash-5.3$ hashhash: 哈希表为空
6:以可重用格式显示
bash-5.3$ hash -lbuiltinhash -p /usr/bin/grep grepbuiltinhash -p /usr/bin/find findbuiltinhash -p /bin/ls ls
这种格式的输出可以直接复制粘贴回终端来重建哈希表。
2. 自动管理机制
1. PATH 变更时自动清空
# 查看当前哈希表bash-5.3$ hash命中 命令 1 /usr/bin/grep 1 /usr/bin/find 1 /bin/ls# 修改 PATHbash-5.3$ PATH=/usr/local/bin:$PATH# 哈希表被自动清空bash-5.3$ hashhash: 哈希表为空
2. 命令移动或删除时
如果缓存的命令被移动或删除,bash 会在下次执行时发现并重新搜索:
3. 应用场景
1:优化脚本性能
#!/bin/bash# 在脚本开始缓存常用命令hash awk sed grep cut 2>/dev/null# 后续多次调用这些命令时不需要搜索 PATHfor file in *.txt; do grep "pattern""$file" | awk '{print $1}'done
2:调试命令路径问题
# 检查命令的实际路径$ hash -t python/usr/bin/python# 如果怀疑有多个版本$ type -a pythonpython is /usr/bin/pythonpython is /usr/local/bin/python# 清空缓存重新测试$ hash -r$ python --version$ hash -t python# 现在显示的是实际使用的路径
3:临时替换命令
# 创建自定义的 ls 版本$ echo'echo "Custom ls"' > /tmp/myls$ chmod +x /tmp/myls# 临时替换 ls$ hash -p /tmp/myls ls$ lsCustom ls# 恢复原来的 ls$ hash -d ls$ ls# 正常列出文件
4. 总结
hash 命令是 bash 的一个性能优化工具,它通过缓存命令路径减少 $PATH 搜索开销。主要用途包括:
注意事项:
- 哈希表是进程本地的:每个 shell 会话有自己的哈希表
- 子进程不继承:在子 shell 或脚本中修改哈希表不影响父 shell
- 命令不可执行也能缓存:
-p 选项可以缓存任何路径,即使文件不可执行 - 别名优先:如果命令有别名,会先执行别名,不会触发哈希查找
虽然日常交互中很少需要手动使用 hash 命令,但了解它的工作原理有助于理解 shell 如何高效地执行命令,并在需要调试命令路径问题时提供有用的工具。
最后是相关命令的比较。
| | |
|---|
type | | type |
which | | which |
command -v | | |
# 比较不同命令$ hash -t ls # 显示缓存中的路径/bin/ls$ which ls # 总是搜索 PATH/bin/ls$ type -a ls # 显示命令类型和所有路径ls is hashed (/bin/ls)$ command -v ls # POSIX 标准方式/bin/ls
参考资料
[1] fnm: https://github.com/Schniz/fnm