Shell基础与运行环境
Shell 基础与运行环境
Shell 是用户与操作系统内核之间的命令解释层;Bash 是 Linux 中最常见的 Shell 实现之一。本篇先建立脚本执行、变量作用域和启动环境的整体认识,后续再进入字符串、数组、流程控制与文本处理。
阅读路线:基础与运行环境 → 字符串、数组与运算符 → 流程控制、函数与重定向 → 文本处理工具与综合练习 → 自动推送脚本
Shell、终端与 Bash
- 终端(Terminal):负责显示输入输出,是与 Shell 交互的窗口。
- Shell:读取命令、展开变量、建立管道并启动程序。
- Bash:Shell 的一种具体实现,常见路径是
/bin/bash。 - Shell 脚本:按顺序保存 Shell 命令的文本文件,通常使用
.sh后缀。
可以先确认当前环境:
代码块收起展开
echo $SHELL # 登录时配置的默认 Shell
echo $0 # 当前 Shell 或脚本名称
bash --version
command -v bash # Bash 可执行文件的位置$SHELL 表示账户的默认 Shell,不一定等于当前正在运行的解释器;判断当前脚本应优先结合 $0、BASH_VERSION 等信息。
第一个 Bash 脚本
新建 hello.sh:
代码块收起展开
#!/usr/bin/env bash
name=${1:-Shell}
printf 'Hello, %s!\n' $name第一行称为 shebang,用于告诉系统应由哪个解释器执行脚本。/usr/bin/env bash 会从当前 PATH 中寻找 Bash,跨环境时通常比写死 /bin/bash 更灵活。
代码块收起展开
chmod +x hello.sh
./hello.sh World如果脚本在 Windows 和 Linux 间复制后出现 bad interpreter 或行尾带有 ^M,通常是 CRLF 行尾导致的,应转换为 LF。
脚本的执行方式
| 执行方式 | 是否需要执行权限 | 运行位置 | 适用场景 |
|---|---|---|---|
bash script.sh | 否 | 新 Bash 进程 | 明确使用 Bash 解释脚本 |
./script.sh | 是 | 新进程 | 按 shebang 正常执行脚本 |
source script.sh | 否 | 当前 Shell | 加载环境变量、函数或别名 |
. script.sh | 否 | 当前 Shell | source 的 POSIX 简写 |
子进程不能直接修改父进程的工作目录和普通变量。因此下面两种方式结果不同:
代码块收起展开
bash change-dir.sh # 脚本中的 cd 不影响当前终端
source change-dir.sh # cd 和变量修改保留在当前终端只有确实需要改变当前 Shell 状态的配置脚本才应使用 source,普通任务脚本通常应独立执行。
命令组合与退出状态
Linux 命令结束后会返回退出状态码:0 表示成功,非 0 表示失败。
代码块收起展开
mkdir demo
echo $?常见组合方式:
代码块收起展开
command1 ; command2 # 无论 command1 是否成功,都执行 command2
command1 && command2 # command1 成功才执行 command2
command1 || command2 # command1 失败才执行 command2
command1 | command2 # 将前一个命令的标准输出交给后一个命令例如:
代码块收起展开
mkdir -p build && cd build
grep 'ERROR' app.log | sort | uniq -c变量与引用
定义和读取变量
变量赋值时,等号两侧不能有空格:
代码块收起展开
project_name='shell-demo'
count=3
echo $project_name
echo ${project_name}-${count}推荐用 ${变量名} 明确变量边界,并在普通字符串场景中使用双引号,避免空格、通配符和空值造成意外拆分。
单引号、双引号与不加引号
代码块收起展开
name='World'
echo '$name' # 原样输出 $name
echo $name # 展开变量
echo $name # 会发生单词拆分和路径名展开,不建议默认使用- 单引号
'...':内容按字面值处理,不展开变量和命令。 - 双引号
...:展开变量和命令替换,但保留整体参数边界。 - 不加引号:还可能发生单词拆分和通配符展开,最容易产生隐蔽问题。
命令替换
使用 $(...) 获取命令输出:
代码块收起展开
current_dir=$(pwd)
today=$(date +%F)
printf '目录:%s,日期:%s\n' $current_dir $today相比旧式反引号,$(...) 更易读,也更容易嵌套。
Shell 变量与环境变量
普通变量只存在于当前 Shell;使用 export 后,它才会作为环境变量传递给后续创建的子进程。
代码块收起展开
app_mode='dev'
export app_mode
bash -c 'echo $app_mode'也可以只为单次命令提供环境变量:
代码块收起展开
APP_MODE=prod ./start.sh常用管理命令:
代码块收起展开
export NAME=value # 导出环境变量
readonly NAME=value # 创建只读变量
unset NAME # 删除变量
env # 查看环境变量
declare -p NAME # 查看变量的准确声明位置参数与特殊变量
执行 ./deploy.sh api prod 时,脚本可以通过特殊变量读取调用信息:
| 变量 | 含义 |
|---|---|
$0 | 脚本名称 |
$1、$2 | 第 1、2 个位置参数 |
$# | 参数数量 |
$@ | 保持参数边界的全部参数 |
$? | 上一条命令的退出状态 |
$$ | 当前 Shell 的进程 ID |
$! | 最近一个后台任务的进程 ID |
代码块收起展开
#!/usr/bin/env bash
printf '脚本:%s\n' $0
printf '参数数量:%s\n' $#
for arg in $@; do
printf '参数:%s\n' $arg
done遍历调用参数时应使用 $@;$* 会更容易丢失原始参数边界。
PATH 与命令查找
Shell 会按照 PATH 中从左到右的目录顺序查找命令:
代码块收起展开
printf '%s\n' $PATH | tr ':' '\n'
command -v java
type cdcommand -v:判断命令最终会解析到哪里。type:还能识别内置命令、函数和别名。- 当前目录默认不在
PATH中,因此执行当前脚本通常要写./script.sh。 - 不建议把
.放入公共PATH,否则可能误执行当前目录中的同名恶意文件。
临时追加目录:
代码块收起展开
export PATH=$HOME/bin:$PATH需要长期生效时,应把它写入合适的 Bash 启动文件,而不是每次手动执行。
登录、交互与启动文件
理解 Bash 环境时要区分两个维度:
- 交互式 / 非交互式:是否正在等待用户输入命令。
- 登录 / 非登录:是否作为一次登录会话启动。
| 场景 | 常见启动文件 |
|---|---|
| 登录 Shell | /etc/profile,再读取 ~/.bash_profile、~/.bash_login、~/.profile 中第一个存在的文件 |
| 交互式非登录 Shell | /etc/bash.bashrc(发行版相关)与 ~/.bashrc |
| 普通非交互脚本 | 通常不会自动读取 ~/.bashrc;可按需使用 BASH_ENV |
常见做法是在 ~/.bash_profile 中主动加载 ~/.bashrc:
代码块收起展开
if [[ -f ~/.bashrc ]]; then
source ~/.bashrc
fi推荐的职责划分:
~/.profile或~/.bash_profile:登录时只需设置一次的环境变量。~/.bashrc:别名、提示符、补全和交互函数。- 项目脚本:项目自身所需的变量与逻辑,不依赖个人终端配置。
判断当前 Bash 类型:
代码块收起展开
[[ $- == *i* ]] && echo '交互式 Shell'
shopt -q login_shell && echo '登录 Shell'调试与安全执行
代码块收起展开
bash -n script.sh # 只检查语法,不执行
bash -x script.sh # 展开并打印执行过程较严谨的脚本可以从下面的模板开始:
代码块收起展开
#!/usr/bin/env bash
set -Eeuo pipefail
main() {
local target=${1:-default}
printf '处理目标:%s\n' $target
}
main $@-e:未处理的失败命令会终止脚本。-u:读取未定义变量时终止脚本。pipefail:管道中任一命令失败,整条管道即视为失败。-E:让错误陷阱在函数和子 Shell 等场景中继续生效。
这些选项会改变错误处理语义,使用时仍应显式处理允许失败的命令,例如放入 if 判断或搭配清晰的兜底逻辑。
常见错误速查
| 问题 | 原因 | 修正方式 |
|---|---|---|
name = value 报错 | 赋值等号两侧有空格 | 改为 name=value |
| 变量为空或被拆成多个参数 | 展开时未加双引号 | 默认写成 $name |
./script.sh: Permission denied | 没有执行权限 | chmod +x script.sh |
| 修改变量后当前终端无变化 | 脚本在子进程执行 | 需要改变当前环境时使用 source |
command not found | 命令不在 PATH,或当前目录未写 ./ | 用 command -v 检查路径 |
bad interpreter / ^M | Windows CRLF 行尾 | 转为 Linux LF 行尾 |
基础练习
- 编写
hello.sh,接收一个姓名参数;没有参数时使用默认值。 - 编写
check-command.sh,用command -v判断指定命令是否存在,并返回不同状态码。 - 分别用
bash script.sh和source script.sh修改变量、切换目录,观察父子 Shell 的差异。 - 打印
$@中的每个参数,验证带空格参数在加引号和不加引号时的区别。
掌握这些基础后,再进入字符串展开、数组、运算符和流程控制,就能明确每段语法究竟发生在当前 Shell、子进程还是命令展开阶段。