Shell脚本入门教程:零基础学Shell
Shell脚本的shebang写法需根据环境选择,跨平台建议用#!/usr/bin/envbash,固定环境可用#!/bin/bash。CRLF换行符会导致报错,可用dos2unix修复。权限问题可能源于文件系统noexec选项,可直接用bash执行。source与./区别在于是否新建子shell。变量赋值需加双引号避免拆分。
Shell脚本的shebang行虽然简单,却常让人纠结:应该使用#!/bin/bash还是#!/usr/bin/env bash?正确的选择取决于脚本的运行环境。

脚本第一行写 #!/bin/bash 还是 #!/usr/bin/env bash?
固定路径的优势在于,大多数Linux发行版(如CentOS、Ubuntu)将/bin/bash放在固定位置,直接运行没有问题。然而,跨平台场景下可能出现问题——某些容器环境或新版macOS中,/bin/bash可能不存在或版本不够新。而#!/usr/bin/env bash通过$PATH动态查找第一个bash,通用性和健壮性更强。
一个容易让人误解的报错:./script.sh: /bin/bash^M: bad interpreter。这通常不是shebang写错,而是Windows换行符(CRLF)导致的问题。使用dos2unix script.sh处理,或在编辑器中把换行符改为LF即可解决。
- 生产环境脚本,尤其是涉及CI/CD或多平台部署时,建议统一使用
#!/usr/bin/env bash。 - 如果只运行在CentOS/RHEL 7+或Ubuntu 20.04+等固定环境中,
#!/bin/bash完全够用。 - 注意
#!/bin/sh不等于#!/bin/bash:前者调用POSIX兼容shell(如dash),不支持数组、[[、$(())等Bash独有特性。
为什么 chmod +x 后还是报 “Permission denied”?
权限问题的真正原因常被误判。实际上,往往是文件系统挂载时带有noexec选项,或脚本存放在NFS、FAT32、NTFS等不支持Unix权限的分区上。
如何验证?使用mount | grep "$(df . | tail -1 | awk '{print $1}')"查看当前目录所在挂载点是否包含noexec,再用ls -l script.sh确认x位是否存在。
- 临时解决方案:直接用
bash script.sh执行。此方式不依赖文件权限,但会启动子shell,变量修改不会影响当前环境。 - 根本解决方法:重新挂载时去掉
noexec,或将脚本移到/tmp、/home等本地ext4/xfs分区。 - 注意:
sudo chmod +x对只读文件系统无效;sudo无法解除挂载选项的限制。
./script.sh 和 source script.sh 的本质区别是什么?
关键差别在于是否新建shell进程。./script.sh会fork一个子shell执行,脚本内定义的变量、函数、cd切换仅作用于子shell,退出后不影响当前终端。source(或简写为.)则直接在当前shell进程内逐行解释执行,所有变更即时生效。
典型误用场景:编写一个export PATH=$PATH:/my/bin的脚本,用./setup.sh执行后发现$PATH未发生变化——因为修改只发生在子shell中。
- 适合
./script.sh:独立任务,如备份、日志清理、批量重命名。 - 适合
source script.sh:初始化环境、加载函数库、设置别名、修改当前shell状态。 - 注意:
source不检查shebang,会直接忽略第一行,完全按当前shell的语法解析。
变量赋值时加不加引号?什么时候必须加?
不加引号是Shell脚本中最隐蔽的bug来源。只要变量值可能包含空格、制表符、通配符(*、)、路径分隔符或特殊字符,就必须用双引号括起来。
错误示例:file="my file.txt"; cp $file /tmp,实际执行的是cp my file.txt /tmp,导致参数被拆分,cp报“missing destination”。正确写法:cp "$file" /tmp。
- 单引号
'$var':完全禁止变量展开,适合纯字面量字符串。 - 双引号
"$var":允许变量展开和命令替换,同时保留空格结构。 - 未加引号
$var:触发word splitting和pathname expansion,属于危险操作。 - 例外:在
[[条件判断中,[[ $var == "abc" ]]不加引号也可行,但加上更安全。
实际编写Shell脚本时,最容易忽略的不是语法细节,而是执行上下文——当前工作目录、环境变量继承关系、stdin/stdout是否被重定向、脚本退出码是否被上游调用者检查。这些细节往往决定了自动化任务的可靠性。
