shell — GitHub Copilot rule for safe bash scripts
A rule that makes GitHub Copilot generate clean, safe shell scripts. Applies to *.sh files automatically.
.github/instructions/shell.instructions.mdWhat shell does
The rule covers bash, sh, and zsh conventions: set -euo pipefail, double-quoted variable references, mktemp for temp files, trap for cleanup. Copilot follows these when generating code in *.sh files.
For JSON and YAML, the rule demands jq and yq instead of ad-hoc grep or awk. Scripts should fail with a clear message when these tools are missing — that is built into the instructions.
Scripts written under this rule target automation and testing, not production. So verbose logging is discouraged and echo output should stay short.
What it helps with
- make Copilot add
set -euo pipefailto every script - generate scripts with safe variable expansions instead of
eval - parse JSON and YAML with
jq/yqinstead ofgrep - create temp files via
mktempwith automatic cleanup - keep a consistent script layout: shebang, header, functions, main
Example prompts for the agent
- generate a backup script following the shell rule
- write a bash script that reads config.json and validates fields
- add error handling and temp file cleanup to this script
How to install shell
Manually: put the files in place
.github/instructions/shell.instructions.mdThe applyTo field sets which files it applies to
Manually: put the files in place
.claude/rules/shell.mdThe paths frontmatter field limits the files: move applyTo from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.cursor/rules/shell.mdcFrontmatter: description, globs, alwaysApply. Move applyTo from the source into globs, or the rule will not attach on its own
Manually: put the files in place
.devin/rules/shell.mdManually: put the files in place
.clinerules/shell.mdStep by step, with commands for each platform: AGENTS.md vs CLAUDE.md: what they are and which one to use
Questions about shell
How do I install the shell rule in GitHub Copilot?
Create .github/instructions/shell.instructions.md in your repository root and paste the rule content there. Copilot will pick it up for all *.sh files.
What does the shell rule do?
It sets standards for generated shell scripts: mandatory set -euo pipefail, safe expansions, mktemp for temp files, jq/yq for structured data.
What does the rule require?
Scripts that parse JSON or YAML need jq and yq. The rule says to check for them and fail with a helpful message if they are not installed.
Source text
shell.instructions.md · MIT
---
description: 'Shell scripting best practices and conventions for bash, sh, zsh, and other shells'
applyTo: '**/*.sh'
---
# Shell Scripting Guidelines
Instructions for writing clean, safe, and maintainable shell scripts for bash, sh, zsh, and other shells.
## General Principles
- Generate code that is clean, simple, and concise
- Ensure scripts are easily readable and understandable
- Add comments where helpful for understanding how the script works
- Generate concise and simple echo outputs to provide execution status
- Avoid unnecessary echo output and excessive logging
- Use shellcheck for static analysis when available
- Assume scripts are for automation and testing rather than production systems unless specified otherwise
- Prefer safe expansions: double-quote variable references (`"$var"`), use `${var}` for clarity, and avoid `eval`
- Use modern Bash features (`[[ ]]`, `local`, arrays) when portability requirements allow; fall back to POSIX constructs only when needed
- Choose reliable parsers for structured data instead of ad-hoc text processing
## Error Handling & Safety
- Always enable `set -euo pipefail` to fail fast on errors, catch unset variables, and surface pipeline failures
- Validate all required parameters before execution
- Provide clear error messages with context
- Use `trap` to clean up temporary resources or handle unexpected exits when the script terminates
- Declare immutable values with `readonly` (or `declare -r`) to prevent accidental reassignment
- Use `mktemp` to create temporary files or directories safely and ensure they are removed in your cleanup handler
## Script Structure
- Start with a clear shebang: `#!/bin/bash` unless specified otherwise
- Include a header comment explaining the script's purpose
- Define default values for all variables at the top
- Use functions for reusable code blocks
- Create reusable functions instead of repeating similar blocks of code
- Keep the main execution flow clean and readable
## Working with JSON and YAML
- Prefer dedicated parsers (`jq` for JSON, `yq` for YAML—or `jq` on JSON converted via `yq`) over ad-hoc text processing with `grep`, `awk`, or shell string splitting
- When `jq`/`yq` are unavailable or not appropriate, choose the next most reliable parser available in your environment, and be explicit about how it should be used safely
- Validate that required fields exist and handle missing/invalid data paths explicitly (e.g., by checking `jq` exit status or using `// empty`)
- Quote jq/yq filters to prevent shell expansion and prefer `--raw-output` when you need plain strings
- Treat parser errors as fatal: combine with `set -euo pipefail` or test command success before using results
- Document parser dependencies at the top of the script and fail fast with a helpful message if `jq`/`yq` (or alternative tools) are required but not installed
```bash
#!/bin/bash
# ============================================================================
# Script Description Here
# ============================================================================
set -euo pipefail
cleanup() {
# Remove temporary resources or perform other teardown steps as needed
if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
# Default values
RESOURCE_GROUP=""
REQUIRED_PARAM=""
OPTIONAL_PARAM="default-value"
readonly SCRIPT_NAME="$(basename "$0")"
TEMP_DIR=""
# Functions
usage() {
echo "Usage: $SCRIPT_NAME [OPTIONS]"
echo "Options:"
echo " -g, --resource-group Resource group (required)"
echo " -h, --help Show this help"
exit 0
}
validate_requirements() {
if [[ -z "$RESOURCE_GROUP" ]]; then
echo "Error: Resource group is required"
exit 1
fi
}
main() {
validate_requirements
TEMP_DIR="$(mktemp -d)"
if [[ ! -d "$TEMP_DIR" ]]; then
echo "Error: failed to create temporary directory" >&2
exit 1
fi
echo "============================================================================"
echo "Script Execution Started"
echo "============================================================================"
# Main logic here
echo "============================================================================"
echo "Script Execution Completed"
echo "============================================================================"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-g|--resource-group)
RESOURCE_GROUP="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Execute main function
main "$@"
```
© github · MIT · github/awesome-copilot