Shell 脚本语法

Shell 脚本语法总结

1. 条件测试 [ ] (类似 Python 的 if

Shell 中使用 [ ] 包裹条件表达式,注意方括号内部必须有空格

bash 复制代码
# Python: if x > 0:
# Shell:
if [ "$1" == "stop" ]; then

常见测试运算符

语法 含义 Python 等价
-f file 文件存在且是普通文件 Path(file).is_file()
-d file 文件存在且是目录 Path(file).is_dir()
-z string 字符串为空 len(string) == 0
-n string 字符串非空 string (truthy)
string1 == string2 字符串相等 string1 == string2
-eq, -ne, -lt, -le, -gt, -ge 数字比较 ==, !=, <, <=, >, >=
-a 逻辑与 (and) and
-o 逻辑或 (or) or
! 逻辑非 (not) not

示例(来自脚本):

bash 复制代码
# 如果目录不存在
if [ ! -d "$TASK_DIR" ]; then
  echo "Error: Task directory not found"
  exit 1
fi

# 如果文件存在
if [ -f "$EVAL_FILE" ]; then
  COMMAND_ARRAY+=("--eval-file" "$EVAL_FILE")
fi

2. 变量操作

定义和访问

bash 复制代码
# Python: name = "Alice"
# Shell:
NAME="Alice"
echo "$NAME"           # 输出变量值(加 $ 符号)
echo "${NAME}"         # 同上,大括号可选但更明确

特殊变量

bash 复制代码
$0    # 脚本名称
$1    # 第一个参数
$2    # 第二个参数
$@    # 所有参数(列表)
${@:2} # 从第2个开始的所有参数(类似 Python slicing)
$?    # 上一个命令的退出码
$$    # 当前进程 PID

3. 数组(类似 Python list)

bash 复制代码
# 定义数组
ARRAY=("apple" "banana" "cherry")

# 访问元素
echo "${ARRAY[0]}"     # "apple"

# 获取长度
echo "${#ARRAY[@]}"    # 3

# 追加元素
ARRAY+=("date")

# 遍历
for item in "${ARRAY[@]}"; do
  echo "$item"
done

示例(来自脚本):

bash 复制代码
# 将元素添加到数组
PYTHON_ARGS=()
PYTHON_ARGS+=("$arg")

4. 字符串操作

bash 复制代码
# 字符串拼接
STR="hello"
STR="$STR world"

# 子串提取
PATH="/home/yjp/projects/LoongFlow"
echo "${PATH:0:5}"     # "/home" (从位置0取5个字符)
echo "${PATH##*/}"    # "LoongFlow" (去掉最长匹配的前缀)
echo "${PATH%/*}"     # "/home/yjp/projects" (去掉最短匹配的后缀)

5. 函数定义

bash 复制代码
# 定义函数
my_function() {
  local arg1="$1"     # local 声明局部变量
  local result="something"
  echo "$result"      # 返回值(通过 echo)
}

# 调用函数
result=$(my_function "input")  # 获取返回值

示例(来自脚本):

bash 复制代码
get_descendants() {
  local parent="$1"
  # ...
  echo "$c"
  get_descendants "$c"  # 递归调用
}

6. 算术运算

bash 复制代码
# 方法1: 使用 (( ))
a=5
b=3
((c = a + b))          # c = 8

# 方法2: 使用 let
let "c = a * b"        # c = 15

# 方法3: 使用 $(( ))
c=$((a - b))           # c = 2

7. 命令替换

bash 复制代码
# 获取命令输出
TODAY=$(date +%Y-%m-%d)   # 推荐写法
TODAY=`date +%Y-%m-%d`    # 旧式写法,不推荐

8. 流程控制

if-elif-else

bash 复制代码
if [ condition1 ]; then
  # do something
elif [ condition2 ]; then
  # do something
else
  # do something
fi    # 注意:if 要以 fi 结尾!

for 循环

bash 复制代码
for item in list; do
  echo "$item"
done

while 循环

bash 复制代码
while [ condition ]; do
  # do something
done

9. 常见陷阱

常见错误 说明
[ $var == "test" ] 变量未加引号,空格会报错
[ $var ] 变量为空时可能出问题,用 [ -n "$var" ]
if ($var) 错误!Shell 不是这样写
a = 1 错误!等号两边不能有空格

10. 脚本中的实用模式

检查参数数量

bash 复制代码
if [ -z "$1" ]; then
  echo "Usage: $0 <arg>"
  exit 1
fi

检查文件/目录

bash 复制代码
if [ ! -f "$CONFIG_FILE" ]; then
  echo "Error: Config file not found"
  exit 1
fi

后台运行

bash 复制代码
nohup command > output.log 2>&1 &
# 2>&1 表示将错误重定向到标准输出

这就是这个脚本中涉及的主要 Shell 语法!核心就是:变量用 $、条件用 [ ]、数组用 @、结尾用 fi。掌握这些就能读懂大部分 Bash 脚本了。

详细解释 nohup command > output.log 2>&1 &

这个命令是 Linux 中典型的后台运行+日志重定向组合,逐个拆解:


整体结构

复制代码
nohup command > output.log 2>&1 &
└─────┘  └──────┘  └───────┘  └─┘└─┘
   │        │         │        │  │ │
   │        │         │        │  │ └── 后台运行符
   │        │         │        │  └──── 标准错误重定向到标准输出
   │        │         │        └────── 输出重定向(覆盖模式)
   │        │         └────────────── 忽略 SIGHUP 信号
   │        └──────────────────────── 要运行的命令
   └─────────────────────────────── 防止终端关闭时进程被终止

逐个解析

1. nohup --- 防止终端关闭时进程被杀死

bash 复制代码
# 不使用 nohup:关闭终端时进程会被终止
python script.py

# 使用 nohup:终端关闭后进程继续运行
nohup python script.py

原理 :nohup 会忽略 SIGHUP 信号(当终端退出时发送给前台进程组的信号)


2. command --- 你要运行的命令

bash 复制代码
# 可以是任意命令
python script.py
./run.sh
npm start

3. > output.log --- 标准输出重定向

符号 含义
> 覆盖写入(先清空文件再写入)
>> 追加写入(追加到文件末尾)
bash 复制代码
# command 的 stdout(标准输出)写入到 output.log
command > output.log

等价于 Python:

python 复制代码
with open('output.log', 'w') as f:
    subprocess.run(command, stdout=f)

4. 2>&1 --- 标准错误重定向到标准输出

这是最让人困惑的部分!需要理解文件描述符:

文件描述符 名称 默认目标
0 标准输入 (stdin) 键盘
1 标准输出 (stdout) 终端
2 标准错误 (stderr) 终端
bash 复制代码
# 2 > &1 的意思是:将 fd=2(stderr)重定向到 fd=1(stdout)指向的位置
2>&1

为什么顺序很重要?

bash 复制代码
# ✅ 正确:先写 > output.log,再写 2>&1
command > output.log 2>&1

# ❌ 错误:顺序颠倒
command 2>&1 > output.log   # stderr 仍会输出到终端!

原因2>&1 执行时,&1 指向的是当时的 stdout 状态。如果先执行 2>&1,那时 stdout 还是终端,所以 stderr 还是会输出到终端。


5. & --- 后台运行

bash 复制代码
# 不带 &:前台运行,命令结束前无法操作终端
command

# 带 &:后台运行,可以继续输入其他命令
command &

完整示例对比

bash 复制代码
# 1. 最简形式(输出到终端,可能被终端关闭杀死)
python script.py

# 2. 后台运行,但输出还在终端
python script.py &

# 3. 后台运行 + 日志文件(进程会被终端杀死)
python script.py > output.log &

# 4. 完整形式:后台运行 + 日志 + 忽略终端关闭
nohup python script.py > output.log 2>&1 &

在脚本中的实际应用

脚本中的这行代码:

bash 复制代码
nohup "${COMMAND_ARRAY[@]}" > "${TASK_DIR}/run.log" 2>&1 &

PID=$!
echo "$PID" > "$PID_FILE"

等价于 Python 的:

bash 复制代码
# Python 等价(简化版)
import subprocess
import os

process = subprocess.Popen(
    command_array,
    stdout=open(f"{task_dir}/run.log", "w"),
    stderr=subprocess.STDOUT,  # stderr 重定向到 stdout
    start_new_session=True     # 相当于 nohup + &
)

with open(pid_file, "w") as f:
    f.write(str(process.pid))

常见变体

bash 复制代码
# 丢弃所有输出(类似 /dev/null)
command > /dev/null 2>&1 &

# 同时记录日志和实时输出(使用 tee)
command 2>&1 | tee output.log &

# 只记录错误日志
command 2> error.log &

相关推荐
踩着两条虫3 小时前
如何评价VTJ.PRO?
前端·架构·ai编程
稳场孤王4 小时前
英语语法学习资料整理
语法
Mh4 小时前
鼠标跟随倾斜动效
前端·css·vue.js
小码哥_常5 小时前
Kotlin类型魔法:Any、Unit、Nothing 深度探秘
前端
Web极客码6 小时前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风7 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap7 小时前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫7 小时前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054737 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json