🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

Dash脚本入门:与Bash的区别及兼容性写法

您是否有过这样的经历:“写好的shell脚本在自己的电脑上能运行,但在服务器上却运行不了……”?这很可能是由于DashBash之间的差异造成的。

本文将解说在许多Linux系统(特别是Debian和Ubuntu)中作为标准shell(/bin/sh)使用的Dash的基本写法。我们准备了丰富的具体代码示例,即使是初学者也能通过复制粘贴来体验“代码成功运行”的乐趣。本文还总结了与Bash的兼容性注意事项,帮助您掌握编写更健壮、更具移植性的脚本的技能。


Dash到底是什么?和Bash有什么不同?

我们平时不经意间所说的“shell脚本”,多数情况下指的是Bash (Bourne Again SHell)。Bash功能非常强大,有许多方便的写法。

Dash (Debian Almquist SHell) 则是一个遵循POSIX标准的、更简单、更轻量的shell。由于其运行速度非常快,在Debian、Ubuntu等操作系统中,它被用作系统启动脚本和标准shell(/bin/sh)。

也就是说,以#!/bin/sh开头的脚本,在您的电脑上(Bash环境)可能可以运行,但在服务器上(Dash环境)则可能会报错。要避免这种情况,最快的途径就是学习Dash也能解析的、遵循POSIX标准的写法


Dash脚本的基本写法【可直接复制粘贴】

首先,让我们来接触一下Dash的基本语法。所有代码都以#!/bin/sh开头,所以可以直接保存为文件并执行。

1. Hello World! - 输出字符串

惯例的“Hello World”。我们使用echo命令来输出字符串。这一点和Bash完全一样。

#!/bin/sh
# 脚本开头要写明用哪个shell来执行,这就是'shebang'

echo "Hello, Dash World!"

2. 变量的使用方法

定义变量时,规则是=左右不加空格。使用时,在变量名前加上$符号。这一点也和Bash一样。

#!/bin/sh

GREETING="你好"
TARGET="世界"

echo "${GREETING}, ${TARGET}!" # 为了明确变量名,推荐用{}将变量名括起来

3. 使用if语句进行条件分支

我们使用if语句进行条件分支。条件表达式用[ ... ]括起来。这是第一个需要注意的地方,Bash中常见的[[ ... ]]在Dash中是不能使用的(后面会详细解说)。

#!/bin/sh

USER_NAME="Alice"

# [ ] 的前后必须有空格
if [ "$USER_NAME" = "Alice" ]; then
  echo "欢迎,Alice。"
else
  echo "你是谁?"
fi

4. 使用for循环进行重复操作

让我们用一个以空格分隔的列表来写一个简单的for循环。

#!/bin/sh

# 循环处理以空格分隔的字符串
for fruit in apple banana cherry
do
  echo "我喜欢 ${fruit}."
done

【最重要】与Bash兼容性方面需要注意的地方

接下来是正题。在Bash中理所当然可以使用的便利功能,在Dash中有时会报错。让我们来看一些代表性的例子。

区别1:数组 (Array)

Dash不支持数组。 这是一个非常大的区别。

Bash的写法(在Dash中无法运行)

#!/bin/sh
# 这段代码在Dash中会报错!
fruits=("apple" "banana" "cherry")
echo "第一个水果是 ${fruits[0]}。"

Dash中的替代方案
使用以空格分隔的字符串或参数列表($@)来实现类似的处理。

#!/bin/sh
# 遵循POSIX标准的循环处理
fruit_list="apple banana cherry"

for fruit in $fruit_list
do
  echo "正在处理的水果: $fruit"
done

区别2:条件表达式 [[ ... ]] vs [ ... ]

Dash无法解析[[ ... ]] 条件表达式必须使用[ ... ](即test命令的语法糖)。

