Your first script

Shebang, chmod +x, and running it.

Beginner12 min · lesson 1 of 14

You already know how to type commands. You run ls, you run date, you hit Enter, something happens. A shell script takes that same skill and saves it. Instead of retyping the same five commands every morning, you write them down once in a file and run the file. That is the whole idea, and everything else in this lesson is detail stacked on top of it.

A script is your commands, saved in a file

A recipe card is a good picture of it. A recipe is an ordered list of steps that anyone can follow to get the same dish. A shell script is an ordered list of commands the shell follows to get the same result. The shell is the program reading your typing right now (on Linux that is usually bash, the Bourne Again Shell). A script is a plain text file holding the exact commands you would have typed, and the shell runs them top to bottom, one line at a time. Let us make the smallest one that does something.

hello.sh
# hello.sh - my very first script
echo "Hello from my first script"

Two lines, two ideas. The second line uses echo, a command that prints whatever you hand it straight back to the screen. The first line starts with a # (hash), which marks a comment: the shell ignores everything from the # to the end of that line. Comments are notes for humans, not instructions for the machine. Use them to explain why a line exists, not what it obviously does, because the person reading your script at 3 a.m. during an outage might be you.

~/secopslog — bash
$ bash hello.sh
Hello from my first script

That worked because you handed the file to bash directly and told it to read the file as a list of commands. It runs with no special setup. But typing bash in front of the name every time gets old, and it is not how real tools on your system behave. You do not type bash /usr/bin/ls, you type ls. To bring your script up to that level, where you run it by name, you need two more things: a shebang line and the execute permission.

The shebang: telling the system what runs this file

A text file does not announce what language it is written in. Open one cold and it could be Bash, Python, Perl, or a shopping list. The shebang is the label that settles the question, the way a language sticker on a boxed appliance tells you which manual to reach for. When you run a file directly, the operating system reads the very first line, and if it begins with the two characters #! (pronounced "shebang", short for hash-bang), it treats the rest of that line as the path to the program that should run the file. So the first line is a message to the system, not a command for Bash.

hello.sh
#!/usr/bin/env bash
# hello.sh - my very first script
echo "Hello from my first script"

Read #!/usr/bin/env bash from left to right: run this file with bash, and find bash by asking env. You could hardcode #!/bin/bash instead, and that often works, but Bash sits in different places on different machines (/bin/bash on most Linux, and a /usr/local/bin or Homebrew path on a Mac). /usr/bin/env is a small program that looks up bash in your PATH and starts the first one it finds, so the same script runs on more machines without editing. One catch: because # normally starts a comment, the shebang only does its special job on the literal first line of the file. Put a blank line or a comment above it and it turns into an ordinary comment, and the effect is lost.

chmod +x: giving the file permission to run

Every file on Linux carries a set of permissions, like a keycard that records who may read it, change it, and run it. A fresh text file can be read and written, but it is not marked runnable, because most files are data, not programs. chmod (change mode) is the command that edits those permissions, and +x adds the execute permission: the "yes, this may be launched as a program" flag. Watch the file's first column before and after.

~/secopslog — bash
$ ls -l hello.sh chmod +x hello.sh ls -l hello.sh
-rw-r--r-- 1 ada ada 88 Jul 17 10:21 hello.sh -rwxr-xr-x 1 ada ada 88 Jul 17 10:21 hello.sh

Before, the permissions read -rw-r--r--. After, they read -rwxr-xr-x. Those new x characters are the execute permission you just added, granted to the owner, the group, and everyone else. From the system's point of view, this file is now a program it is allowed to start. From a security point of view, hand out only the access a file truly needs. It is tempting to reach for chmod 777, which lets every account on the machine read, write, and run the file, but that also lets any other user rewrite your script, so next time you run it you run their version. chmod +x does the job without that risk. To lock it down further, chmod 750 keeps full read, write, and run for you, lets your group read and run the file but not change it, and shuts everyone else out completely.

Two ways to run it, and why the ./ is there

Now you can run the script by pointing at the file with ./hello.sh. That leading ./ trips up almost everyone at first, so here is exactly what it means and why it is not optional.

~/secopslog — bash
$ ./hello.sh
Hello from my first script

./ means "in this folder, right here". The single dot is shorthand for the current directory, and the slash separates it from the filename. So ./hello.sh is a precise address: the file named hello.sh in the directory you are standing in. Give the shell a path like that and it goes straight to the file. Now try the same command without the dot-slash.

~/secopslog — bash
$ hello.sh
bash: hello.sh: command not found

