How to Run a Shell Script in Linux

How to Run a Shell Script in Linux

Reading time1 min
#linux#bash#shell-scripting#command-line#permissions

How to Run a Shell Script in Linux

The short answer:

chmod +x script.sh
./script.sh

Or, without changing permissions:

bash script.sh

The rest of this post explains what those two lines do, the differences between the ways to run a script, and the errors you will meet along the way.

A script that runs commands

A shell script is a text file with commands, run top to bottom:

#!/usr/bin/env bash
set -euo pipefail

src="${1:-/var/www/my-site}"
dest="/backup/$(basename "$src")-$(date +%F).tar.gz"

echo "Backing up $src to $dest"
tar -czf "$dest" -C "$(dirname "$src")" "$(basename "$src")"
ls -lh "$dest"
  • The first line is the shebang. It tells the kernel which interpreter runs the file. #!/usr/bin/env bash finds bash in PATH; #!/bin/bash works on every mainstream Linux distro too. It must be the very first line, with nothing before #!.
  • set -euo pipefail stops the script at the first failing command (-e), treats unset variables as errors (-u), and makes a pipeline fail if any part of it fails (pipefail). It is not a complete safety net, since -e is ignored inside if conditions and a few other places, but it catches most mistakes.
  • Quote variables ("$src"), or paths with spaces will break.

To chain commands on one line, && runs the next command only if the previous one succeeded, || only if it failed, and ; always:

cd /srv/my-app && git pull && docker compose up -d

Run it

1. Make it executable. chmod +x backup.sh adds execute permission for everyone; chmod u+x backup.sh only for the owner. See chmod in Linux for the details.

2. Run it with a path.

./backup.sh                       # from the current directory
~/scripts/backup.sh               # from anywhere
/home/deploy/scripts/backup.sh    # absolute path

The ./ is required because the current directory is not in PATH. That is deliberate: otherwise a file named ls dropped into a shared directory would run instead of the real ls. Typing just backup.sh gives command not found.

3. Or install it as a command. Put it in a directory that is in PATH:

mkdir -p ~/.local/bin
mv backup.sh ~/.local/bin/backup
backup /var/www/my-site

Ubuntu's default ~/.profile adds ~/.local/bin to PATH once the directory exists (log in again after creating it), and the RHEL-family ~/.bashrc adds it as well. Otherwise add export PATH="$HOME/.local/bin:$PATH" to ~/.bashrc. For all users, use /usr/local/bin with sudo.

./script.sh vs bash vs sh vs source

CommandNeeds +xInterpreterRuns in
./script.shyesfrom the shebanga new process
bash script.shnobasha new process
sh script.shnosh (dash on Debian and Ubuntu)a new process
source script.sh or . script.shnoyour current shellyour current shell

Two traps here:

  • On Debian and Ubuntu, /bin/sh is dash, not bash. A bash script run with sh script.sh fails on bash features such as [[ ]] or arrays, with errors like [[: not found. Run bash scripts with bash or via the shebang. On the RHEL family sh is bash, which hides the problem until the script reaches an Ubuntu host.
  • source runs the file in your current shell. Variables, cd and exported settings stay after it finishes, which is the point for files like venv/bin/activate. But an exit inside a sourced file closes your terminal session.

Pass arguments

./deploy.sh staging v1.4.2

Inside the script, $1 is staging, $2 is v1.4.2, $# is the number of arguments, "$@" is all of them and $0 is the script name:

#!/usr/bin/env bash
set -euo pipefail

if [[ $# -lt 2 ]]; then
  echo "Usage: $0 ENVIRONMENT VERSION" >&2
  exit 1
fi

case "$1" in
  staging|production) echo "Deploying $2 to $1" ;;
  *) echo "Unknown environment: $1" >&2; exit 1 ;;
esac

After a script finishes, echo $? shows its exit code. 0 means success, anything else is a failure.

Run other files and programs

  • Installed programs run by name, nginx -t or python3. command -v name shows which file that is.
  • A downloaded binary needs chmod +x my-tool and then ./my-tool.
  • A Python script runs with python3 script.py, or directly with #!/usr/bin/env python3 as the first line plus chmod +x.
  • An AppImage runs like a binary after chmod +x. Many still need the FUSE 2 library (libfuse2t64 on Ubuntu 24.04).
  • .deb and .rpm files are not run, they are installed: sudo apt install ./package.deb or sudo dnf install ./package.rpm.

As root, in the background, on a schedule

sudo ./backup.sh                          # as root
sudo -u deploy ./deploy.sh staging v1.4.2 # as another user
./long-job.sh &                           # background; jobs, fg, bg
nohup ./long-job.sh > job.log 2>&1 &      # keeps running after logout

sudo backup without a path may fail with command not found even though it works for your user. sudo uses its own secure_path, which does not include ~/.local/bin. Use the full path.

For long jobs over SSH, tmux is more convenient than nohup: detach with Ctrl+B then D, and reattach later with tmux attach. For recurring jobs, use cron (crontab -e) or a systemd timer. Cron runs with a minimal PATH, so use absolute paths for the script and for the commands inside it:

0 3 * * * /home/deploy/.local/bin/backup >> /home/deploy/backup.log 2>&1

Common errors

  • Permission denied: the file is not executable. Run chmod +x. If it already is, the filesystem may be mounted noexec, which is common for /tmp on hardened servers; move the script or run bash script.sh.
  • command not found: you forgot ./, or the directory is not in PATH.
  • /bin/bash^M: bad interpreter: No such file or directory: the file has Windows line endings. Fix with sed -i 's/\r$//' script.sh or dos2unix script.sh.
  • cannot execute binary file: Exec format error: a binary built for another CPU architecture. Compare file my-tool with uname -m.
  • syntax error near unexpected token or [[: not found: a bash script run by sh.

To see what a script is doing, run it with bash -x script.sh, which prints every command before it runs. shellcheck script.sh (package shellcheck) finds quoting bugs and other mistakes before you run anything.

Scripts from the internet

curl ... | bash executes code you have not read, with your privileges. Download it, read it, then run it:

curl -fsSLo install.sh https://example.com/install.sh
less install.sh
bash install.sh

Quick reference

chmod +x script.sh                 # make executable
./script.sh                        # run from the current directory
bash script.sh                     # run without execute permission
source env.sh                      # run in the current shell
./script.sh arg1 arg2              # with arguments ($1, $2, "$@")
echo $?                            # exit code of the last command
bash -x script.sh                  # trace every command
sed -i 's/\r$//' script.sh         # fix Windows line endings
nohup ./script.sh > out.log 2>&1 & # keep running after logout
mv script.sh ~/.local/bin/name     # run it by name from anywhere