Dash脚本入门:与Bash的区别及兼容性写法
您是否有过这样的经历:“写好的shell脚本在自己的电脑上能运行,但在服务器上却运行不了……”?这很可能是由于Dash和Bash之间的差异造成的。
本文将解说在许多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开头的脚本,体验一下脚本随处可运行的安心感吧!