A Python-based linter that checks shell scripts against RRFS coding norms.
# Lint everything in the current directory (recursive)
linter_rrfs_code_norms.py .
# Lint specific files
linter_rrfs_code_norms.py scripts/exrrfs_fcst.sh jobs/JRRFS_FCST
# List all rules
linter_rrfs_code_norms.py --list-rules
# Running with no arguments prints the help message
linter_rrfs_code_norms.py- Python 3.10+ (uses
list[str]type hints and dataclasses) - No external dependencies
| ID | Severity | Description |
|---|---|---|
| RRFS001 | warning | Use source instead of . for sourcing files |
| RRFS002 | error | Use [[ instead of [ |
| RRFS003 | error | Use == instead of = for string comparison in [[ ]] |
| RRFS004 | warning | Use -s instead of -f to check if a file exists and is not size zero |
| RRFS005 | warning | Use ${NDATE} for date arithmetic, not date -d (formatting existing dates with date -d is allowed) |
| RRFS006 | error | Use 2 spaces for indentation; no TABs |
| RRFS007 | error | Exported variables must start with uppercase (exception: err, pgm) |
| RRFS008 | error | Use :- (not : alone) for default values in ${VAR:-default} |
| RRFS009 | warning | Use ${var} instead of $var (except shell specials $?, $!, $@, etc.) |
| RRFS010 | warning | Use (( )) instead of [[ ]] for arithmetic operations |
| RRFS011 | error | Double-quote variables in -z/-n tests: -z "${var}" |
| RRFS012 | error | Job files (jobs/) must start with the required header (comments between shebang and header lines are allowed) |
| RRFS013 | error | Script files (scripts/) must start with the required header (comments between shebang and header lines are allowed) |
| RRFS014 | error | Use $(command) instead of backticks |
| RRFS015 | warning | Use true/false without quotes in assignments (only flags ="true" and ="false", not comparisons) |
| RRFS016 | warning | Use ${var^^} to uppercase before comparing to TRUE/FALSE/YES/NO |
| RRFS017 | warning | Use standard names: PDY not YYYYMMDD, cyc not HH, subcyc not MM, CDATE not YYYYMMDDHH |
| RRFS018 | error | Call Python scripts directly with a shebang instead of python script.py |
usage: linter_rrfs_code_norms [-h] [--format {default,compact,json}] [--disable RULES]
[--enable RULES] [--no-recursive] [--list-rules]
[--severity {all,error,warning}] [paths ...]
positional arguments:
paths Files or directories to check (default: .)
options:
--format, -f Output format: default, compact, json
--disable RULES Comma-separated rule IDs to disable globally
--enable RULES Only run these rules (comma-separated)
--no-recursive Don't recurse into directories
--list-rules List all available rules and exit
--severity LEVEL Filter: all, error, warning
# Only check for tab and bracket issues
linter_rrfs_code_norms.py --enable RRFS002,RRFS006 myscript.sh
# Skip the -f vs -s rule
linter_rrfs_code_norms.py --disable RRFS004 .
# JSON output for CI integration
linter_rrfs_code_norms.py --format json scripts/ > lint_results.json
# Only show errors (skip warnings)
linter_rrfs_code_norms.py --severity error .Like shellcheck, you can suppress rules inline in your code.
. /etc/profile # rrfslint: disable=RRFS001# rrfslint: disable-next-line=RRFS002
if [ -d /tmp ]; thenMust appear in the initial comment block before any code:
#!/usr/bin/env bash
# rrfslint: file-disable=RRFS006,RRFS009#!/usr/bin/env bash
# rrfslint: file-disable=allThis skips the file entirely — no rules are checked.
Comma-separate rule IDs:
echo $foo # rrfslint: disable=RRFS009,RRFS014/path/to/file.sh:6:1: warning RRFS001: Use 'source' instead of '.' for better readability.
. /etc/profile
Suggestion: source /etc/profile
/path/to/file.sh:6:1: [RRFS001] Use 'source' instead of '.' for better readability.
[
{
"file": "/path/to/file.sh",
"line": 6,
"column": 1,
"rule": "RRFS001",
"severity": "warning",
"message": "Use 'source' instead of '.' for better readability.",
"suggestion": "source /etc/profile",
"source": ". /etc/profile"
}
]The exit code is 1 if any violations are found, 0 if clean. Use this in CI pipelines:
linter_rrfs_code_norms.py --format compact scripts/ jobs/
if [[ $? -ne 0 ]]; then
echo "RRFS code norm linting failed"
exit 1
fiSample test files are provided in tests/:
# Run against the intentionally bad example
linter_rrfs_code_norms.py tests/bad_example.sh
# Run against the suppression examples
linter_rrfs_code_norms.py tests/suppressed_example.sh
linter_rrfs_code_norms.py tests/file_suppressed_example.sh
# Run against the good examples (should pass clean)
linter_rrfs_code_norms.py tests/jobs/ tests/scripts/