Naming conventions

This document describes the naming standard that has emerged across lib/sh/.


Core principle

The module prefix is the namespace. Everything after it describes what the function does, not that it retrieves something. get_ is not a verb — it is noise.

This mirrors the pattern used in modern standard libraries:

Language Local read Network I/O
Python psutil net_if_addrs()
Node.js os.networkInterfaces() dns.lookup()
Go net net.Interfaces() net.LookupHost()

The shell equivalent: plain noun for local state, descriptive infix for I/O operations.


Patterns

<module>_<noun> — local passive read

Reads local system state. No network calls, no blocking I/O. Failure means the platform is unusual or a required tool is absent.

sys_shell        # what shell is running?
sys_mounts       # what is mounted?
net_ip           # local IP address
net_mac          # MAC address of primary interface
net_dns          # configured DNS servers

<module>_<noun>_<attr> — attribute of a domain

Sub-functions under a domain noun. Always callable directly; also reachable via the dispatcher.

sys_cpu_cores
sys_cpu_mhz
sys_mem_total
sys_mem_percent
sys_swap_used
sys_info_manufacturer
sys_info_serial

<module>_<noun> as dispatcher

The plain noun form doubles as the dispatcher for its attribute functions. With no argument it prints a summary. With a flag or subcommand it routes to the appropriate helper.

sys_cpu                    # summary: manufacturer, model, mhz, count
sys_cpu cores              # single attribute
sys_mem total -M           # single attribute with unit flag
sys_info                   # all: manufacturer, model, serial, bios
sys_info --serial          # single attribute
sys_hogs                   # all three hog reports
sys_hogs mem 20            # single report with line count

The subcommand style (sys_cpu cores) is used where the noun has many attributes and a flag would be unwieldy. The flag style (sys_info --serial) is used where the domain is small or the attributes are heterogeneous.

<module>_query_<noun> — active outbound query

Makes a network call. Failure modes are qualitatively different from local reads: firewalls, DNS, timeouts, service outages.

net_query_ip               # what is my public IP?
net_query_ipinfo 8.8.8.8   # geo/org metadata for an IP
net_query_port host 443    # is this port reachable?
net_query_http_code url    # HTTP status of a URL

net/ local vs query distinction

Functions in net/ follow both patterns, which reflect a meaningful difference in what the function does:

Pattern What it does Examples
net_<noun> Reads local system state — no network I/O net_ip, net_mac, net_dns
net_query_<noun> Makes an outbound network call net_query_ip, net_query_port, net_query_ipinfo

Local reads (net_ip, net_mac) fail only when the platform is unusual or a required tool is absent. Network queries can fail because of firewalls, DNS, timeouts, or service outages. Mixing them under a single verb would obscure that difference.

net_dns_resolve is a deliberate exception: it fires a DNS query outbound but lives in dns.sh as a DNS-domain operation rather than a general query. If a full net_query dispatcher is ever built, it would route internally to net_dns_resolve.

<module>_info_<attr> — hardware identity

Hardware identity attributes are a special case of _attr where the parent noun is info — the machine's self-description. These live in sys/info.sh and are dispatched by sys_info.

sys_info_manufacturer
sys_info_model
sys_info_serial
sys_info_bios

svc_<verb> — service control

Service lifecycle operations use a svc_ prefix rather than sys_ because they are imperative (they change state) rather than descriptive. The verb is mandatory.

svc_start nginx
svc_restart nginx
svc_status nginx
svc_enabled nginx
svc_active nginx

_<module>_<noun> — internal helpers

Functions prefixed with _ are private to their file. Not part of the public API; may change without notice.

_cpuhogs_print_fmt
_mem_read
_mem_convert_kb
_swaphogs_get_proc_info

is_<condition> — boolean predicates

Return 0 (true) or 1 (false). No output to stdout. Used in conditionals.

is_aws
is_azure
is_debian_like
is_redhat_like

These have not yet been given a module prefix. Candidates: sys_is_aws, sys_is_azure. Deferred.

str_*, text_*, line_* — string, display, and stream operations

These three prefixes encode a meaningful distinction:

  • str_ — operates on a string value in memory. Input is an argument, output is a transformed value. Character-level. No terminal assumptions. str_toupper, str_replace, str_ucfirst, str_snake_case.
  • line_ — operates on a stream of lines. Input is stdin or a file, output is line-structured. line_first, line_indent, line_filter.
  • text_ — applies display or presentation formatting, typically for terminal output. ANSI colour and styling (text_bold, text_fg), layout (text_center, text_wordwrap). These functions assume a visual context.

The test: would this function make sense in a non-terminal, non-display context? Case conversion — yes, str_toupper. Centering a line to terminal width — no, text_center.

Functions that transform string values belong in str_* regardless of where they were originally written. text_* aliases may be kept for backward compatibility but are not canonical. Display-oriented wrappers around str_* or line_* functions may carry text_* aliases where callers would naturally reach for that prefix.


File naming

Files follow the same noun-first rule: <noun>.sh, no get_ prefix, no verb. The filename should match the primary function or dispatcher it contains.

sys/cpu.sh            → sys_cpu, sys_cpu_*
sys/mem.sh            → sys_mem, sys_mem_*, sys_swap, sys_swap_*
sys/info.sh           → sys_info, sys_info_*
sys/hogs.sh           → sys_hogs (dispatcher over cpuhogs/memhogs/swaphogs)
net/query.sh          → net_query_*
net/dns.sh            → net_dns, net_dns_resolve
fs/stat_file.sh       → fs_stat, fs_file_age, whoowns
fs/permissions.sh     → fs_permissions
units/temperature.sh  → celsius_to_fahrenheit, temp_convert, … (see exceptions)
units/permissions.sh  → octal_to_rwx, rwx_to_octal, permissions_convert

