Bash vs Dash:编写Shell脚本该用哪个?比较与选择指南
在运营和开发网站时,我们总想把一些固定的小任务自动化,对吧?这时候大显身手的就是“Shell脚本”。但是,一旦开始编写,你是否曾对文件第一行像咒语一样的#!/bin/bash或#!/bin/sh产生过“这到底有什么区别?”的疑问呢?
实际上,这个区别非常重要。特别是为了避免“在自己电脑上运行得好好的,一放到服务器上就报错!”这样的悲剧,这些知识是必不可少的。本文将为初学者通俗易懂地讲解两种常用Shell——Bash和Dash的区别,并提供可以复制粘贴即可尝试的代码,进行彻底解说。读完本文,你也能写出不受环境影响的、健壮的Shell脚本!
Bash和Dash,它们到底是什么?
首先,我们来介绍一下两位主角(两种Shell)。了解它们各自擅长的事情和性格,是与它们“交朋友”的第一步。
Bash (Bourne-Again SHell) - 大家的好伙伴,功能丰富的Shell
在许多Linux系统和旧版macOS中,Bash被用作我们打开终端时首先启动的“登录Shell”。用方向键翻阅命令历史、用Tab键补全命令和文件名、使用彩色显示……我们平时不经意间使用的许多便利功能,大多都是由Bash提供的。可以说,它是一种非常适合人类直接操作(交互式使用)的、功能强大且友好的Shell。
Dash (Debian Almquist SHell) - 幕后英雄,高速轻量的Shell
另一方面,Dash的特点就是极致的轻量和高速。因为它削减了多余的功能,所以特别擅长快速执行脚本。正因为这个特点,在Ubuntu等基于Debian的操作系统中,它被用作系统启动脚本和指定#!/bin/sh时的默认Shell。也就是说,即使我们直接接触它的机会不多,但在系统底层,Dash正在努力地工作着。
最大的分水岭:作为“方言”与“普通话”的POSIX标准
Bash和Dash最大的区别在于它们对POSIX这个标准的遵守程度。
突然出现的专业术语可能会让你有些困惑。简单来说,POSIX就像是“Shell脚本界的普通话”。用这种普通话编写的脚本,可以保证在任何环境(操作系统)下都能以相同的方式运行。
- Dash: 非常忠实于POSIX这个“普通话”,几乎不会说“方言”。
- Bash: 既会说“普通话”,也知道很多方便的“方言”(Bash独有的扩展功能)。
我们平时在终端中使用的便利功能,很多时候都是Bash的“方言”。而在许多系统中,/bin/sh指向的是“只懂普通话的Shell”(如Dash)。这就是“在我的PC(Bash)上能运行,但在服务器(/bin/sh)上就运行不了”现象的主要原因。
在脚本的第一行写上#!/bin/bash,就相当于声明:“这个脚本要用Bash的方言哦!”,这样就可以使用Bash的便利功能了。相反,写#!/bin/sh则相当于声明:“请用普通话!”,如果想提高可移植性和兼容性,使用#!/bin/sh并有意识地采用POSIX兼容的写法,才是上策。
【实践】代码比较!Bash和Dash的写法有何不同?
从这里开始,我们通过具体的代码来比较一下Bash的“方言”和在Dash中也能运行的POSIX兼容“普通话”写法。为了体验“能运行”的感觉,请务必复制到你的终端中执行一下!
1. 数组的处理
当想一次性处理多个值时,我们会使用数组。在Bash中可以直观地编写,但POSIX标准中没有数组的规范。因此,要在Dash中做同样的事情,需要一些技巧。
Bash的写法(方言)
可以使用()轻松定义数组,并像${my_array[1]}这样通过指定索引来获取元素。
#!/bin/bash
# 定义数组
fruits=("apple" "banana" "cherry")
# 输出第二个元素(索引从0开始)
echo ${fruits[1]}
执行结果:
banana
在Dash中也能运行的写法(普通话)
在编写POSIX兼容代码时,通常使用空格分隔的字符串或Shell的位置参数($1, $2...)来代替数组。这里介绍使用for循环处理字符串的方法,这是一种常见的模式。
#!/bin/sh
# 定义为空格分隔的字符串
fruits="apple banana cherry"
# 使用for循环依次输出所有元素
for fruit in $fruits; do
echo "I like $fruit!"
done
执行结果:
I like apple!
I like banana!
I like cherry!
2. 条件分支的写法
“如果XX,则YY”这样的条件分支是脚本的基础。这里也存在很大的差异。
Bash的写法(方言)
在Bash中,可以使用功能更强大的测试命令[[ ... ]]。它非常方便,可以用&&连接多个条件,用==进行字符串比较,甚至可以进行模式匹配。
#!/bin/bash
name="Taro"
age=25
# 用&&连接两个条件
if [[ "$name" == "Taro" && "$age" -gt 20 ]]; then
echo "Welcome, Taro!"
fi
执行结果:
Welcome, Taro!
在Dash中也能运行的写法(普通话)
POSIX兼容的测试命令是[ ... ]。这是一个很早就有的命令,存在一些限制。例如,字符串比较使用单个=,多个条件需要使用-a (AND) 或-o (OR),或者组合使用多个[ ]。为了安全起见,推荐组合使用多个[ ]。
#!/bin/sh
name="Taro"
age=25
# 使用两个 [ ] 来描述条件
if [ "$name" = "Taro" ] && [ "$age" -gt 20 ]; then
echo "Welcome, Taro!"
fi
执行结果 (相同):
Welcome, Taro!
3. 字符串替换
希望将变量中的特定字符替换为其他字符,这种情况也很常见。
Bash的写法(方言)
在Bash中,可以使用${变量/替换前/替换后}这种写法轻松替换字符串。
#!/bin/bash
filename="photo_2025.jpg"
# 将"jpg"替换为"png"
new_filename=${filename/jpg/png}
echo $new_filename
执行结果:
photo_2025.png
在Dash中也能运行的写法(普通话)
在编写POSIX兼容代码时,通常会组合使用sed或awk等外部命令来进行字符串操作。这里介绍一个使用sed的例子。虽然代码会稍长一些,但能确保在任何环境下都能稳定运行,让人放心。
#!/bin/sh
filename="photo_2025.jpg"
# 用echo将变量内容传递给sed命令,进行替换处理
new_filename=$(echo "$filename" | sed 's/jpg/png/')
echo "$new_filename"
执行结果 (相同):
photo_2025.png
结论:到底该用哪个?
看了这么多区别,大家最想知道的肯定是“那我到底该用哪个?”吧。答案是“视情况而定”,但对于初学者,我们有推荐的指导方针。
根据不同用例的推荐选择
-
想在终端里轻松作业时 👉 毫不犹豫选Bash!
在日常使用的Shell中,应该充分利用补全、历史记录等便利功能。没必要特意用不方便的写法。 -
想编写在服务器上运行或分发给他人的脚本时 👉 强烈建议考虑Dash(POSIX兼容)!
如果脚本不只是在你自己的环境下运行,那么兼容性就是最重要的。使用#!/bin/sh,并努力用POSIX的“普通话”来编写。这样可以大大提高脚本在Linux、BSD乃至未来新系统中继续运行的可能性。
给初学者的建议
如果你刚开始学习Shell脚本,我们建议你从一开始就使用`#!/bin/sh`,学习POSIX兼容的写法。为什么呢?因为“大的能兼容小的”。用POSIX兼容方式写的脚本在Bash中也能正常运行,但反之则不然。一开始就掌握“普通话”,那么无论到什么环境都不会遇到困难。
注意!那些容易不小心使用的Bash“方言”(Bashism)
本以为是好心写出的代码,结果却是Bash的“方言”……这种情况很常见。这里介绍几个特别容易犯的“Bashism(Bash主义)”。
[[ ... ]]: 前面也提到了,这是最典型的Bashism。养成用[ ... ]写条件分支的习惯吧。function my_func { ... }: 定义函数时使用的function关键字是Bash特有的。POSIX兼容的写法是my_func() { ... }。echo -e "...": 能够解释\n(换行)等转义序列的-e选项,其行为在所有Shell中并不一致。使用printf命令会安全可靠得多。source ./my_script.sh: 用于加载另一个脚本的source命令也是Bashism。POSIX兼容的写法是. ./my_script.sh(点和空格)。
只要避免这些Bashism,你的脚本的兼容性就会得到极大的提升。
总结:灵活运用Bash的便利与Dash的稳健!
这次,我们通过Bash和Dash的区别,解说了如何编写高兼容性的Shell脚本。
- 平时在终端中使用功能丰富的Bash。
- 编写脚本时,要考虑到Dash(POSIX兼容),以确保在任何环境下都能运行。
- 如果犹豫不决,最佳实践是在脚本的第一行写上
#!/bin/sh,并练习用POSIX的“普通话”来编写。
掌握了这种思维方式,就能防范“因环境问题无法运行”的麻烦,写出在更多场合都有用的脚本。来吧,从今天起,你也来挑战一下编写“在哪里都能运行的Shell脚本”吧!