How Bash runs a script
A shell receives commands, expands syntax, and asks the kernel to run programs. Bash is the default on most servers; sh is a smaller POSIX shell (Dash on Ubuntu), so Bash-only syntax can fail when a script runs under sh. A Bash script commonly starts with #!/usr/bin/env bash; the shebang matters when the file is executed directly as ./hello.sh. Running bash hello.sh explicitly invokes the interpreter and does not require the script's execute bit, though the file must remain readable.
#!/usr/bin/env bash
set -euo pipefail # stop on errors, unset variables, failed pipes
# Lines starting with # are comments
name="${1:-World}"
echo "Hello, $name"
./ is needed because the current directory is not in PATH, the list of directories Bash searches for commands. source script.sh (or . script.sh) runs the file inside the current shell instead of a child process, so variables it sets remain afterwards; that is how ~/.bashrc changes are reloaded.
flowchart LR F["hello.sh<br/>starts with a shebang"] -- "chmod +x" --> X["./hello.sh"] IN["stdin 0<br/>keyboard or a file"] --> X X --> OUT["stdout 1<br/>normal output"] X --> ERR["stderr 2<br/>error messages"] X --> RC["exit status $?<br/>0 = success"] %% step F: The shebang line tells the kernel which interpreter runs the file. %% step X: chmod +x makes it executable; ./ runs it from the current directory. %% step IN: Input arrives on stdin, from the keyboard or a redirected file. %% step OUT: Normal output goes to stdout, which > or | can redirect. %% step ERR: Errors go to stderr so they stay visible when stdout is redirected. %% step RC: Every command returns an exit status; 0 means success.
Streams and redirection
Every process has three standard streams: stdin (0), stdout (1), and stderr (2). Errors go to stderr, so > alone does not capture them.
| Syntax | Effect |
|---|---|
chmod u+x hello.sh |
Add execute for the owner |
bash -n hello.sh |
Check syntax without executing |
date > today.txt |
Replace a file with stdout |
who >> today.txt |
Append stdout to a file |
cmd 2> err.log |
Send stderr to a file |
cmd > all.log 2>&1 |
Send stdout and stderr to one file |
cmd > /dev/null |
Discard output |
ls | sort |
Feed ls stdout to sort stdin |
A here-document feeds several lines to stdin: cat <<EOF > note.txt writes everything up to a line containing only EOF.
Input, variables, and exit status
read -r answer reads one line without treating backslashes specially. Assign variables without spaces around =: name="John" assigns, whereas name = "John" asks Bash to run a command named name.
Every command returns an exit status: 0 for success, anything else for failure. echo $? shows the last one, and exit 1 ends a script with failure so that cron or a CI pipeline notices.
> truncates before writing; use >> to preserve old content. A “Permission denied” error during direct execution often means the execute bit is missing. bash -x hello.sh prints each command as it runs, which is the quickest way to debug.