luaguides

Cross-Platform Scripting with Lua

Lua runs on everything from embedded devices to game engines to servers, making cross-platform scripting a core concern for anyone writing reusable Lua code. Writing scripts that work across operating systems requires handling a few platform differences: file paths, line endings, executable discovery, and environment variables. Lua’s standard library makes most of this manageable, but you need to know what to watch for.

File Paths

The biggest cross-platform headache in any language is path separators. Windows uses \, macOS and Linux use /. Lua’s standard library handles this inconsistently: io.open and dofile accept both separator styles, but some external tools do not.

Use / in Lua code. It works on all platforms that matter. The one exception is paths passed to Windows system calls — but if you’re calling Windows APIs directly, you’re already outside standard Lua territory.

-- Good: forward slashes work everywhere
dofile("scripts/config.lua")
io.open("data/player.txt")

-- Avoid: hardcoded backslashes
dofile("scripts\\config.lua")  -- breaks on Unix

When you need the platform-native separator at runtime — for instance, when building paths to pass to os.execute or when displaying a file path to the user in a platform-appropriate format — you can extract it from Lua’s built-in configuration string. The first character of package.config is always the directory separator for the host platform:

local sep = package.config:sub(1, 1)  -- "/" on Unix, "\" on Windows

This tiny snippet is one of the most useful cross-platform tricks in Lua — it lets your code construct paths that are always correct for the host system without any conditional branching. You can use it to join directory names or to split an incoming path into its components.

Paths to Executables

os.execute on Unix-like systems can find executables via PATH. On Windows, executable discovery relies on the PATHEXT environment variable and the current directory. If your script needs to call an external tool:

local function find_executable(name)
  local is_windows = package.config:sub(1, 1) == "\\"
  
  if is_windows then
    -- Try .exe, .bat, .cmd extensions
    local exts = { ".exe", ".bat", ".cmd", ".ps1" }
    for _, ext in ipairs(exts) do
      local full = name .. ext
      local handle = io.popen(full .. " 2>nul", "r")
      if handle then
        handle:close()
        return full
      end
    end
  else
    -- Unix: search PATH
    local handle = io.popen("which " .. name, "r")
    if handle then
      local result = handle:read("*a"):gsub("%s+$", "")
      handle:close()
      return result
    end
  end
  
  return nil
end

local lua_path = find_executable("lua")
print(lua_path)  -- /usr/bin/lua or C:\lua\lua.exe

Line Endings

Text files on Windows end with \r\n, Unix uses \n. This difference can corrupt configuration files, data exports, and any text output that another program will consume. When writing text files that other tools will read, normalise the line endings to match the host platform:

local function write_text(filename, content)
  local is_windows = package.config:sub(1, 1) == "\\"
  local eol = is_windows and "\r\n" or "\n"
  
  local file = io.open(filename, "w")
  if not file then error("cannot open " .. filename) end
  file:write(content:gsub("\n", eol))
  file:close()
end

Most Lua I/O functions treat both \r\n and \n as line endings on input, so reading Windows text files usually works without special handling. The problem is writing. If you generate a file with \n on Windows, Notepad will display it as one long line — use the normalisation function above whenever your output might land on a different platform than where it was created.

Detecting the Platform

local function get_platform()
  local config = package.config
  if config:sub(1, 1) == "\\" then
    return "windows"
  elseif config:sub(1, 1) == "/" then
    -- Could be Unix; check for known Unix indicators
    local handle = io.popen("uname -s 2>/dev/null", "r")
    if handle then
      local result = handle:read("*a"):gsub("%s+$", "")
      handle:close()
      return result:lower()
    end
    return "unix"
  end
  return "unknown"
end

print(get_platform())  -- "linux", "macosx", "windows"

package.config is the most reliable built-in signal. uname only works on Unix-like systems.

Handling environment variables

Environment variable names on Windows are case-insensitive; on Unix they are case-sensitive. Lua’s os.getenv and os.setenv work the same way on both platforms, but the variables themselves differ.

local home = os.getenv("HOME") or os.getenv("USERPROFILE")  -- Unix vs Windows
local temp = os.getenv("TMPDIR") or os.getenv("TEMP") or "/tmp"
local path_sep = package.config:sub(1, 1) == "\\" and ";" or ":"

Common environment variables that differ:

PurposeUnixWindows
Home directoryHOMEUSERPROFILE
Temporary filesTMPDIRTEMP
Path separator:;
PATH variablePATHPath

Directory Discovery

local function script_dir()
  -- Get the directory containing the currently running script
  local source = debug.getinfo(1, "S").source
  local path = source:match("^@(.*)") or source
  return path:match("(.*[/\\])") or "."
end

local function cwd()
  local handle = io.popen("cd", "r")
  local result = handle:read("*a"):gsub("%s+$", "")
  handle:close()
  return result
end

debug.getinfo(1, "S").source gives the script path. Strip the filename to get the directory. This works with both lua script.lua and lua /full/path/script.lua. Be aware that on Windows the path includes a drive letter, so the pattern matching above handles both forward slashes and backslashes when extracting the directory portion.

Using LuaFileSystem for portable cross-platform code

LuaFileSystem (lfs) provides portable filesystem operations:

local lfs = require("lfs")

-- Get platform-agnostic attributes
local attr = lfs.attributes("test.lua")
print(attr.mode, attr.size, attr.modification)

-- Iterate directories portably
for file in lfs.dir("/tmp") do
  if file ~= "." and file ~= ".." then
    print(file)
  end
end

-- Set file timestamps
lfs.touch("test.lua")  -- updates modification time to now

Install with luarocks install luafilesystem. Most Lua distributions on Windows include it by default. If your project cannot depend on external C libraries, the pure-Lua techniques shown earlier in this guide cover the most common cross-platform scripting needs without any dependencies.

See Also