How To Run A Sh Script

How To Run A Sh Script

Reading time1 min
#Linux#Automation#Security#ShellScripting#Unix#Bash

Mastering the Essentials: How to Run a .sh Script Efficiently and Securely

Shell scripts (.sh) drive the automation backbone of any serious Linux or Unix infrastructure. Whether you’re pushing configuration changes to a fleet of CentOS 7 servers or orchestrating nightly backups on Ubuntu 22.04 LTS, executing shell scripts is unavoidable—but fraught with pitfalls if not handled with care.


What Is a .sh Script?

At core, a .sh file is an executable text document containing Unix shell instructions. The file’s shebang (e.g., #!/bin/bash) determines the interpreter, but beware: not all environments default to bash. For reproducibility across systems, always verify your interpreter explicitly.

Typical example:

#!/bin/bash
echo "Hello, world"

Inspection: Trust but Verify

Never assume a downloaded or inherited script is safe. Open each script—preferably in a code-aware editor (vim, nano, even bat for syntax highlighting)—and visually scan for:

  • Destructive operations (rm -rf /, :(){:|:&};:, etc.).
  • Embedded remote execution (curl … | sh).
  • Environment variable manipulation (export PATH=…).
  • Sudo or root invocations without clear justification.

If your organization uses a SIEM or has an internal code review process, route scripts through proper channels before use. Even a small typo in a file deletion command can be catastrophic.


Permissions: Make It Executable

Shell scripts aren't executable by default except in certain version-controlled directories. Confirm permission with ls -l script.sh—missing an x? Grant execution with:

chmod u+x hello.sh

This restricts execution privileges to the owner. If the script is part of a multi-user environment, consider group ownership or using sudoers for controlled escalation.


Running the Script

There’s a subtlety here: binary executability vs. explicit interpretation.

  • Direct Execution:

    ./hello.sh
    

    Assumes the current working directory is not in your $PATH (for security, it shouldn’t be). Prefixing with ./ forces execution from the current location.

  • Interpreter Invocation:

    bash hello.sh
    

    or

    sh hello.sh
    

    This can help with debugging or running scripts on systems where permissions are locked down (e.g., on a CI runner).

Note: When porting scripts between distributions, check the available shells with cat /etc/shells. Alpine users, for example, get ash by default.


Efficient Operation: Avoid Surprises

Automation at scale punishes shortcuts. Harden your scripts:

  • Absolute Paths:
    Never rely on $PATH for critical operations; hardcode binaries:

    /usr/bin/rsync -a --delete /data/ /backup/
    

    On systems with multiple Python or Bash binaries, be explicit (/usr/bin/python3.9 vs python).

  • Safe Mode Flags:

    set -euo pipefail
    
    • -e: exit on error,
    • -u: treat unset variables as errors,
    • -o pipefail: any error in a pipeline breaks the script.
  • Redirection and Logging:
    All outputs, including STDERR, should be logged for audit or debugging:

    ./deploy.sh > /var/log/deploy.log 2>&1
    

    Rotate logs to avoid disk bloat.

  • Actionable Output:
    Don’t be silent on failure—print the error and exit non-zero:

    if [[ ! -f "$CONFIG" ]]; then
      echo "FATAL: Config $CONFIG missing" >&2
      exit 99
    fi
    

Security Hardening

  • Principle of Least Privilege:
    Only escalate with sudo where needed. Running entire scripts as root leads to irreversible mistakes.

  • Input Validation:
    For scripts that take arguments or STDIN, always sanitize:

    [[ "$1" =~ ^[a-zA-Z0-9_-]{1,32}$ ]] || { echo "Bad input"; exit 2; }
    
  • Environment Lockdown:
    Consider:

    export PATH="/usr/bin:/bin"
    

    This prevents execution of rogue binaries shadowing system names.

  • Keep Up-to-date:
    Running an old Bash (e.g., pre-4.3) leaves you exposed to bugs like Shellshock (CVE-2014-6271). Check versions:

    bash --version
    

Gotcha: Even scripts with rigorous input checks can fail due to locale differences. Specify LC_ALL=C where possible.


Automation Example: Scheduling with Cron

Routine automation relies on predictable execution. A typical cron entry running a backup every day at 02:00:

0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

Remember: scripts run from cron have a minimal environment. Always define full paths and anticipate missing ENV variables.


Reference Table: Common Steps

ActionCommand/Recommendation
Inspect scriptvim script.sh
Make executablechmod u+x script.sh
Execute./script.sh or bash script.sh
Enforce safety flagsset -euo pipefail at top of script
Input validation[[ "$arg" =~ ... ]] conditional
Logs & errors> log 2>&1 for output, unique non-zero exits
Least privilegeNon-root unless using explicit sudo

Non-Obvious Tip

For mutable deployment environments (e.g., containers bootstrapping via entrypoint scripts), always run:

dos2unix script.sh

on any script transferred from Windows. Otherwise, expect cryptic /bin/bash^M: bad interpreter errors.


Manual checks, defensive coding, and controlled automation are what separate a maintainable fleet from one that introduces silent, cascading failures. If possible, use CI/CD to lint scripts (e.g., ShellCheck) before deployment.


Further reading:
Next up: patterns for writing reusable, testable Bash scripts (trap handlers, set -x, and version-locking).


If you found this process useful for production environments, consider sharing with your team or subscribing for further Linux automation strategies.