Bash Scripts: Variables & Quoting
A Bash script is a text file full of commands that Bash runs one after another, exactly as if you had typed them. Anything you repeat, such as a backup, a deployment or a daily report, can become a script, so it runs the same way every time, even when you are tired or someone else runs it.
This lesson starts the scripting section of the course. You will write and run a script, then learn the building blocks: variables, the quoting rules that trip up almost everyone, command substitution, arithmetic, arrays and Bash's handy parameter tricks. All examples run in Bash 5.3.
Your first script
Create a file called hello.sh with nano:
#!/usr/bin/env bash
# hello.sh: my first script
name="Priya"
city="Jaipur"
echo "Namaste, $name from $city!"
echo "Today is $(date +%A). Logged in as $(whoami)."The first line is the shebang: #! followed by the program that should run the file. #!/usr/bin/env bash finds bash through PATH, which works on more systems; #!/bin/bash is also very common on Linux. Lines starting with # are comments, notes for humans that Bash ignores. Now make the file executable and run it:
chmod +x hello.sh
./hello.sh
bash hello.shNamaste, Priya from Jaipur! Today is Friday. Logged in as priya. Namaste, Priya from Jaipur! Today is Friday. Logged in as priya.
./hello.sh means "run hello.sh from the current folder"; the ./ is needed because the current folder is not in PATH. Without chmod +x you get "Permission denied". bash hello.sh runs the file through Bash directly and does not need the execute permission. The .sh extension is only a convention; Linux uses the shebang, not the name.
Variables
Assign with name=value and no spaces around =. Read the value with $name, or ${name} when text follows straight after it. All Bash variables hold text; there are no separate number or string types.
item="Kaju Katli"
weight=2
echo "Item: $item"
echo "Packed ${weight}kg of $item"
readonly SHOP="Sharma Sweets"Item: Kaju Katli Packed 2kg of Kaju Katli
readonly makes a constant that cannot be changed later in the script. Use UPPER_CASE for constants and exported settings, and lower_case for ordinary variables, so you never accidentally overwrite important environment variables such as PATH or USER.
Quoting: the rule that prevents most bugs
Bash treats three kinds of quoting differently:
| Form | Effect | Example result |
|---|---|---|
"double" | Variables and $(...) are expanded; spaces are kept | "Item: $item" gives Item: Kaju Katli |
'single' | Everything is literal, nothing is expanded | 'Item: $item' gives Item: $item |
\ | Escapes one character | "Price: \$12" gives Price: $12 |
When a variable is used without quotes, Bash splits its value on spaces and expands wildcards in it. That breaks as soon as a value contains a space:
file="exam marks.txt"
cat $file
cat "$file"cat: exam: No such file or directory cat: marks.txt: No such file or directory 92
Unquoted, $file became two arguments, exam and marks.txt. The rule is simple: always put variables in double quotes, "$file", unless you have a specific reason not to. The tool ShellCheck (sudo apt install shellcheck, then shellcheck script.sh) warns you about every unquoted variable and many other mistakes.
Command substitution and arithmetic
$(command) runs a command and inserts its output. $(( expression )) does whole-number arithmetic:
today=$(date +%F)
files=$(ls /etc | wc -l)
price=450
qty=3
total=$(( price * qty ))
echo "$today: $files entries in /etc, bill Rs $total"
echo $(( 7 / 2 )) $(( 7 % 2 )) $(( 2 ** 10 ))
(( qty++ ))
echo "qty is now $qty"2026-10-30: 208 entries in /etc, bill Rs 1350 3 1 1024 qty is now 4
Inside $(( )) you can write variable names without $. Bash arithmetic is integers only: 7 / 2 is 3, and % gives the remainder. For decimals, use awk or bc. You may see backticks, as in today=`date`, in old scripts; they mean the same as $(date) but cannot be nested easily, so use $( ).
To print formatted output, prefer printf over echo. It works like C's printf and behaves the same everywhere: printf "%-10s %6.2f\n" "Ladoo" 12.5 prints Ladoo 12.50.
Reading input
read reads one line from the keyboard (or a pipe) into a variable. -p shows a prompt and -r stops backslashes being treated specially, so always use it:
read -r -p "Your name: " uname
echo "Hello, $uname"Your name: Kabir Hello, Kabir
Scripts that run unattended (from cron or systemd) should take arguments instead of asking questions; you will learn arguments in Functions, Arguments & Exit Codes.
Parameter expansion
Bash can transform a variable's value as it reads it, with no extra programs:
file="/var/www/shop/report-2026.csv"
echo "${file##*/}"
echo "${file%.csv}.bak"
echo "${#file}"
name="kabir"
echo "${name^} ${name^^}"
echo "${backup_dir:-/srv/backups}"report-2026.csv /var/www/shop/report-2026.bak 29 Kabir KABIR /srv/backups
##*/ removes everything up to the last / (the file name), %.csv removes a suffix, ${#var} is the length, ^ and ^^ capitalise, and :- supplies a default when the variable is empty or unset, which is perfect for optional settings.
Arrays
An array holds a list of values. Quote "${array[@]}" to get each element as a separate word, even if it contains spaces:
cities=(Pune Jaipur "Navi Mumbai")
cities+=(Kochi)
echo "First: ${cities[0]}, count: ${#cities[@]}"
for c in "${cities[@]}"; do echo "- $c"; doneFirst: Pune, count: 4 - Pune - Jaipur - Navi Mumbai - Kochi
Indexes start at 0. You will meet for loops properly in the next lesson.
Common mistakes
- Spaces around
=.name = "Priya"runs a command calledname. Writename="Priya". - Unquoted variables.
rm $filewithfile="my notes.txt"tries to deletemyandnotes.txt. Writerm "$file". - Single quotes when you wanted expansion.
'Hello $name'prints the dollar sign literally. - Windows line endings. A script saved in Notepad may fail with
$'\r': command not found. Convert it withsed -i 's/\r$//' script.sh, or save with LF endings in your editor. - Forgetting
./. Typinghello.shsays "command not found" because the current folder is not inPATH.
Key points
- A script starts with a shebang such as
#!/usr/bin/env bash; make it executable withchmod +xand run it with./script.sh. - Variables:
name=value(no spaces), read with"$name"or"${name}". - Double quotes expand variables, single quotes keep text literal; quote every variable.
$(command)inserts command output;$(( ))does integer arithmetic.${var:-default},${var%suffix},${var##*/}and${#var}transform values without extra tools.
Exercise
Write a script bill.sh that asks for a customer name, an item and a quantity, uses a fixed price of Rs 350 per item, and prints a bill like Bill for Ananya: 3 x Barfi = Rs 1050 (2026-10-30). Make the customer name start with a capital letter even if typed in lower case.
Show solution
#!/usr/bin/env bash
# bill.sh: print a simple sweet-shop bill
readonly PRICE=350
read -r -p "Customer: " customer
read -r -p "Item: " item
read -r -p "Quantity: " qty
total=$(( qty * PRICE ))
echo "Bill for ${customer^}: $qty x $item = Rs $total ($(date +%F))"Customer: ananya Item: Barfi Quantity: 3 Bill for Ananya: 3 x Barfi = Rs 1050 (2026-10-30)
readonly protects the price from being changed by mistake, $(( )) does the multiplication, and ${customer^} capitalises the first letter. Every variable is inside double quotes or arithmetic, so names with spaces such as "Navi Mumbai" would also work.
Quick Quiz
1. What does echo 'Total: $total' print?
Single quotes prevent all expansion.
2. Why should you write "$file" instead of $file?
Unquoted variables undergo word splitting and wildcard expansion.
3. What is the result of echo $(( 17 / 5 )) in Bash?
Bash arithmetic uses whole numbers only, so the decimal part is dropped.
4. With f="photo.jpg", what does echo "${f%.jpg}.png" print?
%.jpg removes the .jpg suffix before .png is added.
Learn Linux with a mentor
Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