Bash的写法(在Dash中无法运行)
[[可以方便地进行字符串匹配和使用逻辑运算符(&&, ||),但它是Bash独有的扩展功能。

#!/bin/sh
# 这段代码在Dash中会报错!
COUNT=10
if [[ "$USER" = "root" && $COUNT -gt 5 ]]; then
  echo "条件匹配。"
fi

Dash的写法 (遵循POSIX标准)
使用[ ... ],逻辑与用-a表示,逻辑或用-o表示。

#!/bin/sh
# 注意兼容性的写法
COUNT=10
# 组合多个条件时使用 -a (AND) 或 -o (OR)
if [ "$USER" = "root" -a $COUNT -gt 5 ]; then
  echo "条件匹配。"
fi

区别3:函数声明的function关键字

在Dash中不能使用function关键字。 函数以函数名() { ... }的形式声明。

Bash的写法(在Dash中无法运行)

#!/bin/sh
# 这段代码在Dash中会报错!
function say_hello() {
  echo "Hello from function!"
}

say_hello

Dash的写法 (遵循POSIX标准)
这是自古以来就有的标准写法,当然在Bash中也能运行。

#!/bin/sh
# 遵循POSIX标准的函数声明
say_hello() {
  echo "你好!从函数中调用。"
}

# 函数的调用
say_hello

区别4:进程替换 <()

能将命令的执行结果像文件一样处理的进程替换<()是Bash的功能。

Bash的写法(在Dash中无法运行)

#!/bin/sh
# 这段代码在Dash中会报错!
# comm命令比较两个已排序的文件
comm <(ls -1 dir1) <(ls -1 dir2)

Dash中的替代方案
先保存到临时文件中,或者巧妙地使用管道来处理。

#!/bin/sh
# 使用临时文件的方法
TMP1="/tmp/dir1_list"
TMP2="/tmp/dir2_list"

ls -1 dir1 > "$TMP1"
ls -1 dir2 > "$TMP2"

comm "$TMP1" "$TMP2"

# 不要忘记清理
rm "$TMP1" "$TMP2"

区别5:花括号展开 {1..10}

能生成连续数值或字符串的花括号展开是Bash的功能。

Bash的写法(在Dash中无法运行)

#!/bin/sh
# 这段代码在Dash中会直接输出字符串 {1..5}
echo "创建文件file{1..5}.txt"
touch file_{1..5}.txt

Dash中的替代方案
要生成连续数字,我们使用传统的seq命令。

#!/bin/sh
# 组合seq命令和for循环
echo "创建从1到5的连续文件"
for i in $(seq 1 5)
do
  touch "file_${i}.txt"
  echo "已创建第${i}个文件。"
done

应用示例:用Dash编写实用脚本

最后,让我们活用至今所学的知识,来编写一个稍微实用一点的脚本。这是一个将当前目录下的.log文件统一移动到带日期的备份目录中的脚本。当然,它是与Dash兼容的。

#!/bin/sh

# 获取YYYY-MM-DD格式的今天日期
# date命令的格式指定在POSIX中是标准化的,因此是安全的
BACKUP_DIR="backup_$(date +%Y-%m-%d)"

# 如果备份目录不存在则创建
if [ ! -d "$BACKUP_DIR" ]; then
  echo "创建备份目录 ${BACKUP_DIR}。"
  mkdir "$BACKUP_DIR"
fi

# 循环处理.log文件
# *.log 的通配符展开是shell自带的标准功能
for file in *.log
do
  # 确认文件存在且为普通文件
  if [ -f "$file" ]; then
    echo "正在移动 ${file} 到 ${BACKUP_DIR}/。"
    mv "$file" "$BACKUP_DIR/"
  fi
done

echo "备份完成。"

总结

注意Dash和Bash之间的差异,并用心编写遵循POSIX标准的写法,可以极大地提高脚本的兼容性。刚开始可能会觉得有些不便,但多花这点功夫可以防止因环境差异导致的意外错误,让您向“能干的程序员”更近一步。

这次介绍的内容虽然基础,但非常重要。请一定在您的环境中尝试运行以#!/bin/sh开头的脚本,体验一下脚本随处可运行的安心感吧!


相关文章

Bash vs Dash:写shell脚本该用哪个?比较与选择