Shell 问题与修复
Shell 配置文件作用范围 /etc/profile 系统级别,影响所有用户的登录 shell,主要用于 bash 和 sh,并且任何兼容 Bourne shell 的登录 shell 都会加载它。…
Shell 配置文件作用范围
/etc/profile 系统级别,影响所有用户的登录 shell,主要用于 bash 和 sh,并且任何兼容 Bourne shell 的登录 shell 都会加载它。
/etc/zshrc 系统级别,影响所有用户的 zsh
/etc/bashrc 系统级别,影响所有用户的 bash
~/.zshrc 用户级别,只影响当前用户 zsh,不影响其他用户
~/.bashrc 用户级别,只影响当前用户 bash,不影响其他用户
zsh 不会加载 bash 配置
zsh 只加载自己的配置文件(.zshenv、.zprofile、.zshrc 等)
bash 只加载自己的配置文件(.bashrc、.bash_profile 等)
两者互不干扰
zsh 配置文件执行顺序
1/etc/zshenv # 最早加载
2/etc/zprofile # 登录 shell 使用
3/etc/zshrc # 交互式 shell, 打开一个终端窗口、标签页
4/etc/zlogin # 登录完成后执行
5/etc/zlogout # 退出登录 shell 时执行
6
7~/.zshenv
8~/.zprofile
9~/.zshrc
10~/.zlogin
11~/.zlogout
使用频率结论(从高到低):zshrc > zprofile > zshenv > zlogin > zlogout
**换句话说:**前两个是日常必用,中间一个偶尔需要,后两个大多数人一辈子都用不上
zshrc 和 zprofile 的区别
zshrc 每个交互 shell 都执行
触发场景:
- 打开一个新 Terminal 窗口
- 新开一个 tab
- tmux split
- exec zsh
- 某些 IDE 内嵌终端
一次操作 = 一次完整执行
zprofile 每次登录后只执行一次
触发场景:
- 登录系统后的第一个终端
- SSH 登录
- 明确指定 login shell
后续 Shell 嵌套不会再执行
一个真实对比,你进入终端后:
- 运行 exec zsh
- zshrc 会执行,zprofile 不会再执行
这就是性能差异的根源
以后你判断一行配置放哪,只问一句:
“这行代码,值得在 exec zsh / tmux split 时反复执行吗?”
- 不值得 → zprofile
- 值得 → zshrc
.profile 文件作用
.profile 是“为不指定 shell 的登录环境准备的兜底文件”。
在 zsh 成为默认 shell 之后:
- 日常使用中 几乎不会直接用到
- 但在某些边缘场景里,它仍然是最后一道保险
bash 的行为
- 登录 shell:
- 优先读取 ~/.bash_profile
- 如果不存在 → 读取 ~/.profile
- 所以在 bash 时代,.profile 非常重要。
zsh 的行为(关键)
- zsh 默认不会读取 .profile。
- 它的对应物是:
.profile → .zprofile - 除非系统或工具用 /bin/sh,否则 .profile 不会被执行
export PATH 执行顺序
1export PATH="/Users/zailiang/Library/pnpm:$PATH"
2export PATH="/opt/homebrew/opt/mysql@8.4/bin:$PATH"
如果原来 PATH 是:
1/usr/bin:/bin:/usr/local/bin
执行这一句后变成:
1/opt/homebrew/opt/mysql@8.4/bin:/Users/zailiang/Library/pnpm:/usr/bin:/bin:/usr/local/bin
本质上执行的是 “把新的目录插入到现有 PATH 的最前面”。这不会丢掉之前的内容,只是不断把新的路径往前堆栈。
PATH 是一个“冒号分隔的路径列表”,当你输入一个命令(比如 node),shell 会从最前面的路径依次查找。
环境变量加不加 export 的区别
1FPATH="$HOME/.docker/completions:$FPATH"
没有 export 的作用:
- 在当前 shell 中临时修改 FPATH
- Shell 会使用这个值进行补全
- 不会自动传递给子进程
1export FPATH="$HOME/.docker/completions:$FPATH"
而加上 export 则会:
- 修改当前 shell 中的值
- 将这个变量传递给后续所有子进程
如果你想让补全目录在所有 shell 和脚本中都有效,建议使用 export
如果只是要当前 shell 临时加载,不关心子进程,则可以省略 export。
PATH 路径重复
每次 source 后 FPATH 或者 PATH 会重复
在 ~/.zshrc 的开头添加:
1# 去重 PATH 和 FPATH
2typeset -U path fpath
3# 或单独写
4typeset -U path
5typeset -U fpath
不会重复(覆盖式)
1export PATH="/usr/local/bin"
会重复(追加式)
1export PATH="/usr/local/bin:$PATH"
2# 如果多次执行,PATH 会变成:
3# /usr/local/bin:/usr/local/bin:/usr/local/bin:...
执行脚本开头
脚本第一行以 #!/bin/bash 开头,是一个称为 shebang 的特殊注释,它告诉操作系统该脚本应该使用哪个解释器来执行。
执行脚本方式
- 使用
source命令执行(当前 Shell)
1source test.sh
- 使用解释器命令执行(子 Shell),无需为脚本赋予可执行权限
1sh test.sh
2zsh test.sh
- 使用相对路径或绝对路径执行(子 Shell),需要为脚本赋予可执行权限
1/path/to/test.sh # 使用绝对路径
2./test.sh # 使用相对路径(注意:去掉 ./ 将被视为命令)
- 默认情况下,Shell 脚本会逐行执行每个命令。
- 如果不处理错误,脚本会在某些命令失败后仍然继续运行。
- 推荐在脚本中添加 set -e 或使用条件判断以提高可靠性。
脚本位置参数
$0脚本名 $1第 1 个参数 $2第 2 个参数 $#参数数量 $*获取所有参数(整体)$@获取所有参数(集合)$[3*2]数值运算
环境变量命令
1env # 查看环境变量
2set # 设置局部变量
3unset # 撤销变量
4export # 局部变量提升至全局变量
5readonly # 设置只读变量
命令符号
$() 命令替换符,允许你在命令中使用其他命令的输出结果。
例如:current_dir=
pwd; echo“ 当前目录是: $current_dir“ 输出:当前目录是: /home/zailiang
> 重定向符号,用于将命令的输出重定向到文件。
例如:command > file.txt 将 command 的输出写入到 file.txt 文件中,覆盖文件中的内容。
>> 追加重定向符号,用于将命令的输出追加到文件末尾。
例如:command >> file.txt 将 command 的输出追加到 file.txt 文件末尾。
< 输入重定向符号,用于将文件内容作为命令的输入。
例如:command < file.txt 将 file.txt 文件的内容作为 command 命令的输入。
& 后台运行符号,用于将命令放入后台运行。
例如:command & 将 command 命令放入后台运行,不阻塞当前终端。
&& 逻辑与符号,用于在一条命令中执行多个命令,只有前一个命令执行成功时才执行后一个命令。
例如:command1 && command2 如果 command1 执行成功,则执行 command2。
|| 逻辑或符号,用于在一条命令中执行多个命令,只有前一个命令执行失败时才执行后一个命令。
例如:command1 || command2 如果 command1 执行失败,则执行 command2。
; 命令分隔符,用于在一条命令中依次执行多个命令,不管前一个命令是否执行成功。
例如:command1 ; command2 先执行 command1,然后执行 command2。
$ 在Unix/Linux shell中,用于引用变量或执行命令的输出。
例如,$HOME 引用用户的家目录,$(command) 执行command并返回其输出。
| 管道符。将一个命令的输出作为另一个命令的输入。通常与 grep 一同使用。
~ 在Unix/Linux shell中,表示当前用户的家目录。
' 和 " 单引号和双引号都用于定义字符串,但它们处理变量和转义字符的方式不同。
单引号内的内容将被完全视为普通字符串,而双引号内的内容则允许变量扩展和某些转义字符。
权限表示法
- 文件类型(第 1 个字符):
- 普通文件
d 目录
l 软链接
- (User)用户权限(第 2 至第 4 个字符)
- (Group)组权限(第 5 至第 7 个字符)
- (Others)其他用户权限(第 8 至第 10 个字符)
r 读权限
w 写权限
x 执行权限
- 没有相应的权限
例如,
-rwxr-x---表示一个具有读、写、执行权限的文件,对于所有者(用户)来说,对于组来说有读和执行权限,而对于其他用户则没有任何权限。
权限修改方式
- 符号模式
+-=符号来表示权限的增加、减少和设置。使用 u(用户)、g(组)、o(其他)和 a(所有)表示权限的作用对象。
1chmod u+w filename # 将文件的所有者添加写权限
2chmod go-x directoryname # 将目录的所有者和所属组的执行权限去除
3chmod a=rw filename # 将文件设置为所有用户可读写
- 数字模式
4读2写1执行 使用三个八进制数字来表示权限,每个数字代表一个用户类别(所有者、所属组、其他用户)。权限的值对应不同的权限组合,使用数字模式时,将三个数字组合在一起,分别表示所有者、所属组和其他用户的权限。
1chmod 640 filename # 将文件设置为所有者可读写,所属组可读,其他用户没有权限
2chmod 777 directoryname # 将目录设置为所有用户都具有读、写、执行权限
切换目录层级
cd / 切换到根目录
cd - 切换到先前所在的目录
cd ~ 或 cd 切换到用户目录
cd .. 切换到上级目录
cd ../.. 切换到上两级目录
命令组成
1git commit -a --message "commit message"
命令名称:git commit
- 这是命令的核心部分,指明执行的具体操作。比如
ls用于列出文件,cd用于更改目录,cp用于复制文件等。
选项:-a --message
- 选项是对命令行为进行修改的参数,通常以一个或两个短横线(
-或--)开头。 - 短选项:通常使用单个字符。例如,
ls -l中的-l表示长格式显示。 - 长选项:通常是一个完整的单词或短语,例如,
--all,可以更明确地表达意图。
参数:"commit message"
- 参数指定命令要处理的文件、目录或其他对象。
- 例如,
cp source.txt destination.txt中的source.txt和destination.txt就是参数,分别表示源文件和目标文件。
管道与重定向:
- 通过管道符号(
|)将一个命令的输出作为下一个命令的输入。- 比如,
ls | grep "txt"用于查找包含 “txt” 的文件。
- 比如,
- 重定向符号(如
>、>>、<)用于重定向输出或输入。- 例如,
ls > file.txt将ls命令的输出保存到file.txt文件中。
- 例如,
环境变量与别名:
- 有时命令会引用环境变量。例如,
echo $HOME输出用户的主目录。 - 别名允许为复杂的命令创建简化的替代名称,例如,
alias ll='ls -alF'。
命令换行输入
- 使用反斜杠
\,然后按Enter键,此方式\后面不允许有空格
1echo "This is a very long line that I want to \
2continue on the next line"
- 使用小括号
()、花括号{}或方括号[]来包围多行命令
1(
2echo "This is line 1"
3echo "This is line 2"
4)
- 使用单引号或双引号,这种方法会将换行符包含在输出中
1echo "This is line 1
2This is line 2"
命令提示区域语言设置
解决终端界面或者提示信息为中文的问题
1locale # 列出当前终端的区域语言配置
2
3LANG=zh_CN.UTF-8
4LANGUAGE=en_US
5LC_CTYPE="en_US.UTF-8"
6LC_NUMERIC="en_US.UTF-8"
7LC_TIME="en_US.UTF-8"
8LC_COLLATE="en_US.UTF-8"
9LC_MONETARY="en_US.UTF-8"
10LC_MESSAGES="en_US.UTF-8"
11LC_PAPER="en_US.UTF-8"
12LC_NAME="en_US.UTF-8"
13LC_ADDRESS="en_US.UTF-8"
14LC_TELEPHONE="en_US.UTF-8"
15LC_MEASUREMENT="en_US.UTF-8"
16LC_IDENTIFICATION="en_US.UTF-8"
17LC_ALL=
- 如果显示为
zh_CN.UTF-8,而不是en_US.UTF-8英文语言包,可能就是因为语言设置问题。
1 locale -a # 列出系统支持的所有区域语言
- 修改区域语言
1export LANG=en_US.UTF-8
2export LANGUAGE=en_US
3export LC_ALL=en_US.UTF-8
查看或更改默认编辑器
1echo $EDITOR
2export EDITOR=vim
相同命令执行优先级顺序
VSCode 中的终端 node 比 Mac 中的终端 node 优先高
1--- VSCode Terminal
2➜ ~ echo $PATH
3...
4/opt/homebrew/bin
5/opt/homebrew/sbin
6...
7/Users/zailiang/Library/pnpm
8...
9
10--- Mac Terminal
11➜ ~ echo $PATH
12...
13/Users/zailiang/Library/pnpm
14...
15/opt/homebrew/bin
16/opt/homebrew/sbin
17...
VS Code 里 /opt/homebrew/bin 出现在 ~/Library/pnpm 之前,所以命中的是 brew 的 node;
mac 自带终端里 ~/Library/pnpm 在 /opt/homebrew/bin 之前,所以命中的是 pnpm 的 node。
这不是 Node、pnpm、brew 的冲突,而是 PATH 顺序不一致。
1# HomeBrew
2export HOMEBREW_PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
3export HOMEBREW_API_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api #ckbrew
4export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
5eval $(/opt/homebrew/bin/brew shellenv)
6
7# PNPM - 放在 Homebrew 之后,确保 pnpm 的 node 优先
8export PATH="/Users/zailiang/Library/pnpm:$PATH"
将 pnpm 的 PATH 配置移到 brew shellenv 之后,这样 pnpm 路径会添加在 Homebrew 路径之后,确保 pnpm 的 node 优先
Obsidian 云同步目录建立软链接
1ln -s \
2"$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents" \
3"$HOME/Obsidian"
以后
1cd ~/Obsidian