How To Run .sh Files in Linux: A Practical Engineer’s Guide
Automate or stagnate. Shell scripting isn’t optional in modern Linux administration or DevOps. But just slapping bash script.sh on every workload only covers the basics. Real-world command execution brings permissions, interpreter mismatches, subtle environment quirks, and risk—all factors you ignore at your peril.
What’s a .sh File Really?
A .sh file is simply a POSIX shell script—plain text, usually Bash- or sh-compatible, containing commands run line by line. It can automate user onboarding, orchestrate builds, or clean temp directories nightly via cron. The only requirement: the commands must be interpretable by the designated shell.
Validate First: Always Inspect the Script Content
Blind execution is occasionally disastrous. Before running anything:
cat script.sh
# or, for colorized context:
less -N script.sh
# quick edit (nano/vim/emacs based on your comfort)
nano script.sh
Review all third-party scripts, especially those pulled from forums or Stack Overflow. Shell scripts frequently embed rm, curl|bash, or worse—backdoors.
Set Proper Permissions
Unix file permissions determine security and behavior; ignore at your own risk.
ls -l script.sh
Typical permission readout:
-rw-r--r-- 1 alex devs 216 May 29 19:17 script.sh
If there’s no x (executable), you must add it:
chmod +x script.sh
Re-check:
-rwxr-xr-x 1 alex devs 216 May 29 19:17 script.sh
Note: If you’re scripting on mounted network drives (e.g., within a Docker bind-mount), you may encounter odd permission enforcement depending on NFS or CIFS flags.
Two Primary Execution Methods
1. Directly via Shell Interpreter
bash script.sh
# or
sh script.sh
- This method ignores the file’s executable bit—permissions don’t matter as long as the shell can read the file.
- Useful when you need to specify the shell explicitly (e.g., differences between
/bin/bashand/bin/sh). - Practical detail: Some scripts will only work with Bash due to arrays, process substitution (
<(cmd)), etc. Running withshon Debian/Ubuntu (dash) will silently break those.
2. Execute as Standalone Program
./script.sh
- Requires execute bit set (
chmod +x). - Script must start with a correct shebang line—first line must be, e.g.,
#!/bin/bash - If you omit the shebang or use a wrong path (for example, Mac’s
/bin/bashvs some Linuxes’/usr/bin/bash), expect cryptic “bad interpreter: No such file or directory” errors.
Known Gotcha: The current directory (.) is often not part of $PATH for security reasons, hence ./script.sh instead of just script.sh. Editing your $PATH to include . is discouraged outside of controlled development sandboxes.
About Shebangs: Subtle, Crucial
The shebang (#!) declares the interpreter. Three commonly used patterns:
#!/bin/bash # Hardwired, standard in many distros; not everywhere.
#!/usr/bin/env bash # Portable, defers lookup to whoever is first in PATH.
#!/bin/sh # Generic shell, but not always Bash.
Compatibility issue: On Ubuntu, /bin/sh points to Dash—a fast shell with reduced features. Many scripts written for Bash will throw subtle errors:
script.sh: 7: Syntax error: "(" unexpected
Creating a Minimal Script (And a Real Example)
nano cleanup_tmp.sh
Content:
#!/usr/bin/env bash
set -e # fail on error, critical for cron jobs
echo "Purging /tmp files older than 72 hours"
find /tmp -type f -mtime +3 -delete
Make executable:
chmod +x cleanup_tmp.sh
Run:
./cleanup_tmp.sh
Side Note: On Ubuntu 22.04’s default Bash 5.1.16, set -e catches nonzero return codes, but won’t halt on failed pipes unless you add set -o pipefail.
Passing Arguments
Scripts often require parameters. Call with:
./deploy.sh staging myapp
Inside deploy.sh, these map to $1, $2, etc.
Example:
#!/bin/bash
env="$1"
application="$2"
echo "Deploying $application to $env"
Non-obvious tip: Wrap arguments in double quotes to handle spaces, i.e. "$1". Otherwise, files or app names with whitespace break.
Debugging Shell Scripts: Beyond echo
Bugs propagate fast in automation. Tactics:
- Enable debugging output:
Add near the script top:
Or, run with:set -x
This yields trace output for every command, example:bash -x script.sh+ echo 'Purging /tmp files older than 72 hours' - Syntax check only:
bash -n script.sh - Log checkpoints:
Useechoor better,loggerto send to syslog:logger "Starting cleanup at $(date)"
Practical issue: Large scripts with many subshells or background processes (&) become unwieldy to debug with just set -x. In those cases, sprinkle trap lines:
trap 'echo "Error at line $LINENO"; exit 1' ERR
Environment and Sourcing Pitfalls
Each invocation with ./script.sh launches a separate process. Environment variables exported in your interactive shell are only visible in scripts if you export them:
MY_ENV=prod ./deploy.sh
or
export MY_ENV=prod
./deploy.sh
If your script sets variables meant to persist after execution (modifying $PATH, e.g.), use:
source script.sh
# or shorthand:
. script.sh
Caveat: Sourcing scripts with side effects (like exit or set -e) can litter your interactive shell with unwanted state. Test carefully.
Table: Shell Script Execution Reference
| Operation | Command | Notes |
|---|---|---|
| View script contents | cat script.sh | Or less, nano, vim |
| Check/modify permissions | ls -l script.sh / chmod +x script.sh | |
| Run explicitly with Bash | bash script.sh | Ignores executable bit |
| Run as standalone script | ./script.sh | Requires shebang + executable bit |
| Pass arguments | ./script.sh arg1 arg2 | Accessible via $1, $2, … |
| Debug line-by-line | bash -x script.sh / set -x | set -e; trap for error tracing |
| Syntax only (no run) | bash -n script.sh | Useful for CI pre-checks |
| Source into current shell | source script.sh or . script.sh | Shares exported variables/functions |
Not Quite Obvious: Executing on Non-GNU Systems
Scripts that assume GNU utilities (sed, grep, etc.) may fail on BSD/macOS due to option differences. Explicitly specify versions where possible (e.g., /usr/bin/gsed vs /bin/sed). Check the target shell’s --version for feature set; Bash 4+ features fail on distros with system Bash <4 (many RHEL/CentOS 7 boxes).
Engineering Summary
Running .sh files correctly in Linux is rarely a case of “just run this command.” Secure, predictable automation requires scrutinizing permissions, understanding interpreter paths, being mindful of environment scope, and adopting strong debugging habits.
Trade-off: Strict permissioning and per-shell shebangs increase reliability but add friction to cross-system portability. Harden scripts where safety matters, but remain pragmatic for local tooling.
Write, review, and run scripts as if your infrastructure depends on it—because, eventually, it will.
