The first script I ever wrote was just three lines long, but it saved me from typing the same five commands every single morning. That’s really the whole point of Bash scripting — automating repetitive work. In this guide, I’ll walk you through the complete process of creating a Bash script from scratch, step by step, including everything you need to make it actually runnable.
What Is a Bash Script?
A Bash script is simply a plain text file containing a sequence of commands that Bash can execute, one after another, exactly as if you’d typed them into the terminal yourself. Instead of running commands manually every time, you save them once and run the whole file whenever you need to.
Step 1: Open a Text Editor
You can write a Bash script using any plain text editor — nano, vim, gedit, VS Code, or even a graphical text editor. I’ll use nano here since it’s beginner-friendly and available on almost every Linux system.
nano myscript.sh
This opens a new (or existing) file named myscript.sh in the nano editor. The .sh extension isn’t strictly required by Bash itself, but it’s a strong convention that immediately tells anyone browsing your files that this is a shell script.
Step 2: Add the Shebang Line
The very first line of your script should be the shebang, which tells the system which interpreter to use to run the file.
#!/bin/bash
This line must be the absolute first line — no blank lines or spaces before it — or the system won’t recognize it correctly.
Step 3: Write Your Commands
Below the shebang, add whatever commands you want the script to execute, one per line.
#!/bin/bash
echo "Starting the script..."
echo "Current date and time:"
date
echo "Files in this directory:"
ls
echo "Script finished."
Save the file. In nano, that’s Ctrl+O to write out, then Enter to confirm, and Ctrl+X to exit.
Step 4: Make the Script Executable
Before you can run the script directly, you need to give it execute permission using chmod.
chmod +x myscript.sh
What this does: chmod (change mode) modifies the file’s permission bits. The +x flag adds execute permission for the file, allowing it to be run as a program rather than just opened as plain text.
You can confirm the permission was applied using:
ls -l myscript.sh
Output:
-rwxr-xr-x 1 user user 145 Jul 28 14:00 myscript.sh
Notice the x characters in the permission string (rwxr-xr-x) — those confirm execute permission is now set for the owner, group, and others.
Step 5: Run the Script
There are two common ways to run a Bash script.
Method 1: Using ./
./myscript.sh
Output:
Starting the script...
Current date and time:
Tue Jul 28 14:32:10 UTC 2026
Files in this directory:
myscript.sh
Script finished.
The ./ tells the shell explicitly to look for the script in the current directory, since Bash doesn’t search the current directory by default for security reasons.
Method 2: Running It Through Bash Directly
bash myscript.sh
This works even if the file doesn’t have execute permission, since you’re explicitly telling Bash to interpret the file rather than trying to execute it as a standalone program.
Step 6: Add Structure — Variables, Input, and Logic
A real-world script usually does more than print static text. Let’s build one that actually does something useful.
#!/bin/bash
# A simple script to back up a folder
echo "Enter the folder path you want to back up:"
read source_folder
if [ ! -d "$source_folder" ]
then
echo "Error: That folder does not exist."
exit 1
fi
timestamp=$(date +%Y%m%d_%H%M%S)
backup_name="backup_$timestamp.tar.gz"
tar -czf "$backup_name" "$source_folder"
echo "Backup created: $backup_name"
Sample run:
Enter the folder path you want to back up:
/home/user/documents
Backup created: backup_20260728_143520.tar.gz
This script:
- Asks the user for a folder path
- Checks whether that folder actually exists using
-d - Exits with an error message if it doesn’t
- Builds a timestamped filename for the backup
- Compresses the folder into a
.tar.gzarchive - Confirms the backup was created
Step 7: Passing Arguments to a Script
Instead of always prompting for input, scripts can accept arguments directly from the command line.
#!/bin/bash
echo "You are backing up: $1"
tar -czf "backup.tar.gz" "$1"
echo "Done!"
Sample run:
$ ./backup.sh /home/user/photos
You are backing up: /home/user/photos
Done!
Step 8: Adding Error Handling
Good scripts check whether things actually succeeded before moving on.
#!/bin/bash
mkdir /some/protected/folder
if [ $? -ne 0 ]
then
echo "Failed to create folder. Check your permissions."
exit 1
fi
echo "Folder created successfully."
$? holds the exit status of the last command — 0 means success, anything else means it failed.
Step 9: Running a Script from Anywhere
If you want to run your script by name from any directory (without typing ./ or the full path), move it into a directory listed in your PATH, such as /usr/local/bin, and make sure it’s executable.
sudo mv myscript.sh /usr/local/bin/myscript
sudo chmod +x /usr/local/bin/myscript
Now you can simply type:
myscript
from any location in the terminal.
How This Works Internally
- When you run
./myscript.sh, the shell asks the kernel to execute the file. The kernel reads the first two bytes; if they’re#!, it looks at the rest of that line to determine the interpreter (/bin/bashin our case) and hands the file off to it. chmod +xsets the executable bit in the file’s permission metadata, stored as part of the filesystem’s inode information — without this bit, the kernel refuses to execute the file directly, even if its content is valid Bash.- When you run
bash myscript.shinstead, you’re bypassing the executable-bit requirement entirely, since you’re explicitly launching thebashinterpreter and handing it the script file as an argument. $?is set automatically by Bash after every command completes, reflecting that command’s exit status — this is a core mechanism used throughout error handling in scripts.
Real-World Use Cases
- Automated backups, as shown above.
- Deployment scripts that pull the latest code, install dependencies, and restart a service.
- System maintenance scripts that clean up old log files, check disk space, or rotate backups.
- Environment setup scripts that install and configure everything a new developer machine needs in one run.
Best Practices
- Always start with a shebang line (
#!/bin/bash). - Use meaningful script names that describe what they do (
backup_database.sh, notscript1.sh). - Include comments explaining the script’s purpose, especially near the top.
- Validate input and check command exit statuses (
$?) before proceeding to the next step. - Use
set -eat the top of critical scripts to make the whole script exit immediately if any command fails, preventing cascading errors.
Security Considerations
- Never store passwords or API keys in plain text inside a script; use environment variables or a secrets manager instead.
- Be cautious about giving scripts execute permission for everyone (
chmod 777) — restrict permissions to only what’s needed (chmod 750, for example). - Validate any input used to build filenames or paths, to avoid accidental (or malicious) path traversal.
- Avoid running scripts as
rootunless absolutely necessary, and audit any script before running it with elevated privileges.
Optimization Tips
- Break large scripts into functions for readability and reusability, rather than one long sequential list of commands.
- Avoid unnecessary subshells and external command calls inside loops — prefer Bash builtins where possible.
- Use
shellcheck(a popular static analysis tool for shell scripts) to catch common mistakes and inefficiencies before running your script in production.
Troubleshooting Common Issues
- “Permission denied” when running the script — you forgot to run
chmod +xon the file. - “bad interpreter: No such file or directory” — usually caused by incorrect line endings (Windows-style
\r\ninstead of Unix\n) in the shebang line; convert the file usingdos2unixif needed. - Script runs but has no effect — double-check you’re running the version you just edited and not an older cached copy, and verify file paths are correct.
- “command not found” for a command you know exists — check that the command’s directory is included in your
PATHenvironment variable.
Frequently Asked Questions
Q: Do Bash scripts need the .sh extension? A: No, it’s just a naming convention. Bash doesn’t require it, but it’s helpful for readability and tooling.
Q: What’s the difference between running ./script.sh and bash script.sh? A: ./script.sh requires the execute permission bit to be set and relies on the shebang line; bash script.sh explicitly invokes Bash regardless of the execute permission.
Q: How do I run a script automatically at startup or on a schedule? A: Use cron for scheduled tasks (crontab -e) or add it to your system’s startup services for boot-time execution.
Q: Can a Bash script call another Bash script? A: Yes, simply reference it by path, e.g., ./other_script.sh, or use source other_script.sh if you want it to run within the same shell session.
Common Mistakes to Avoid
- Forgetting to make the script executable before trying to run it directly.
- Leaving out the shebang line, causing inconsistent behavior across different systems.
- Not validating user input or command success before proceeding.
- Hardcoding paths or values that should really be variables or arguments.
Summary
Creating a Bash script is straightforward: write your commands in a plain text file, add a shebang line at the top, make the file executable with chmod +x, and run it. From there, the real skill lies in structuring your script well — using variables, handling input, checking for errors, and documenting your intentions clearly. Once you’ve built a handful of scripts this way, automating your daily workflow becomes second nature.
