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 &

相关推荐
拉拉肥_King15 小时前
pc端视频压缩:FFmpeg.wasm 实战指南
前端
0x8615 小时前
基于 Dio 实现 SSE 流式通信
前端
ZC跨境爬虫15 小时前
跟着 MDN 学 HTML day_40:(DOMImplementation 接口完全解析)
前端·ui·html·媒体
Highcharts.js15 小时前
Highcharts 纯 JavaScript 图表库深度使用评测
开发语言·前端·javascript·功能测试·ecmascript·highcharts·技术评测
码码哈哈0.015 小时前
基于 RSA 非对称加密与挑战码机制的前端登录安全方案
前端·安全·状态模式
ZC跨境爬虫15 小时前
跟着 MDN 学 HTML day_39:(DOMException 异常接口完全解析)
前端·javascript·html·媒体
渐儿16 小时前
NestJS 教程 Part 2 — 数据层、API 设计与业务异步
前端
渐儿16 小时前
Next.js 教程 Part 2 — 数据获取、Server Actions 与状态
前端
用户1257585243616 小时前
XYGo Admin ArtTable 表格组件:一行代码搞定加载、刷新与分页
前端