Intro to Dash Scripting: Differences from Bash and How to Write for Compatibility
Ever had that experience where you write a shell script, it works on your PC, but fails on the server? That might be because of the differences between Dash and Bash.
In this article, we'll explain the basic syntax of Dash, the actual shell behind /bin/sh on many Linux systems (especially Debian and Ubuntu). We've packed it with concrete code examples so even beginners can copy, paste, and experience it 'just working'. We also cover the key points of compatibility with Bash, helping you gain the skills to write more robust and portable scripts.
So, What is Dash Anyway? How Is It Different from Bash?
What we casually call a "shell script" often refers to Bash (Bourne Again SHell). Bash is extremely powerful and offers many convenient features.
On the other hand, Dash (Debian Almquist SHell) is a simpler, more lightweight shell that complies with the POSIX standard. Because it's very fast, it's used as the system's startup script shell and the default system shell (/bin/sh) in operating systems like Debian and Ubuntu.
This means a script starting with #!/bin/sh might run on your PC (a Bash environment) but could error out on a server (a Dash environment). The best way to prevent this is to learn the POSIX-compliant syntax that Dash can also interpret.
Basic Dash Scripting Syntax [Copy & Paste OK]
First, let's get familiar with the basic grammar of Dash. Every code snippet starts with #!/bin/sh, so you can save them directly into a file and run them.
1. Hello World! - Printing a String
The obligatory "Hello World". We use the echo command to print strings. This is exactly the same as in Bash.
#!/bin/sh
# At the top of the script, we write a 'shebang' to specify which shell to run it with
echo "Hello, Dash World!"
2. Using Variables
The rule is not to put spaces around the = sign when defining a variable. When you use it, you prefix the variable name with a $. This is also the same as in Bash.
#!/bin/sh
GREETING="Hello"
TARGET="World"
echo "${GREETING}, ${TARGET}!" # It's recommended to enclose variable names in {} for clarity
3. Conditional Branching with if Statements
We use if statements for conditional branching. The conditional expression is enclosed in [ ... ]. This is the first key point to watch out for, as the [[ ... ]] often seen in Bash cannot be used in Dash (we'll explain this in detail later).
#!/bin/sh
USER_NAME="Alice"
# Spaces are required before and after the [ ]
if [ "$USER_NAME" = "Alice" ]; then
echo "Welcome, Alice."
else
echo "Who are you?"
fi
4. Repetition with for Loops
Let's write a simple for loop using a space-separated list.
#!/bin/sh
# Process a space-separated string with a loop
for fruit in apple banana cherry
do
echo "I like ${fruit}."
done
[Most Important] Points to Watch for Compatibility with Bash
This is where it gets serious. Convenient features that are standard in Bash can cause errors in Dash. Let's look at some common examples.
Difference 1: Arrays
Dash does not support arrays. This is a very significant difference.
β The Bash Way (Doesn't work in Dash)
#!/bin/sh
# This code will cause an error in Dash!
fruits=("apple" "banana" "cherry")
echo "The first fruit is ${fruits[0]}."
β Workaround in Dash
We can achieve similar functionality using space-separated strings or the argument list ($@).
#!/bin/sh
# POSIX-compliant loop processing
fruit_list="apple banana cherry"
for fruit in $fruit_list
do
echo "Processing fruit: $fruit"
done
Difference 2: Conditional Expressions [[ ... ]] vs [ ... ]
Dash cannot interpret [[ ... ]]. Always use [ ... ] (which is syntactic sugar for the test command) for conditional expressions.
β The Bash Way (Doesn't work in Dash)[[ is convenient for string matching and logical operators (&&, ||), but it's a Bash-specific extension.
#!/bin/sh
# This code will cause an error in Dash!
COUNT=10
if [[ "$USER" = "root" && $COUNT -gt 5 ]]; then
echo "Condition met."
fi
β The Dash Way (POSIX-compliant)
Use [ ... ] and represent logical AND with -a and logical OR with -o.
#!/bin/sh
# Compatibility-conscious syntax
COUNT=10
# Use -a (AND) or -o (OR) to combine multiple conditions
if [ "$USER" = "root" -a $COUNT -gt 5 ]; then
echo "Condition met."
fi
Difference 3: The function Keyword
The function keyword cannot be used in Dash. Declare functions in the format function_name() { ... }.
β The Bash Way (Doesn't work in Dash)
#!/bin/sh
# This code will cause an error in Dash!
function say_hello() {
echo "Hello from function!"
}
say_hello
β The Dash Way (POSIX-compliant)
This is the older, standard way of writing it, and of course, it also works in Bash.
#!/bin/sh
# POSIX-compliant function declaration
say_hello() {
echo "Hello! Called from a function."
}
# Calling the function
say_hello
Difference 4: Process Substitution <()
Process substitution <(), which lets you treat a command's output like a file, is a Bash feature.
β The Bash Way (Doesn't work in Dash)
#!/bin/sh
# This code will cause an error in Dash!
# The comm command compares two sorted files
comm <(ls -1 dir1) <(ls -1 dir2)
β Workaround in Dash
Save the output to a temporary file first, or handle it using a clever pipeline.
#!/bin/sh
# Method using temporary files
TMP1="/tmp/dir1_list"
TMP2="/tmp/dir2_list"
ls -1 dir1 > "$TMP1"
ls -1 dir2 > "$TMP2"
comm "$TMP1" "$TMP2"
# Don't forget to clean up
rm "$TMP1" "$TMP2"
Difference 5: Brace Expansion {1..10}
Brace expansion, for generating sequential numbers or strings, is a Bash feature.
β The Bash Way (Doesn't work in Dash)
#!/bin/sh
# In Dash, this code will literally output the string {1..5}
echo "Creating files file{1..5}.txt"
touch file_{1..5}.txt
β Workaround in Dash
For generating a sequence of numbers, let's use the classic seq command.
#!/bin/sh
# Combine the seq command with a for loop
echo "Creating sequential files from 1 to 5"
for i in $(seq 1 5)
do
touch "file_${i}.txt"
echo "Created file number ${i}."
done
Applied Example: A Practical Script in Dash
Finally, let's use what we've learned to write a slightly more practical script. This script moves all .log files in the current directory to a backup directory named with today's date. And of course, it's Dash-compatible.
#!/bin/sh
# Get today's date in YYYY-MM-DD format
# The date command's format specifiers are standardized in POSIX, so this is safe
BACKUP_DIR="backup_$(date +%Y-%m-%d)"
# Create the backup directory if it doesn't exist
if [ ! -d "$BACKUP_DIR" ]; then
echo "Creating backup directory ${BACKUP_DIR}."
mkdir "$BACKUP_DIR"
fi
# Loop through .log files
# Wildcard expansion for *.log is a standard shell feature
for file in *.log
do
# Check if the file exists and is a regular file
if [ -f "$file" ]; then
echo "Moving ${file} to ${BACKUP_DIR}/."
mv "$file" "$BACKUP_DIR/"
fi
done
echo "Backup complete."
Summary
By being aware of the differences between Dash and Bash and making an effort to write POSIX-compliant code, the portability of your scripts will dramatically improve. It might feel a bit inconvenient at first, but this extra step will prevent unexpected errors due to environment differences and bring you one step closer to being a "capable programmer".
The topics covered here are basic but extremely important. Please try running a script with #!/bin/sh in your own environment. And experience the peace of mind that comes from knowing your script will run anywhere!