一、Bash Shell 脚本完整总结
Bash = Bourne‑Again Shell,是 Linux/macOS/WSL/GitBash 的命令解释器。
本质:把你在终端手动敲的一条条命令,写到文件里批量执行。不是编译语言,是解释执行。文件后缀一般
.sh。
1. 脚本开头
#!/bin/bash
叫 shebang,告诉操作系统:用 /bin/bash 解释这个文件。
Windows GitBash 也识别这一行。
运行脚本两种方式:
# 方式1:赋予执行权限
chmod +x dev.sh
./dev.sh
# 方式2:直接交给bash解释
bash dev.sh
2. 注释
# 这是单行注释
bash没有多行注释符号,多行只能每行加#。
<<EOF不是注释,是 here‑doc 文本块。
3. 变量
3.1 普通变量
name="zeekr" # =两边**不能有空格**!这是高频踩坑点
echo $name # 使用变量
echo "${name}" # 推荐加{},边界清晰
- 字符串双引号
"":支持变量展开; - 单引号
'':原样输出,不解析变量。
a=100
echo "$a" #输出100
echo '$a' #输出$a
3.2 特殊内置变量(非常常用)
表格
| 变量 | 含义 |
|---|---|
$0 | 脚本本身文件名 / 路径 |
$1 $2 $3 | 脚本 / 函数的第 1、2、3 个参数 |
$# | 传入参数总个数 |
$@ | 全部参数(数组形式) |
$? | 上一条命令退出码,0 = 成功,非 0 = 失败 |
$$ | 当前脚本进程 PID |
示例:./dev.sh aa bb $1就是aa,$2就是bb。
3.3 局部变量(函数内部)
test(){
local x=10 # local:仅函数内部生效,外部不可见
echo $x
}
3.4 数组
bash 一维数组,索引从 0 开始
arr=("a.apk" "b.apk" "c.apk")
echo ${arr[0]} #取第0个元素
echo ${#arr[@]} #获取数组长度(元素个数)
echo ${arr[@]} #取出全部元素
3.5 命令输出赋值 $()
把命令执行结果存到变量
files=$(find . -name "*.apk")
旧写法
`find . -name "*.apk"`反引号,等价,但可读性差,优先用$()。
4. 字符串操作(bash 内置,不用调用外部工具)
path="app/build/output/apk/zeekr/debug/app.apk"
${path##*/} #贪婪删除前缀,取文件名 → app.apk
${path%%/*} #贪婪删除后缀
${path^} #首字母大写
${path,,} #全部小写
5. 输入输出
echo 输出
echo "hello world"
read 获取用户输入
read var #读取输入存入var
read -p "请输入:" var #带提示文字(dev.sh大量使用)
重定向
cmd > out.txt #标准输出覆盖写入文件
cmd >> out.txt #追加写入文件
cmd 2>/dev/null #把错误输出丢弃(屏蔽报错)
cmd > log.txt 2>&1 #标准输出+错误全部写入日志
Here‑doc <<EOF
把多行文本直接输出到文件,dev.sh 保存配置用的:
cat > test.txt <<EOF
line1
line2
EOF
EOF 是自定义标记,前后标记必须完全一致。
6. 判断语法 if [ 条件 ]
⚠️
[前后必须有空格,]前面也必须空格,写错直接报错。
if [ -f "a.txt" ]; then
echo "文件存在"
elif [ -d "./dir" ]; then
echo "是文件夹"
else
echo "不存在"
fi
文件判断(高频)
表格
| 条件 | 含义 |
|---|---|
[ -f file ] | 文件存在且是普通文件 |
[ -d dir ] | 存在且是目录 |
[ ! -f file ] | 取反,文件不存在 |
字符串判断
[ "$a" = "$b" ] #字符串相等
[ "$a" != "$b" ] #不等
[ -z "$str" ] #字符串为空
[ -n "$str" ] #字符串非空
数字比较
[ $x -gt 10 ] #大于
[ $x -ge 10 ] #大于等于
[ $x -lt 10 ] #小于
[ $x -le 10 ] #小于等于
[ $x -eq 10 ] #等于
[ $x -ne 10 ] #不等于
⚠️数字比较用
-gt/-lt,不能直接用> <;>在[]里面会被识别为重定向。
7. 分支 case
dev.sh 菜单大量使用,匹配多分支
case $choice in
1)
do_build
;;
2)
do_test
;;
0)
exit 0
;;
*)
echo "无效选择"
;;
esac
1|2)可以匹配 1 或者 2;;;每个分支结束必须写;*)相当于 default,匹配其他所有情况。
8. 循环
while 循环
while true; do #无限循环,dev.sh主菜单就是这个
echo "loop"
sleep 1
done
while read line; do
echo "$line"
done < test.txt
for 循环
#遍历数组
for name in "${arr[@]}"; do
echo "$name"
done
#数字循环
for i in {1..5}; do
echo $i
done
IFS 分割字符串
IFS是内部字段分隔符,用来按逗号 / 空格切分字符串:
str="1,2,3"
IFS=',' read -ra parts <<< "$str"
-a把切割结果存入数组parts。dev.sh 多选测试方法就是这个。
9. 函数
func_name() {
# $1 $2 获取传入的参数
echo "param: $1"
return 0 #返回退出码,不是返回值
}
#调用
func_name "hello"
bash 函数没有 return 返回数据,return 只能返回 0‑255 的退出码;要输出数据只能 echo,然后用
var=$(func xxx)捕获输出。
10. 调用外部命令
bash 最大能力:调用系统命令 adb find grep sed gradlew
adb shell dumpsys package xxx
find app/build -name "*.apk"
grep -A1 "@Test" file.kt #A1:匹配行同时输出后面1行
sed -n 's/.*fun ([^(]*).*/\1/p' file.kt
管道 |
把前一个命令输出交给后一个命令作为输入
adb logcat | grep CarProperty
source 命令
source ./config.sh
在当前 shell 执行文件,把里面变量导入当前环境,dev.sh 加载.last_config就用这个。
11. 退出
exit 0 #正常退出
exit 1 #异常退出
return #退出当前函数,不是退出脚本
12. 非常容易踩坑的坑清单(写 shell 必看)
var="abc",=两边不能有空格;if [ xxx ],[后面、]前面必须空格;- 变量尽量加双引号
"$var",防止带空格、换行炸语法; $1 ${arr[@]}这类变量 / 数组,不加引号遇到文件名带空格直接逻辑错乱;- 数字比较用
‑gt‑lt‑eq,不能直接写>; - Windows GitBash 会自动转换路径,adb push 要设置
MSYS_NO_PATHCONV=1关闭路径转换; - bash 没有真正的函数返回值,return 只是退出码;
- 数组遍历务必写
"${arr[@]}",双引号不能丢。
13. Bash vs Python 核心差异总结
表格
| 项目 | Bash Shell | Python |
|---|---|---|
| 定位 | 调用系统命令、脚本流水线、菜单自动化 | 通用编程语言,逻辑运算强 |
| 函数 | func(){} | def func(): |
| 变量 | a=10,无类型 | a=10,强类型 |
| 判断 | if [ -f xx ];then | if os.path.exists(xx): |
| 数组 | arr=(a b c) | list 列表 |
| 字符串处理 | ${var##*/}内置 | 字符串方法.split() |
| 调用外部程序 | 直接写命令 | subprocess.run() |
你手上的 dev.sh:业务逻辑很少,大部分工作就是组装 adb/gradlew/find/grep 命令,减少手动敲长命令
二、bash_minidemo.sh 完整演示脚本
bash_minidemo.sh 完整演示脚本
复制全部保存为 bash_minidemo.sh, GitBash / WSL / Linux/macOS 执行:
chmod +x bash_minidemo.sh
./bash_minidemo.sh
#!/bin/bash
set -eo pipefail
# set -e: 命令出错直接退出脚本
# set -u: 使用未定义变量直接报错
# set -o pipefail: 管道中间命令失败整体标记失败
echo "==================== 1.变量演示 ===================="
# 普通变量,=两边不能空格
name="VolvoAdapter"
version=100
echo "name=$name , version=$version"
echo "大括号边界:${name}_demo"
# 双引号解析变量;单引号原样输出
echo "双引号: name=$name"
echo '单引号: name=$name'
# 命令输出赋值 $()
cur_dir=$(pwd)
echo "当前目录: $cur_dir"
echo ""
echo "==================== 2.内置特殊变量 ===================="
echo '脚本本身路径 $0:' "$0"
echo '传入第1参数 $1:' "$1"
echo '参数总个数 $#:' "$#"
echo ""
echo "==================== 3.字符串内置操作 ===================="
file_path="app/build/output/apk/zeekr/debug/app.apk"
echo "原始路径: $file_path"
echo '${file_path##*/} 取文件名:' "${file_path##*/}"
echo '${file_path^} 首字母大写:' "${name^}"
echo '${name,,} 全部小写:' "${name,,}"
echo ""
echo "==================== 4.数组 ===================="
apk_list=("app1.apk" "app2.apk" "app3.apk")
echo "数组全部元素: ${apk_list[@]}"
echo "数组长度: ${#apk_list[@]}"
echo "下标0元素: ${apk_list[0]}"
# for遍历数组
echo "for遍历数组:"
for item in "${apk_list[@]}"; do
echo " -> $item"
done
echo ""
echo "==================== 5.read读取用户输入 ===================="
read -p "请随便输入一点文字: " user_input
echo "你输入的是: $user_input"
echo ""
echo "==================== 6.if条件判断 ===================="
# 文件判断
test_file="./test_demo.tmp"
if [ -f "$test_file" ]; then
echo "$test_file 文件存在"
else
echo "$test_file 文件不存在"
fi
# 字符串判空
str1=""
str2="hello"
if [ -z "$str1" ]; then
echo "str1是空字符串"
fi
if [ -n "$str2" ]; then
echo "str2非空: $str2"
fi
# 数字比较
a=5
b=10
if [ $a -lt $b ]; then
echo "$a 小于 $b"
fi
echo ""
echo "==================== 7.Here‑doc 写入文件 ===================="
cat > "$test_file" <<EOF
line 1 测试文本
line 2 name=$name
EOF
echo "已写入 $test_file"
cat "$test_file"
echo ""
echo "==================== 8.case多分支 ===================="
read -p "请输入数字 1/2/0 测试case: " sel
case $sel in
1)
echo "你选了1"
;;
2)
echo "你选了2"
;;
0)
echo "选择退出case演示"
;;
*)
echo "其他未知选项"
;;
esac
echo ""
echo "==================== 9.函数演示 ===================="
demo_func(){
local arg1="$1" # local局部变量,仅函数内部
echo "demo_func收到参数: $arg1"
# return只返回退出码,0成功,非0失败
return 0
}
demo_func "hello_bash"
echo ""
echo "==================== 10.字符串分割 IFS ===================="
csv_str="a,b,c,d"
IFS=',' read -ra parts <<< "$csv_str"
echo "分割后数组元素:"
for p in "${parts[@]}"; do
echo " $p"
done
echo ""
echo "==================== 11.管道 | 与重定向 ===================="
# 管道,ls输出交给grep过滤
ls -la | grep sh
# 丢弃错误输出 2>/dev/null
ls not_exist_file.txt 2>/dev/null || echo "文件不存在,错误已经屏蔽"
echo ""
echo "==================== 12.while循环(按行读取文件) ===================="
echo "读取刚才生成的 $test_file :"
while read -r line; do
echo " | $line"
done < "$test_file"
# 清理临时文件
rm -f "$test_file"
echo ""
echo "全部demo执行完毕"
运行效果说明
- 运行后会交互式等待你输入内容;
- 会生成临时文件
test_demo.tmp,脚本结束自动删除; set -euo pipefail是严谨脚本推荐开启,写错变量、命令失败脚本直接终止,方便排错。