./main.sh vs source main.sh 讲透

在终端里跑脚本的可能写法:

  • ./main.sh
  • source main.sh(或 . main.sh
  • bash main.sh / sh main.sh
  • 甚至 exec ./main.shnohup ./main.sh &

它们看起来都“能跑”,但性质上有差异。讲清楚避免经典踩坑。

差异的核心只有三点:

  1. 在哪个进程里执行?(当前 shell 还是子 shell)
  2. 会不会影响当前环境?(cd / export / 函数 / alias 等是否保留)
  3. 用哪个解释器?(shebang 决定还是你当前 shell 决定)

1. ./main.sh:把脚本当“程序”执行

./main.sh 会启动一个新的进程来运行脚本(可以理解为子 shell / 子进程)。

特性

  • 需要可执行权限:通常要 chmod +x main.sh
  • 解释器由 shebang 决定:脚本第一行例如:#!/usr/bin/env bash
  • 不会影响当前终端环境:脚本里 cd、变量赋值、export、函数定义……对你的当前 shell 都不会生效
  • echo $? 看到的是脚本退出码

适用场景

  • 构建/部署/批处理
  • 只想跑一次任务,不希望“污染”当前终端

2. source main.sh / . main.sh:把脚本“引入”当前 shell

source(或点号 .)不是启动新进程,而是把文件内容当作命令,在当前 shell 进程里逐行执行

特性

  • 不需要可执行权限(只要可读即可)
  • 会影响当前终端环境:脚本里做的 cd、变量、export、函数、alias、set -oulimit 等都会留下来
  • 脚本里的 exit 会直接把你的终端退出。因为脚本就是在当前 shell 里跑的,被 source 的脚本要提前结束,应该用 return(或把逻辑写到函数里)
  • 解释器不是 shebang 决定的:而是你的当前 shell(bash/zsh 等)。当然这个脚本的语法必须兼容你当前 shell

适用场景

  • 加载环境变量(如 venv/bin/activateconda
  • 设置 PATH / 代理 / 函数工具库
  • 需要让脚本的改动“留在当前终端”里

经典案例

假设脚本 main.sh 内容如下:

cd /tmp
FOO=bar
export BAR=baz

./main.sh 运行

./main.sh
pwd         # 仍然是原目录
echo "$FOO" # 空
echo "$BAR" # 空(通常)

因为这些变更发生在子进程里,脚本结束后子进程消失,环境也就消失了。

source main.sh 运行

source main.sh
pwd         # 变成 /tmp
echo "$FOO" # bar
echo "$BAR" # baz

因为变更发生在当前 shell,自然“留下来了”。

其他类似命令

3. bash main.sh:强制用 Bash 执行(子进程)

特性

  • ./main.sh类似,在子进程里跑:对当前 shell 不生效。不过不需要可执行权限
  • 强制用 bash 解析(无视 shebang)

适用场景

  • 脚本使用 bash 特性(数组、[[ ]]set -o pipefail
  • 你不想改权限或不想依赖 shebang

4. main.sh:错误写法

你可能想要./main.sh,但直接写main.sh不对,这是因为大多数系统默认 当前目录不在 PATH(安全原因)。

  • 当你在 Shell 中直接输入一个命令时,如果命令中包含斜杠,Shell 会将其视为路径,直接访问该位置的文件。

  • 无斜杠则触发 PATH 查找:如果命令是单纯的 main.sh,Shell 会将其理解为命令名,并在 PATH 环境变量所列出的目录中按顺序查找。出于安全考虑,绝大多数系统默认不将当前目录(.)加入 PATH

为什么作为参数时又不需要 ./ 了?

main.sh 不是命令行的第一个“单词”时,它的解析者不再是当前的 Shell,而是前面的命令。

  • bash main.sh:这里 bash 本身是一个命令,Shell 通过 PATH 找到了 /bin/bash 并启动它。main.sh 是作为参数传递给 bash 解释器的。bash 在处理这个参数时,会将其直接当作文件路径来寻找,而文件路径的查找默认始于当前目录source main.sh. main.sh同理

5. exec ./main.sh:用脚本替换当前 shell 进程

exec 会让脚本进程替换当前 shell 进程,而不是新开子进程。

  • 不会“回来”到原来的 shell(因为它被替换了)
  • 常用于:容器 entrypoint、进程接管、长驻前台任务

6. ( ... ):括号创建子 shell,隔离副作用

括号会创建一个子 shell,里面的 cd、变量等不会影响外部。

( source main.sh )
# main.sh 里的 cd/export 不会污染当前 shell
需求 推荐写法
只想跑脚本,不影响当前终端 ./main.shbash main.sh
脚本要修改当前终端环境(export / cd / 函数) source main.sh / . main.sh
脚本依赖 bash 特性,且不想 chmod bash main.sh
追求 POSIX 可移植 sh main.sh(确保脚本兼容)
想执行但不污染当前 shell ( ./main.sh )( source main.sh )
要用脚本接管当前进程 exec ./main.sh
要后台运行 / 断开终端仍运行 nohup ./main.sh & / setsid
posted @ 2026-01-21 15:43  Ofnoname  阅读(4)  评论(0)    收藏  举报