‹ 返回笔记 Esc·

Shell 问题与修复

Shell 配置文件作用范围 /etc/profile 系统级别,影响所有用户的登录 shell,主要用于 bash 和 sh,并且任何兼容 Bourne shell 的登录 shell 都会加载它。…

Shell 配置文件作用范围

/etc/profile 系统级别,影响所有用户的登录 shell,主要用于 bashsh,并且任何兼容 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 的作用:

  1. 在当前 shell 中临时修改 FPATH
  2. Shell 会使用这个值进行补全
  3. 不会自动传递给子进程
1export FPATH="$HOME/.docker/completions:$FPATH"

而加上 export 则会:

  1. 修改当前 shell 中的值
  2. 将这个变量传递给后续所有子进程

如果你想让补全目录在所有 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 的特殊注释,它告诉操作系统该脚本应该使用哪个解释器来执行。


执行脚本方式

  1. 使用 source 命令执行(当前 Shell)
1source test.sh
  1. 使用解释器命令执行(子 Shell),无需为脚本赋予可执行权限
1sh test.sh
2zsh test.sh
  1. 使用相对路径或绝对路径执行(子 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---表示一个具有读、写、执行权限的文件,对于所有者(用户)来说,对于组来说有读和执行权限,而对于其他用户则没有任何权限。


权限修改方式

  1. 符号模式 + - = 符号来表示权限的增加、减少和设置。使用 u(用户)、g(组)、o(其他)和 a(所有)表示权限的作用对象。
1chmod u+w filename                    # 将文件的所有者添加写权限
2chmod go-x directoryname        # 将目录的所有者和所属组的执行权限去除
3chmod a=rw filename                    # 将文件设置为所有用户可读写
  1. 数字模式 421执行 使用三个八进制数字来表示权限,每个数字代表一个用户类别(所有者、所属组、其他用户)。权限的值对应不同的权限组合,使用数字模式时,将三个数字组合在一起,分别表示所有者、所属组和其他用户的权限。
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.txtdestination.txt 就是参数,分别表示源文件和目标文件。

管道与重定向

  • 通过管道符号(|)将一个命令的输出作为下一个命令的输入。
    • 比如,ls | grep "txt" 用于查找包含 “txt” 的文件。
  • 重定向符号(如 >>><)用于重定向输出或输入。
    • 例如,ls > file.txtls 命令的输出保存到 file.txt 文件中。

环境变量与别名

  • 有时命令会引用环境变量。例如,echo $HOME 输出用户的主目录。
  • 别名允许为复杂的命令创建简化的替代名称,例如,alias ll='ls -alF'

命令换行输入

  1. 使用反斜杠 \,然后按 Enter 键,此方式 \ 后面不允许有空格
1echo "This is a very long line that I want to \
2continue on the next line"
  1. 使用小括号 ()、花括号 {} 或方括号 [] 来包围多行命令
1(
2echo "This is line 1"
3echo "This is line 2"
4)
  1. 使用单引号或双引号,这种方法会将换行符包含在输出中
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=
  1. 如果显示为zh_CN.UTF-8,而不是en_US.UTF-8 英文语言包,可能就是因为语言设置问题。
1 locale -a            # 列出系统支持的所有区域语言
  1. 修改区域语言
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