Module layout patterns

When a module's functions fit naturally into a single file, name it base.sh. The include then reads as include <module>/base, which is unambiguous and avoids the tautological include path/path or include git/git.

When the module splits cleanly along read/write lines, use two files named to reflect that split. The default pair is:

  • inspect.sh — read-only queries, predicates, introspection. No state is modified.
  • manage.sh — write operations, idempotent create/update/delete.

Use domain-appropriate names where something more expressive fits. users/query.sh + users/provision.sh is clearer than users/inspect.sh + users/manage.sh in that context. The inspect/manage pair is the fallback when nothing better presents itself.

Literal read/write, verb-only names (get, set), and HTTP methods (GET, POST) are all rejected — they are either too close to reserved words or carry the wrong connotations.

git/base.sh           → single-file module; all git_* functions
path/base.sh          → single-file module; all path_* functions
users/query.sh        → read-only: user_uid, user_gid, user_accounts …
users/provision.sh    → write/idempotent: ensure_user_exists, ensure_group_exists …

Sentinel variables

Sentinel variables follow the file path, not the function names:

_SHELLAC_LOADED_<dir>_<filename_without_extension>

Examples:

_SHELLAC_LOADED_sys_cpu
_SHELLAC_LOADED_sys_info
_SHELLAC_LOADED_net_query
_SHELLAC_LOADED_net_dns

When a file is renamed or moved, the sentinel must be updated to match the new path.


Named exceptions

Some functions are exempt from the module-prefix rule where the prefix would be tautological or the short form is clearly the better name.

Function File Why exempt
toarray array/toarray.sh array_toarray reads as a stutter; the filename already provides the namespace
mapfile array/mapfile.sh Deliberate shadow of the bash builtin; must match the builtin name to act as a drop-in
cpuhogs, memhogs, swaphogs sys/*.sh Short, well-known names; sys_cpuhogs adds no clarity. sys_hogs is the namespaced entry point.
celsius_to_fahrenheit, octal_to_rwx, etc. units/*.sh The <unit>_to_<unit> pattern is self-describing; a module prefix adds nothing. units_celsius_to_fahrenheit is worse in every way.
whoowns fs/stat_file.sh fs_whoowns adds no clarity; the name reads as a natural English question. Thin wrapper over fs_stat owner.
greet misc/greet.sh misc_greet adds nothing; the function is a self-contained imperative with no attribute to qualify.
validate_config utils/validate_config.sh Parked pending a decision on where config validation belongs. May move to a config/ module or gain a util_ prefix in a future pass.
secrets_genpasswd, secrets_genphrase crypto/genpasswd.sh, crypto/genphrase.sh Password/passphrase generation lives in crypto/ under the secrets_* sub-namespace (cf. Go crypto/rand, Python secrets). Short forms genpasswd and genphrase are kept as aliases.
confirm utils/prompt.sh Alias for prompt_confirm; kept for natural English readability and backwards compatibility. Lives alongside prompt_response and prompt_password in the prompt cluster.
detect_type core/types.sh Every language converges on type() or typeof for this concept. The shell builtin type is already taken (command lookup), making detect_type the closest available form. core_detect_type adds no clarity.
readlist array/readlist.sh Deliberately echoes the readarray/mapfile builtin naming. array_readlist would be a stutter. Defaults to READLIST as the target array, mirroring mapfileMAPFILE.
int numbers/numeric.sh Alias for num_parse; equivalent to Go's strconv.Atoi. num_int would be a stutter; the short form is the universal cross-language name.
is_integer, is_float, is_numeric, is_positive_integer numbers/numeric.sh Backward-compatible aliases for num_is_integer --regex, num_is_float --regex, num_is_numeric, num_is_positive_integer. The is_* names are the canonical public predicates; num_is_* exposes the additional --regex flag.

Note: math_ceiling, math_floor, math_round, math_trunc (numbers/rounding.sh) use math_ rather than num_. Python, Go, and JavaScript all converge on a math namespace for these operations; num_ is the canonical project prefix but math_ is the cross-language convention and wins here. The short forms (ceiling, floor, round, trunc) are kept as aliases.

Note: sum and average (numbers/sum.sh) are stream aggregators rather than unary math operations. No language puts these in a math.* namespace. They stay as named exceptions; array_sum in the array module handles the array case.

Note: strict_euopipefail, strict_nowhitesplitting (core/) are intentional exceptions. The project discourages set -e / pipefail patterns, but these are provided for users who want them. The strict_ prefix is the namespace; no module prefix is added.

Note: cmd_check, cmd_list (utils/cmd.sh) use cmd_ rather than util_cmd_. This is a deferred decision — cmd_ reads naturally and is unambiguous, but may gain a util_ prefix if the utils/ module develops a broader convention. Revisit when other utils/ functions are reviewed.

Note: functions in units/ that read filesystem or system state are not exempt — those belong in the appropriate module. get_permissions() was moved to fs/permissions.sh as fs_permissions() for this reason. The boundary is: if the function converts between representations, it lives in units/; if it reads external state to produce a value, it belongs in fs/, sys/, or net/.

The test: if adding the prefix makes the name longer without making it clearer, the short form is the right call.


What we explicitly rejected

Pattern Reason
get_<noun> POSIX/C legacy. No semantic value. Dropped everywhere.
get_<domain>info_<attr> info infix is noise; the module prefix is sufficient context.
net_get_<noun> PowerShell-style verbosity. The absence of query_ already implies local/passive.
<noun>::<verb> Double-colon namespace is not idiomatic shell.