Bash Shell 脚本总结

2 阅读4分钟

一、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 必看)

  1. var="abc"=两边不能有空格
  2. if [ xxx ][后面、]前面必须空格;
  3. 变量尽量加双引号 "$var",防止带空格、换行炸语法;
  4. $1 ${arr[@]}这类变量 / 数组,不加引号遇到文件名带空格直接逻辑错乱;
  5. 数字比较用‑gt‑lt‑eq,不能直接写>
  6. Windows GitBash 会自动转换路径,adb push 要设置MSYS_NO_PATHCONV=1关闭路径转换;
  7. bash 没有真正的函数返回值,return 只是退出码;
  8. 数组遍历务必写 "${arr[@]}",双引号不能丢。

13. Bash vs Python 核心差异总结

表格

项目Bash ShellPython
定位调用系统命令、脚本流水线、菜单自动化通用编程语言,逻辑运算强
函数func(){}def func():
变量a=10,无类型a=10,强类型
判断if [ -f xx ];thenif 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执行完毕"

运行效果说明

  1. 运行后会交互式等待你输入内容;
  2. 会生成临时文件 test_demo.tmp,脚本结束自动删除;
  3. set -euo pipefail 是严谨脚本推荐开启,写错变量、命令失败脚本直接终止,方便排错。