bash script.sh与./script.sh执行脚本的差异探究:以历史命令获取场景为例
为什么
bash env.sh和./env.sh执行结果不同? 首先得搞清楚这两种执行方式的核心差异,以及history命令的工作逻辑——这是解开你问题的关键:
核心差异:子shell的运行模式与环境继承
bash env.sh:直接启动一个全新的非交互式bash子进程来运行脚本。这个子进程是完全独立的,默认不会继承父shell(你当前正在用的交互式bash)的内存历史列表,而且非交互式bash默认会关闭history功能(相当于执行了set +o history),连读取历史文件(比如~/.bash_history)的操作都不会主动触发。./env.sh:执行逻辑分两种情况:- 无shebang声明时:系统内核找不到脚本开头的解释器声明,会调用你当前的登录shell(也就是你正在用的交互式bash)来处理这个脚本。此时启动的子shell会继承父shell的环境变量(包括
HISTFILE),并且因为父shell是交互式的,这个子shell会默认开启history功能,能读取到父shell的历史文件(如果你的bash配置了实时写入历史,比如PROMPT_COMMAND='history -a',那刚输入的命令已经存在历史文件里了)。 - 有
#!/bin/bash声明时:内核直接根据shebang启动一个非交互式bash子进程,这就和bash env.sh的情况完全一致了——非交互式模式下history功能关闭,拿不到任何历史输出。
- 无shebang声明时:系统内核找不到脚本开头的解释器声明,会调用你当前的登录shell(也就是你正在用的交互式bash)来处理这个脚本。此时启动的子shell会继承父shell的环境变量(包括
逐个解释你的现象
bash env.sh输出为空:
这个命令启动的非交互式bash子进程既没继承父shell的内存历史,又默认禁用了history功能,history命令本身就没有输出,后面的sed自然也处理不出任何内容。无shebang的
./env.sh正常输出:
此时系统用你当前的交互式bash来启动子shell,它继承了父shell的HISTFILE变量,并且开启了history功能。如果你的bash配置了实时把命令写入历史文件(大部分默认配置都会这么做,或者至少在执行新命令前会刷新历史),那子shell就能读取到你执行脚本前输入的最后一条命令。加了
#!/bin/bash的./env.sh输出为空:
加了shebang后,内核直接启动非交互式bash子进程,和bash env.sh的运行环境完全一致——history功能关闭,无法读取历史,所以输出为空。
补充:怎么让非交互式shell能读取历史?
如果想让bash env.sh或者带shebang的./env.sh也能拿到父shell的最后一条命令,可以在脚本开头加两行:
#!/bin/bash HISTFILE=~/.bash_history # 指定历史文件路径 set -o history # 开启history功能
不过要注意,这只能拿到已经写入历史文件的命令——如果父shell还没把刚输入的命令写入(比如没设置PROMPT_COMMAND='history -a'),那还是拿不到最新的那条。
内容的提问来源于stack exchange,提问作者Mohamed Amine Oueslati




