Standard library · System and tools

os/user

Who is running the program, and the standard directories that belong to them.

import os.user

The os.user package answers two questions: who is running the program (Username, Uid, Gid), and where that user's files belong (HomeDir, ConfigDir, CacheDir). Programs usually keep their settings in the user config directory and throwaway data in the user cache directory; this package gives the conventional path on each system, for instance ~/.config on Linux and %APPDATA% on Windows. Note that macOS also gets ~/.config and ~/.cache, the convention of command line tools, rather than ~/Library.

For the machine rather than the user, see os.platform; for reading other environment variables, env; for joining paths onto these folders, fs.path.

How the directories are found

These functions do not ask the operating system for a list of folders. They read environment variables, the same way most command line tools do, which also means that whoever starts the program can point them somewhere else:

FunctionLinux, macOS and BSDWindows
HomeDir$HOME%USERPROFILE%
ConfigDir$XDG_CONFIG_HOME, else ~/.config%APPDATA%
CacheDir$XDG_CACHE_HOME, else ~/.cache%LOCALAPPDATA%
Username$USER, else $LOGNAME, else the user database%USERNAME%

The example sets the variables itself with env.Set, so it prints the same on every Unix machine:

os-user-resolve.salam
import env
import os.user

func main:
    env.Set("HOME", "/home/sara")
    env.Unset("XDG_CONFIG_HOME")
    env.Unset("XDG_CACHE_HOME")
    println "home:  ", user.HomeDir()
    println "config:", user.ConfigDir()
    println "cache: ", user.CacheDir()
    env.Set("XDG_CONFIG_HOME", "/data/sara/config")
    env.Set("XDG_CACHE_HOME", "/fast-disk/cache")
    println "config:", user.ConfigDir()
    println "cache: ", user.CacheDir()
end
Outputhome: /home/sara config: /home/sara/.config cache: /home/sara/.cache config: /data/sara/config cache: /fast-disk/cache

Settings and cache for your own program

Give your program its own folder inside each of these directories, named after the program, and create it with dir.MkdirAll before the first write: on a fresh account even ~/.config may not exist yet. Settings belong in the config folder. Anything you can rebuild, such as downloads or a search index, belongs in the cache folder, which the user or a cleanup tool may empty at any time.

The first line of this example points HOME at a folder in the current directory so that running it does not touch your real settings. A real program leaves that line out.

os-user-app-dirs.salam
import env
import fs.dir
import fs.file
import fs.path
import os
import os.user

func main:
    env.Set("HOME", path.Join(os.Cwd(), "demo-home"))
    env.Unset("XDG_CONFIG_HOME")
    env.Unset("XDG_CACHE_HOME")
    config := path.Join(user.ConfigDir(), "notes")
    cache := path.Join(user.CacheDir(), "notes")
    dir.MkdirAll(config)
    dir.MkdirAll(cache)
    settings := path.Join(config, "settings.ini")
    repeat 2 in run:
        if file.Exists(settings):
            println "run", run + 1, "found", file.ReadText(settings).trim()
        else:
            file.WriteText(settings, "theme=dark\n")
            println "run", run + 1, "wrote the default settings"
        end
    end
    file.WriteText(path.Join(cache, "index.bin"), "0123456789")
    println "settings at ~/" + path.Rel(user.HomeDir(), settings)
    println "cache holds", dir.Size(cache), "bytes"
    dir.RemoveAll(user.HomeDir())
end
Outputrun 1 wrote the default settings run 2 found theme=dark settings at ~/.config/notes/settings.ini cache holds 10 bytes

Expanding ~ in paths from users

The shell turns ~/notes into a full path before your program sees it, but a path read from a settings file or typed at a prompt still starts with ~. Neither fs.file nor fs.path expands it, so replace it yourself with HomeDir:

os-user-expand-home.salam
import env
import fs.path
import os.user
import str

func expand(p: str): str:
    if p == "~": ret user.HomeDir() end
    if str.StartsWith(p, "~/"):
        ret path.Join(user.HomeDir(), p.substr(2, p.len() - 2))
    end
    ret p
end

func main:
    env.Set("HOME", "/home/sara")
    each p in ["~", "~/notes/today.md", "/etc/hosts", "reports/may.csv"]:
        println p, "->", expand(p)
    end
end
Output~ -> /home/sara ~/notes/today.md -> /home/sara/notes/today.md /etc/hosts -> /etc/hosts reports/may.csv -> reports/may.csv

Things to watch for

  • Username is fine for a greeting or a log line, but it comes from environment variables that anyone starting the program can change. Never use it to decide what someone is allowed to do.
  • Uid and Gid are -1 on Windows. On Unix a Uid of 0 means the program runs as root, which is worth a warning in a tool that should not.
  • When its variable is not set, for example in a service started with an empty environment, HomeDir returns an empty string, and ConfigDir and CacheDir on Unix then return the relative paths .config and .cache. Check HomeDir first; otherwise your files silently land in the current directory.

Functions

Username

func Username(): str
The name of the current user: $USER, else $LOGNAME, else the system user database; %USERNAME% on Windows. It can be changed by whoever starts the program.
os-user-Username.salam
import os.user

func main:
    println user.Username().len() > 0
    println user.Uid() >= 0, user.Gid() >= 0
    println user.HomeDir().len() > 0
    println user.ConfigDir().len() > 0, user.CacheDir().len() > 0
end
Outputtrue true true true true true

Uid

func Uid(): int
The numeric user id from the system, 0 for root; minus one on Windows.

The example for this item is shown with the ones above: os-user-Username.salam

Gid

func Gid(): int
The numeric group id of the user; minus one on Windows.

The example for this item is shown with the ones above: os-user-Username.salam

HomeDir

func HomeDir(): str
The home directory of the user, from $HOME (%USERPROFILE% on Windows); empty when the variable is not set.

The example for this item is shown with the ones above: os-user-Username.salam

ConfigDir

func ConfigDir(): str
The standard directory for program settings: $XDG_CONFIG_HOME or ~/.config, %APPDATA% on Windows. It may not exist yet.

The example for this item is shown with the ones above: os-user-Username.salam

CacheDir

func CacheDir(): str
The standard directory for program caches: $XDG_CACHE_HOME or ~/.cache, %LOCALAPPDATA% on Windows. It may not exist yet.

The example for this item is shown with the ones above: os-user-Username.salam