A POSIX-compliant command-line skeleton with file locking and state persistence. Fully renameable - the tool adapts to whatever you name the binary.
- Strict POSIX compliance (uses
exprinstead of$((...)), no bashisms) - Configurable lock scope (per-user or system-wide)
- File-based locking with timeout support and PID ownership verification
- Persistent state management
- Modular architecture with library separation
- Automatic directory creation for locks and state files
- Support for both system-wide and per-user installation
- Self-installing - no build tools required
- Fully dynamic naming - rename
bin/posix-script-skeletonto anything by cloning the tool, all paths adapt automatically
- POSIX
/bin/sh - POSIX utilities only:
expr,basename,dirname,mkdir,rm,sleep,printf,cat,id
Non-POSIX dependencies: none
Important: The original posix-script-skeleton is a template. Use --clone to create your own tool:
./bin/posix-script-skeleton --cloneThis interactive command will:
- Ask for your tool name
- Ask for target directory
- Copy the skeleton with the new name
- Remove the skeleton marker (making it a standalone tool)
- Guide you through next steps
-
Navigate to your new tool:
cd /path/to/your-new-tool -
Edit the config to set your preferences:
vi config
Set
LOCK_SCOPEandSTATE_SCOPEto either"system"or"user" -
Implement your tool's logic:
- Your own logic and arg parsing for the new tool goes into
lib/core.shor other custom files you create - usage function in
lib/io.shcan be modified for help messages - Other files like lock, state, install, clone and bootstrap don't need modification unless you want to change core behavior
- Your own logic and arg parsing for the new tool goes into
-
Test your tool:
./bin/your-tool-name --help ./bin/your-tool-name
-
Install based on config:
# For user installation (LOCK_SCOPE="user") ./bin/your-tool-name --install # For system-wide installation (LOCK_SCOPE="system") sudo ./bin/your-tool-name --install
Note: The --install command is only available in cloned tools. The original skeleton must be cloned first using --clone.
-
Edit the config file to set your preferences:
vi config
Set
LOCK_SCOPEandSTATE_SCOPEto either"system"or"user" -
Install based on config:
# For user installation (LOCK_SCOPE="user") ./bin/<your-tool-name> --install # For system-wide installation (LOCK_SCOPE="system") sudo ./bin/<your-tool-name> --install
If your config has LOCK_SCOPE="system" or STATE_SCOPE="system":
sudo ./bin/<toolname> --installThis will install:
- Binary:
/bin/<toolname> - Config:
/etc/<toolname>/config - Libraries:
/etc/<toolname>/lib/ - State directory:
/var/tmp/<toolname>/ - Lock file:
/tmp/<toolname>.lock
If your config has LOCK_SCOPE="user" and STATE_SCOPE="user":
./bin/<toolname> --installThis will install:
- Binary:
~/.local/bin/<toolname> - Config:
~/.<toolname>rc - Libraries:
~/.local/lib/<toolname>/ - State directory:
~/.local/state/<toolname>/ - Lock file:
/tmp/<toolname>.<uid>.lock
Note: Make sure ~/.local/bin is in your PATH.
Note: The --uninstall command is only available from installed versions, not from source.
# From user installation
your-tool-name --uninstall
# From system-wide installation
sudo your-tool-name --uninstallNote: State directories are preserved during uninstallation. Remove them manually if needed.
For development, you can run directly from the source directory without installation:
./bin/<toolname>In development mode, the tool uses the local config file and stores state in data/.
The tool name adapts throughout the structure. Examples below use <toolname> as a placeholder for your actual tool name.
/bin/<toolname> Main binary
/etc/<toolname>/
├── config Configuration file
└── lib/ Library modules
├── bootstrap.sh
├── core.sh
├── install.sh
├── io.sh
├── lock.sh
└── state.sh
/var/tmp/<toolname>/ State files
└── <toolname>.state
/tmp/<toolname>.lock Lock file~/.local/bin/<toolname> Main binary
~/.<toolname>rc Configuration file
~/.local/lib/<toolname>/ Library modules
~/.local/state/<toolname>/ State files
└── <toolname>.state
/tmp/<toolname>.<uid>.lock Lock file<toolname>/
├── .skeleton-origin Marker (original skeleton only)
├── bin/
│ └── <toolname> Main entry point
├── config Configuration file
├── lib/
│ ├── bootstrap.sh Environment detection
│ ├── core.sh Core application logic
│ ├── clone.sh Clone functionality
│ ├── install.sh Installation logic
│ ├── io.sh Input/output utilities
│ ├── lock.sh File locking
│ └── state.sh State persistence
└── data/ Runtime data (auto-created)
└── <toolname>.<uid>.state Per-user state filesNote: The .skeleton-origin marker file identifies the original skeleton and is removed during the clone operation.
All configuration is done via the config file only. Environment variables do not override config settings.
The configuration file location depends on the mode:
- System-wide install: Config at
/etc/<toolname>/config - Per-user install: Config at
~/.<toolname>rc - Development mode: Config at
./config(in source directory)
| Variable | Values | Default | Description |
|---|---|---|---|
LOCK_SCOPE |
user / system |
user |
Lock scope: per-user or system-wide |
STATE_SCOPE |
user / system |
user |
State file scope |
LOCK_TIMEOUT |
seconds | 0 |
Lock timeout (0 = wait indefinitely) |
LOCK_NONBLOCK |
0 / 1 |
0 |
Exit immediately if locked (1) or wait (0) |
Important: Edit the config file directly. Environment variables cannot override these settings.
The tool automatically detects which config to use based on its installation:
- Development mode (running from source): Uses
./config - User installation: Uses
~/.<toolname>rc - System installation: Uses
/etc/<toolname>/config
Commands available depend on the tool type and mode:
./bin/posix-script-skeleton --clone # Create a new tool
./bin/posix-script-skeleton --help # Show help./bin/your-tool-name --install # Install the tool
./bin/your-tool-name --help # Show help
./bin/your-tool-name # Run the toolyour-tool-name --uninstall # Uninstall
your-tool-name --help # Show help
your-tool-name # Run the tool| Option | Description | Available In |
|---|---|---|
-h, --help |
Show help message | All modes |
--clone |
Clone skeleton with new name | Original skeleton only |
--install |
Install the tool | Cloned dev mode only |
--uninstall |
Uninstall the tool | Installed mode only |
# Clone the skeleton to create your tool
./bin/posix-script-skeleton --clone
# Test your cloned tool before installation
cd /path/to/your-tool
./bin/your-tool --help
./bin/your-tool
# Install as user (per-user locking)
./bin/your-tool --install
# After user installation
your-tool # Per-user lock: /tmp/your-tool.1000.lock
# Run as different users simultaneously (user mode)
your-tool & # User lock: /tmp/your-tool.1000.lock
sudo your-tool # Root lock: /tmp/your-tool.0.lock
# Uninstall when done
your-tool --uninstall- Each user gets isolated lock:
/tmp/<toolname>.<uid>.lock - Multiple users can run simultaneously without blocking
- State stored per-user:
~/.local/state/<toolname>/<toolname>.state - Lockfile contains process PID for ownership verification
- Single lock for all users:
/tmp/<toolname>.lock - Only one instance runs across entire system
- Root and users share the same lock
- State shared system-wide:
/var/tmp/<toolname>/<toolname>.state
- Automatically creates lock directory if missing
- Uses atomic file operations (
set -C) for race-free locking - PID-based ownership verification prevents lock theft
- Cleanup on EXIT, INT, TERM signals
- Maintains run counter across executions
- State scope configured via config file only
- System install:
/var/tmp/<toolname>/<toolname>.state - User install:
~/.local/state/<toolname>/<toolname>.state - Development mode:
./data/<toolname>.<uid>.state - Automatically initialized on first run
- Automatically creates state directory if missing
- Configure via
STATE_SCOPEin config file
This skeleton is strictly POSIX-compliant:
- ✅ All arithmetic uses
expr(not$((...))) - ✅ Command substitution uses
$(...)consistently - ✅ No arrays, no
[[ ]], no(( )) - ✅ No
localkeyword (uses underscore-prefix convention) - ✅ Uses
printfinstead ofechofor portability - ✅ Exit codes: 0 (success), 1 (error)
- ✅ Compatible with: dash, ash, ksh, and any POSIX shell
MIT License This is a skeleton/template project. Customize as needed for your use case.
