The os subcontext provides filesystem operations and information about the machine running your program. Its results are ordinary Rye values: paths, blocks, dictionaries, tables, and native objects. We can combine them with map, filter, functions, and conditions to build small tools without shell pipelines.
These examples need a native Rye build with OS support:
rye .Needs { os }
os/cwd? |print
We use os/name to call a word in the subcontext without switching contexts. File paths such as %reports are file URIs, not strings. file converts a string to a file URI, while os/join-path joins path components. Use [ ... ] when those components contain expressions that need evaluating.
Each recipe below is independent. Run file-oriented examples beside your sample files. To avoid surprises when combining operations, resolve the starting directory once with os/realpath, then build paths from it rather than changing directory with os/cd.
A reporting script often needs a configurable output directory, with a useful default for local runs.
What’s new: env? realpath mkdir-p join-path
rye .Needs { os }
output: os/env? "REPORT_DIR" |fix { "./reports" } |file
root: os/realpath output
os/mkdir-p root |^check "could not create report directory"
report: os/join-path [ root "run.txt" ]
Write report "Report generation started.\n"
|^check "could not write report"
print [ "Report written to:" report ]
env? fails when the variable is absent; fix supplies the fallback. mkdir-p creates missing parent directories and accepts an existing directory. This example creates or overwrites run.txt; an existing empty REPORT_DIR is not treated as an absent variable.
For example, in a POSIX shell:
REPORT_DIR=/tmp/my-reports rye report.rye
Before reviewing or packaging a project, list the Markdown files changed during the last two days, together with their sizes and modification dates.
What’s new: finder Name Mtime-since! file-info?
rye .Needs { os }
do\in os {
root: os/realpath %.
recent: finder root
|Type "f"
|Name "*.md"
|Exclude-path ".git"
|Mtime-since! 2 .days
|Eval |^check "could not search the project"
recent
|map fn { path } {
info: file-info? path
[ path info."size" info."mod-time" ]
}
|table\rows* { "Path" "Bytes" "Modified" }
|print
}
The finder is a query builder: each filter returns the same finder, and Eval performs the search and returns a block of file URIs. Here "f" selects files, Name matches the basename, and the modification interval is in milliseconds, compatible with Rye duration words such as 2 .days or 1 .hours. The interval is truncated to whole seconds.
map turns each path into a row, then table\rows* supplies the column names. The function gives each file its own local info binding. Keep the full path in the report so identically named files in different directories remain distinguishable.
Change "*.md" to "*.log" to inspect logs. To investigate large files instead, replace the name and time filters with |Size> 10485760 (larger than 10 MiB). Exclude-path filters matching results; it should not be treated as a guarantee that excluded directories won’t be traversed.
Suppose a directory contains CSV exports alongside other files. Stage only the CSV files in a new temporary directory, then create a ZIP archive of that staging directory.
What’s new: glob is-file mktemp cp zip
rye .Needs { os }
do\in os {
files: glob "*.csv" |filter { ::path is-file path }
^ensure files .length? > 0 "No CSV files found"
work: mktemp |^check "could not create temporary directory"
staging: join-path [ work "exports" ]
mkdir staging |^check "could not create staging directory"
for files { ::source
cp source join-path [ staging basename source ]
|^check "could not stage a CSV file"
}
archive: join-path [ work "exports.zip" ]
zip staging archive |^check "could not create archive"
print [ "Archive:" archive ]
print [ "Archive size in bytes:" file-size archive ]
}
The original files are only read. The archive lives outside the directory being archived, so it cannot include itself. A fresh temporary directory also avoids overwriting an earlier export.
This is a non-recursive selection from one directory, so basenames do not collide. If you extend it to a recursive search, preserve relative directories rather than flattening all filenames into one folder.
The temporary directory is deliberately left in place so you can retrieve the archive. After moving the archive somewhere permanent, you can remove the printed temporary directory. Be careful: both os/rm-rf and os/rmdir recursively remove contents; os/rm removes only a file or an empty directory. If the recipe fails midway, the staging files are also left for inspection.
A lightweight health check can combine host information with memory usage and ordinary Rye conditions.
What’s new: host-info? virtual-memory? load-avg?
rye .Needs { os }
host: os/host-info? |^check "could not read host information"
memory: os/virtual-memory? |^check "could not read memory information"
print [ "Host:" host."hostname" ]
print [ "Platform:" host."platform" host."platform-version" ]
print [ "Uptime in seconds:" host."uptime" ]
print [ "Memory used (%):" memory."used-percent" ]
either memory."used-percent" > 90 {
print "Warning: memory usage is above 90%."
} {
print "Memory usage is within the warning threshold."
}
; Load averages are not available on every platform.
os/load-avg?
|fix { dict { "1" "unavailable" "5" "unavailable" "15" "unavailable" } }
|print
The memory dictionary also contains total and free, in bytes. Load averages use the dictionary keys "1", "5", and "15" for their time windows in minutes; they are not CPU percentages.
This is a snapshot, not a monitoring service. The 90% threshold is just an example, not a diagnosis of memory pressure. You could run the script periodically, or combine its checks with the MQTT recipes to publish a status message.
When several copies of a program are running, list their PIDs and executable paths before deciding which one needs attention. This recipe only inspects processes; it does not stop them.
What’s new: processes? Name? Pid? Exe?
rye .Needs { os }
needle: "rye"
os/processes? |^check "could not list processes"
|filter {
.Name? |fix { "" } |contains needle
}
|map fn { proc } {
[ proc .Pid?
proc .Name? |fix { "<unavailable>" }
proc .Exe? |fix { "<unavailable>" } ]
}
|table\rows* { "PID" "Name" "Executable" }
|print
Change needle to part of the process name you want to find. Matching is case-sensitive. processes? returns native process objects; their generic methods retrieve individual details as needed.
A process can disappear between enumeration and inspection, and permissions can prevent reading its executable path. The fix blocks let the report continue in those cases.
To inspect just the current Rye process, use os/process? os/pid?. Other available methods include Ppid?, Username?, and Memory-info?. Avoid publishing complete command lines in reports: they can contain passwords or tokens.