Operating System Interfaces in Go
Introduction
Go provides powerful interfaces to interact with the operating system. This guide covers system calls, environment variables, and cross-platform programming.
Understanding OS interfaces enables you to build system utilities, manage processes, and interact with system resources effectively.
Environment Variables
Basic Environment Operations
package main
import (
"fmt"
"os"
"strings"
)
// GetEnv gets an environment variable
func GetEnv(key string) string {
return os.Getenv(key)
}
// GetEnvOrDefault gets environment variable with default
func GetEnvOrDefault(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultValue
}
// SetEnv sets an environment variable
func SetEnv(key, value string) error {
return os.Setenv(key, value)
}
// UnsetEnv unsets an environment variable
func UnsetEnv(key string) error {
return os.Unsetenv(key)
}
// GetAllEnv gets all environment variables
func GetAllEnv() map[string]string {
env := make(map[string]string)
for _, pair := range os.Environ() {
parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 {
env[parts[0]] = parts[1]
}
}
return env
}
// Example usage
func EnvironmentExample() {
// Get environment variable
home := GetEnv("HOME")
fmt.Printf("Home: %s\n", home)
// Get with default
debug := GetEnvOrDefault("DEBUG", "false")
fmt.Printf("Debug: %s\n", debug)
// Set environment variable
SetEnv("MY_VAR", "my_value")
// Get all environment variables
allEnv := GetAllEnv()
fmt.Printf("Total env vars: %d\n", len(allEnv))
}
Good: Proper OS Interface Implementation
package main
import (
"fmt"
"os"
"os/signal"
"os/user"
"path/filepath"
"runtime"
"syscall"
"time"
)
// OSInfo provides OS information
type OSInfo struct {
OS string
Arch string
Hostname string
User string
HomeDir string
TempDir string
}
// GetOSInfo gets OS information
func GetOSInfo() (*OSInfo, error) {
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
currentUser, err := user.Current()
if err != nil {
return nil, err
}
return &OSInfo{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
Hostname: hostname,
User: currentUser.Username,
HomeDir: currentUser.HomeDir,
TempDir: os.TempDir(),
}, nil
}
// ProcessInfo provides process information
type ProcessInfo struct {
PID int
PPID int
Args []string
Env map[string]string
WorkDir string
StartTime time.Time
}
// GetProcessInfo gets current process information
func GetProcessInfo() *ProcessInfo {
env := make(map[string]string)
for _, pair := range os.Environ() {
parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 {
env[parts[0]] = parts[1]
}
}
wd, _ := os.Getwd()
return &ProcessInfo{
PID: os.Getpid(),
PPID: os.Getppid(),
Args: os.Args,
Env: env,
WorkDir: wd,
}
}
// SignalHandler handles OS signals
type SignalHandler struct {
signals chan os.Signal
done chan bool
}
// NewSignalHandler creates a new signal handler
func NewSignalHandler() *SignalHandler {
return &SignalHandler{
signals: make(chan os.Signal, 1),
done: make(chan bool),
}
}
// Handle handles signals
func (sh *SignalHandler) Handle(callback func(os.Signal)) {
signal.Notify(sh.signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
for {
select {
case sig := <-sh.signals:
callback(sig)
case <-sh.done:
return
}
}
}()
}
// Stop stops signal handling
func (sh *SignalHandler) Stop() {
signal.Stop(sh.signals)
close(sh.done)
}
// FilePermissions manages file permissions
type FilePermissions struct {
path string
}
// NewFilePermissions creates new file permissions manager
func NewFilePermissions(path string) *FilePermissions {
return &FilePermissions{path: path}
}
// GetPermissions gets file permissions
func (fp *FilePermissions) GetPermissions() (os.FileMode, error) {
info, err := os.Stat(fp.path)
if err != nil {
return 0, err
}
return info.Mode().Perm(), nil
}
// SetPermissions sets file permissions
func (fp *FilePermissions) SetPermissions(mode os.FileMode) error {
return os.Chmod(fp.path, mode)
}
// IsReadable checks if file is readable
func (fp *FilePermissions) IsReadable() bool {
_, err := os.Open(fp.path)
return err == nil
}
// IsWritable checks if file is writable
func (fp *FilePermissions) IsWritable() bool {
info, err := os.Stat(fp.path)
if err != nil {
return false
}
return info.Mode().Perm()&0200 != 0
}
// IsExecutable checks if file is executable
func (fp *FilePermissions) IsExecutable() bool {
info, err := os.Stat(fp.path)
if err != nil {
return false
}
return info.Mode().Perm()&0111 != 0
}
// Example usage
func OSInterfaceExample() {
// Get OS info
osInfo, _ := GetOSInfo()
fmt.Printf("OS: %s/%s\n", osInfo.OS, osInfo.Arch)
fmt.Printf("User: %s\n", osInfo.User)
// Get process info
procInfo := GetProcessInfo()
fmt.Printf("PID: %d\n", procInfo.PID)
// Handle signals
handler := NewSignalHandler()
handler.Handle(func(sig os.Signal) {
fmt.Printf("Received signal: %v\n", sig)
})
// Check file permissions
perms := NewFilePermissions("test.txt")
readable := perms.IsReadable()
fmt.Printf("Readable: %v\n", readable)
}
Bad: Improper OS Interface Usage
package main
// BAD: No error handling
func BadGetEnv(key string) string {
return os.Getenv(key) // Could be empty
}
// BAD: No signal handling
func BadSignalHandling() {
// No signal handling
// Process can't be interrupted gracefully
}
// BAD: No permission checking
func BadFileAccess(path string) {
// No permission checks
// Could fail at runtime
}
Problems:
- No error handling
- No signal handling
- No permission checking
- No cross-platform consideration
Cross-Platform Programming
package main
import (
"os"
"path/filepath"
"runtime"
)
// GetConfigDir gets platform-specific config directory
func GetConfigDir() string {
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("APPDATA"), "MyApp")
}
home, _ := os.UserHomeDir()
return filepath.Join(home, ".config", "myapp")
}
// GetDataDir gets platform-specific data directory
func GetDataDir() string {
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("APPDATA"), "MyApp", "Data")
}
home, _ := os.UserHomeDir()
return filepath.Join(home, ".local", "share", "myapp")
}
// GetLogDir gets platform-specific log directory
func GetLogDir() string {
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("APPDATA"), "MyApp", "Logs")
}
return "/var/log/myapp"
}
// GetExecutablePath gets executable path
func GetExecutablePath() (string, error) {
return os.Executable()
}
// GetWorkingDirectory gets working directory
func GetWorkingDirectory() (string, error) {
return os.Getwd()
}
// ChangeWorkingDirectory changes working directory
func ChangeWorkingDirectory(path string) error {
return os.Chdir(path)
}
Best Practices
1. Always Check Errors
if err := os.Setenv(key, value); err != nil {
return err
}
2. Handle Signals Gracefully
handler := NewSignalHandler()
handler.Handle(func(sig os.Signal) {
// Cleanup
})
3. Use Cross-Platform Paths
path := filepath.Join(homeDir, ".config", "app")
4. Check Permissions
perms := NewFilePermissions(path)
if !perms.IsReadable() {
return fmt.Errorf("file not readable")
}
Common Pitfalls
1. No Error Handling
Always check errors from OS operations.
2. Platform Assumptions
Don’t assume Unix-like environment.
3. No Signal Handling
Handle signals for graceful shutdown.
4. Hardcoded Paths
Use platform-specific path functions.
Resources
Summary
Proper OS interface usage is essential. Key takeaways:
- Handle environment variables safely
- Implement signal handling
- Check file permissions
- Use cross-platform paths
- Handle errors properly
- Support multiple platforms
- Test on target platforms
By mastering OS interfaces, you can build robust system utilities.
Comments