Skip to content
shellmap

Make a script executable

Give a script the right permissions and shebang so you can run it directly (`./script.sh`) instead of through an interpreter (`bash script.sh`).

How to make a script executable in each shell

Bashunix
chmod +x script.sh

`chmod +x` adds the execute bit for owner+group+other (it's `+x` on the umask-masked set — usually owner-write effective). For owner-only execute: `chmod u+x`. The script ALSO needs a shebang line (`#!/usr/bin/env bash` as line 1) for the kernel to know what interpreter to launch — without it, `./script.sh` runs the script through whatever shell launched it (often bash, but not guaranteed).

Zshunix
chmod +x script.zsh

Same `chmod` mechanism. Shebang convention: `#!/usr/bin/env zsh` (portable; finds zsh on `$PATH`) vs `#!/bin/zsh` (absolute path; fails on macOS Catalina+ where system zsh is `/bin/zsh` but Homebrew zsh is `/opt/homebrew/bin/zsh`). Prefer `env` for portability.

Fishunix
chmod +x script.fish

Same `chmod` + shebang pattern: `#!/usr/bin/env fish`. Fish-specific: fish doesn't implement bash `set -e` (exit-on-error) — for "fail fast" semantics, check every command's exit status explicitly (`if not cmd; exit 1; end`) or use `set -l status` patterns.

PowerShellwindows
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

PowerShell `.ps1` scripts are blocked by ExecutionPolicy by default on Windows (`Restricted` for desktop pwsh 5.1, `RemoteSigned` for pwsh 7+). `RemoteSigned -Scope CurrentUser` allows local-authored scripts and signed downloaded scripts — the standard dev setup. No `chmod` equivalent on Windows; on Linux/macOS pwsh, `.ps1` files need both `chmod +x` AND `#!/usr/bin/env pwsh` shebang.

cmd.exewindows
icacls script.bat /grant Everyone:RX

`.bat` and `.cmd` files are executable by default (NTFS execute bit is set when the file is created with those extensions). No `chmod +x` step. To restrict: `icacls script.bat /grant:r Everyone:R` (read-only, no execute). To "make" a `.txt` script executable, rename it to `.bat` or `.cmd` — extension drives execution, NOT a permission flag.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • Shebang lines (`#!/...`) MUST be the literal first two bytes of the file — no BOM, no leading whitespace, no comment block. UTF-8 BOM (3 bytes: 0xEF 0xBB 0xBF) at the start breaks the kernel's shebang parser silently (`./script.sh: cannot execute binary file`). Editors that save UTF-8 with BOM by default (Notepad pre-Windows 11, some Visual Studio configs) cause this. Save as "UTF-8 without BOM" or strip via `sed -i "1s/^\xEF\xBB\xBF//" script.sh`.
  • `chmod +x` without `chmod +r` is useless — the kernel needs to READ the script to interpret it. The two flags are usually applied together implicitly (`+x` against a `644`-permissioned file masks to add x → `755` effective for owner+group+other). On a `600` file, you need `chmod 700`.
  • pwsh ExecutionPolicy is NOT a security boundary — it stops accidental execution of unknown scripts, but trivially bypassed with `pwsh -ExecutionPolicy Bypass -File script.ps1`. Treat it as a "did you mean to run this" prompt, not a sandbox. For real script-trust enforcement, sign the script (`Set-AuthenticodeSignature`) and require `AllSigned`.
  • WSL bash sees the Linux `+x` bit on files in `/home/<user>`, but on `/mnt/c/...` files the bit is COMPUTED from extension + DrvFs metadata config. `.sh` files under `/mnt/c` may appear executable in `ls -l` (showing `-rwxrwxrwx`) but actually be denied execution. The fix: keep scripts inside the WSL filesystem (`~/scripts/`), not on `/mnt/c/`. Alternative: enable metadata in `/etc/wsl.conf` `[automount] options = "metadata"`.

Related commands

Related tasks