Same file, same folder, and the shell says it cannot find it. Here is why. When you type a bare name like ls or hello.sh, the shell does not look in your current folder. It searches PATH, an environment variable (a named setting the shell keeps in memory) that holds a list of directories separated by colons, and it runs the first matching program it finds there. Your current folder is not on that list, so a bare hello.sh is invisible to the search even with the file right in front of you. Writing ./ skips the search entirely by naming the file's location outright. You can print your own PATH to see the list.

~/secopslog — bash
$ echo "$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Leaving the current directory off PATH is a deliberate safety decision, and it matters more than it looks. Suppose your current folder were searched. An attacker who could drop a file into a shared directory could name it ls, or git, or date. The moment you changed into that folder and typed the real command out of habit, you would run their file instead of the trusted system tool. Keeping the current directory off PATH means typing a command name always finds the real program, never a stray file sitting in whatever directory you happen to be in. The small annoyance of writing ./ is the visible edge of that protection.

So you have two ways to run a script, and both are fine. bash hello.sh hands the file to Bash as input and needs neither a shebang nor the execute bit, which makes it handy for a quick test. ./hello.sh runs the file as a program in its own right, using the shebang to pick the interpreter and the execute bit for permission. Real tools ship the second way.

Read a script before you run it
The flip side of how easy scripts are to run is how easy they are to run blindly. You will meet install instructions that read curl https://example.com/install.sh | bash, which uses curl (a tool that fetches files over the web) to download a script and feed it straight into Bash without ever showing you a line of it. You are trusting that server, and the whole network path to it, with commands that run as you. If the file were swapped for a malicious one, you would never see it coming. A script is a text file, which means you can always read it. Download it first, open it, see what it does, then run it: curl -fsSLO https://example.com/install.sh, read install.sh, and only then bash install.sh.

Your greeting script

Put the pieces together into something with a little life in it. This script greets whoever runs it and prints the current date and time.

greet.sh
#!/usr/bin/env bash
# greet.sh - say hello to the current user and show the date
name="$(whoami)" # whoami prints the username running this script
echo "Hello, $name!"
echo "Today is $(date)"

Two new pieces here. whoami is a command that prints the name of the user running it. Wrapping it in $( ... ) is command substitution: Bash runs the command inside the parentheses and drops its output right into the line, so name ends up holding your username. Then $name inside the double quotes pastes that value into the greeting. The same trick prints the clock: $(date) runs the date command and inserts whatever it reports. Make it executable and run it.

~/secopslog — bash
$ chmod +x greet.sh ./greet.sh
Hello, ada! Today is Fri Jul 17 10:24:31 UTC 2026
Three ways to name greet.sh, three rules
Run greet.sh
same file, three commands
bash greet.sh
Bash reads the file as input
No execute bit or shebang needed
./greet.sh
Run the file as a program
Needs chmod +x; shebang picks bash
greet.sh
Shell searches PATH only
Folder not on PATH: command not found
The dot-slash names the file where it sits; a bare name is looked up in PATH, and your folder is not on that list.
Quick check
01You are in the folder that holds greet.sh. It has a shebang and the execute bit, yet typing greet.sh prints "command not found". Why?
Incorrect — A missing execute bit gives "Permission denied", and only when you use ./greet.sh. Here the bit is set.
Correct — Point at the file with ./greet.sh, or run bash greet.sh.
Incorrect — A bad interpreter gives a "bad interpreter: No such file or directory" error about the interpreter, not "command not found" for the script.
Incorrect — The shebang is a comment to Bash and is completely normal; that is never the problem.
02The lesson's scripts open with `#!/usr/bin/env bash` rather than `#!/bin/bash`. Why is the env form usually preferred?
Correct — env makes the shebang portable (for example a Homebrew bash path on a Mac) instead of hardcoding one fixed location.
Incorrect — env has nothing to do with the execute bit; you still need chmod +x to run the file by path.
Incorrect — env does not speed anything up; its only job is to locate the interpreter.
Incorrect — #!/bin/bash is valid and often works; it is simply less portable than the env form.
03`bash deploy.sh` runs your script correctly, but `./deploy.sh` fails with 'Permission denied.' What is the cause and the fix?
Incorrect — a bad interpreter gives a different error, and the fact that `bash deploy.sh` works shows the file's contents are fine.
Correct — `bash deploy.sh` feeds the file to Bash and needs no execute bit, but `./deploy.sh` runs it as a program, which requires the execute permission.
Incorrect — a missing PATH entry causes 'command not found' for a bare name, not 'Permission denied' for a ./ path.
Incorrect — this is about the file's execute permission, not administrative privilege.

Related