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/bash
and/bin/sh
). - Practical detail: Some scripts will only work with Bash due to arrays, process substitution (
<(cmd)
), etc. Running withsh
on 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/bash
vs 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:
Useecho
or better,logger
to 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.