Bash vs. Dash: Which Should You Use for Shell Scripting? A Comparison and Guide
When you're managing or developing a website, you often want to automate small, routine tasks. That's where "shell scripts" come in handy. But when you start writing one, have you ever looked at the magic-like incantation on the first line, like #!/bin/bash or #!/bin/sh, and wondered, "What's the difference?"
This difference is actually incredibly important. It's essential knowledge, especially to avoid the tragedy of "It worked perfectly on my computer, but it throws an error on the server!" In this article, we'll break down the differences between two common shells, Bash and Dash, in a way that's easy for beginners to understand, complete with code you can copy and paste to try for yourself. After reading this, you'll be able to write robust shell scripts that work in any environment!
So, What Are Bash and Dash Anyway?
First, let's introduce our two main characters (the two shells). Getting to know their strengths and personalities is the first step to getting along with them.
Bash (Bourne-Again SHell) - Everyone's Pal, the Feature-Rich Shell
Bash is the default "login shell" on many Linux systems and older versions of macOS. It's the shell that starts up when we open a terminal. Many of the convenient features we use without thinking—scrolling through command history with the arrow keys, auto-completing commands and filenames with the Tab key, using colorful prompts—are provided by Bash. It's a high-functioning, friendly shell that's extremely well-suited for direct human interaction.
Dash (Debian Almquist SHell) - The Unsung Hero, the Fast and Lightweight Shell
Dash, on the other hand, is known for being extremely lightweight and fast. It's stripped of extra features, making it specialized for executing scripts quickly. Because of this, it's used as the default shell for system startup scripts and for scripts specified with `#!/bin/sh` on Debian-based operating systems like Ubuntu. In other words, even if we don't interact with it directly very often, Dash is working hard behind the scenes of the system.
The Biggest Difference: POSIX Compliance as "Dialect" vs. "Standard Language"
The biggest difference between Bash and Dash lies in how strictly they adhere to a standard called POSIX.
That technical term might be a bit confusing at first. Simply put, POSIX is like the "standard language of the shell script world." Scripts written in this standard language are guaranteed to run the same way in any environment (OS).
- Dash: Is very faithful to the POSIX "standard language." It hardly speaks any dialects.
- Bash: Can speak the "standard language," but also knows many convenient "dialects" (Bash-specific extensions).
The handy features we use in the terminal every day are often Bash "dialects." On many systems, /bin/sh points to a shell that only understands the "standard language" (like Dash). This is the main reason for the "works on my PC (Bash) but not on the server (/bin/sh)" phenomenon.
Writing #!/bin/bash on the first line of your script is like declaring, "This script will use Bash dialects!" allowing you to use Bash's convenient features. Conversely, writing #!/bin/sh is like requesting, "Please use the standard language!" If you want to increase portability and compatibility, the standard practice is to use #!/bin/sh and be mindful of writing POSIX-compliant code.
【Hands-on】Code Comparison: How Do Bash and Dash Differ in Syntax?
From here, let's look at specific code examples to compare Bash's "dialect" with the POSIX-compliant "standard language" that also works in Dash. To experience it "working," please copy and run these in your terminal!
1. Handling Arrays
Arrays are used when you want to handle multiple values together. In Bash, this is intuitive, but the POSIX standard has no specification for arrays. Therefore, doing the same thing in Dash requires a bit of a workaround.
The Bash Way (Dialect)
You can easily define an array using () and access elements using an index like ${my_array[1]}.
#!/bin/bash
# Define an array
fruits=("apple" "banana" "cherry")
# Echo the second element (index starts at 0)
echo ${fruits[1]}
Result:
banana
The Dash-Compatible Way (Standard Language)
When writing in a POSIX-compliant way, you use a space-separated string or the shell's positional parameters ($1, $2...) instead of an array. Here, we'll show how to use a for loop with a string. Setting the values and processing them in a for loop is a common pattern.
#!/bin/sh
# Define as a space-separated string
fruits="apple banana cherry"
# Use a for loop to output each element in order
for fruit in $fruits; do
echo "I like $fruit!"
done
Result:
I like apple!
I like banana!
I like cherry!
2. Conditional Statements
Conditional branches like "if X, then do Y" are a fundamental part of scripting. There's a big difference here, too.
The Bash Way (Dialect)
Bash uses a more advanced test command, [[ ... ]]. It's very convenient, allowing you to connect multiple conditions with &&, perform string comparison with ==, and even do pattern matching.
#!/bin/bash
name="Taro"
age=25
# Connect two conditions with &&
if [[ "$name" == "Taro" && "$age" -gt 20 ]]; then
echo "Welcome, Taro!"
fi
Result:
Welcome, Taro!
The Dash-Compatible Way (Standard Language)
The POSIX-compliant test command is [ ... ]. This is an older command with some limitations. For example, string comparison uses a single =, and multiple conditions must use -a (AND) or -o (OR), or be combined using multiple sets of [ ]. For safety, combining multiple [ ] is recommended.
#!/bin/sh
name="Taro"
age=25
# Write the condition using two sets of [ ]
if [ "$name" = "Taro" ] && [ "$age" -gt 20 ]; then
echo "Welcome, Taro!"
fi
Result (Same):
Welcome, Taro!
3. String Substitution
Replacing a specific character in a variable with another is also a common task.
The Bash Way (Dialect)
In Bash, you can easily substitute strings with the syntax ${variable/old/new}.
#!/bin/bash
filename="photo_2025.jpg"
# Replace "jpg" with "png"
new_filename=${filename/jpg/png}
echo $new_filename
Result:
photo_2025.png
The Dash-Compatible Way (Standard Language)
When writing POSIX-compliant scripts, it's common to combine external commands like sed or awk for string manipulation. Here's an example using sed. It's a bit longer, but it gives you the peace of mind that it will work reliably in any environment.
#!/bin/sh
filename="photo_2025.jpg"
# Pipe the variable's content to the sed command for substitution
new_filename=$(echo "$filename" | sed 's/jpg/png/')
echo "$new_filename"
Result (Same):
photo_2025.png
Conclusion: So, Which One Should You Use?
We've looked at the differences, but the question you really want answered is, "So, which one should I use?" The answer is "it depends on the situation," but there is a recommended guideline for beginners.
Recommended Choice by Use Case
-
For quick tasks in your terminal 👉 Use Bash without hesitation!
In your everyday shell, you should take full advantage of convenient features like completion and history. There's no need to use a more cumbersome syntax. -
For scripts that will run on a server or be distributed to others 👉 Strongly consider Dash (POSIX compliance)!
If the script needs to run in more than just your own environment, compatibility is the most important factor. Use#!/bin/shand make an effort to write in the POSIX "standard language." This greatly increases the chances that your script will continue to work on Linux, BSD, and even future systems.
Advice for Beginners
If you're just starting to learn shell scripting, we recommend learning to write in a POSIX-compliant way with `#!/bin/sh` from the beginning. Why? Because the more compatible version covers the less compatible one. A POSIX-compliant script will run fine in Bash, but the reverse is not true. If you master the "standard language" first, you won't run into trouble no matter what environment you find yourself in.
Watch Out! Common Bash "Dialects" (Bashisms) You Might Use by Accident
It's a common story: you write code that seems perfectly fine, only to find out it was a Bash "dialect." Here are a few "Bashisms" that are particularly easy to use by mistake.
[[ ... ]]: As mentioned earlier, this is the most classic Bashism. Get into the habit of writing conditional branches with[ ... ].function my_func { ... }: Thefunctionkeyword for defining functions is a Bash feature. The POSIX-compliant way ismy_func() { ... }.echo -e "...": The-eoption, which interprets escape sequences like\n(newline), doesn't behave the same way in all shells. Using theprintfcommand instead is far safer and more reliable.source ./my_script.sh: Thesourcecommand for loading another script is also a Bashism. The POSIX-compliant version is. ./my_script.sh(a dot followed by a space).
Just by avoiding these Bashisms, you will dramatically improve your script's compatibility.
Summary: Master the Convenience of Bash and the Robustness of Dash!
In this article, we've explored how to write highly compatible shell scripts by looking at the differences between Bash and Dash.
- For everyday terminal use, stick with Bash and its feature-packed convenience.
- When writing scripts, be mindful of Dash (POSIX compliance) so they can run in any environment.
- When in doubt, the best practice is to write
#!/bin/shon the first line of your script and practice writing in the POSIX "standard language."
By adopting this mindset, you can prevent "it doesn't work because of the environment" troubles and write scripts that are useful in a wider range of situations. So, why not start aiming for "scripts that run anywhere" today?
Check out this article next
Dash Script Examples (if statements, loops, simple functions)