NAME

Sidef::Types::Glob::Backtick - Shell command execution via backticks

SYNOPSIS

# Execute a shell command and capture its output
var out = `ls -1`
say out

# With string interpolation
var dir = "/tmp"
var files = `ls #{dir}`

# Explicit object usage (normally implicit)
var cmd = Backtick("uname -a")
var out = cmd.call
say out

# Chaining with string methods
var user = `whoami`.trim
var lines = `cat file.txt`.lines

# Using in expressions
if (`which git`.trim) {
    say "Git is installed"
}

DESCRIPTION

The Sidef::Types::Glob::Backtick class represents a backtick expression in Sidef, which executes shell commands and captures their output.

A backtick expression executes a command using the system shell (typically /bin/sh on Unix-like systems or cmd.exe on Windows) and returns the command's standard output as a String object.

This behavior is analogous to backticks in Perl, Ruby, or POSIX shells:

`command`

The command is executed in a subshell with the output captured and returned as a string. All text written to STDOUT is captured, while STDERR is not captured by default (it goes to the parent process's STDERR).

Common Use Cases

Backtick expressions are typically used for:

  • Querying system information (kernel version, hostname, etc.)

  • Interfacing with external command-line programs

  • Capturing output from Unix utilities in scripts

  • Quick system administration tasks

  • Reading data from external sources via shell commands

INHERITS

This class inherits from:

METHODS

call

var output = backtick_obj.call
var output = backtick_obj()

Executes the stored shell command and returns its standard output as a String.

The returned string includes everything written to STDOUT by the command, including any trailing newlines. Trailing whitespace is not automatically removed; use the trim method if needed.

Return value: String containing the command's output

Exit status: The exit status of the command is not returned. If you need to check the exit status, use the Sys.run method instead.

Error handling: If the command fails or cannot be found, behavior depends on the shell. Typically:

  • An empty string may be returned

  • Partial output up to the point of failure may be returned

  • Shell error messages may appear on STDERR

Example:

var kernel = Backtick("uname -r")
say kernel.call

run

var output = backtick_obj.run

Alias for call. Executes the command and returns its output.

This method exists for internal consistency with other callable Sidef objects and for compatibility with the general "run" interface pattern.

Example:

var cmd = Backtick("date")
var timestamp = cmd.run

to_s

var command_string = backtick_obj.to_s

Returns the literal command string represented by the backtick expression without executing it.

This is useful for:

  • Debugging - see what command would be executed

  • Logging - record the command before execution

  • Introspection - examining backtick objects programmatically

Return value: String containing the command text

Example:

var cmd = Backtick("ls -la")
say cmd.to_s              # prints: ls -la
say cmd.call              # executes and returns output

dump

var representation = backtick_obj.dump

Returns a string representation of the Backtick object suitable for debugging.

Example:

var cmd = Backtick("echo hello")
say cmd.dump    # Backtick("echo hello")

BACKTICK SYNTAX

Backticks can be written directly in Sidef source code using the grave accent character (`` ` ``):

var date = `date`
var user = `whoami`
var files = `ls *.sidef`

The command string is passed to the system shell exactly as written, allowing full use of shell features.

String Interpolation

Backtick expressions support Sidef string interpolation, allowing variables to be embedded in commands:

var dir = "/home/user"
var count = `ls #{dir} | wc -l`

var filename = "data.txt"
var contents = `cat #{filename}`

Shell Features

Since commands are executed by the shell, all shell features are available:

  • Pipes: `ps aux | grep firefox`

  • Redirections: `command 2&1`>

  • Glob expansion: `ls *.txt`

  • Command substitution: `echo $(date)`

  • Environment variables: `echo $HOME`

  • Logical operators: `command1 && command2`

Example with shell features:

# Multiple commands
var info = `uname -s && uname -r`

# Pipe through multiple commands
var top_processes = `ps aux | sort -rn -k 3 | head -5`

# Redirection
var combined = `command 2>&1`

EXAMPLES

Basic Command Execution

# Get current date
var date = `date`
say date

# Get username
var user = `whoami`.trim
say "Hello, #{user}!"

# Check if command exists
var git_path = `which git 2>/dev/null`.trim
if (git_path) {
    say "Git found at: #{git_path}"
}

Using Shell Pipelines

# Count running processes
var count = `ps aux | wc -l`.to_i
say "Running processes: #{count}"

# Find large files
var large = `find . -type f -size +1M | head -10`
say large

# Text processing
var words = `cat file.txt | tr ' ' '\n' | sort | uniq -c`

String Processing

# Trim whitespace
var hostname = `hostname`.trim
say hostname

# Split output into lines
var files = `ls`.lines
files.each { |file|
    say "File: #{file}"
}

# Parse structured output
var df = `df -h`.lines
df.each { |line|
    var parts = line.split(/\s+/)
    say parts
}

Conditional Execution

# Check if program is installed
if (`which python3`.trim) {
    say "Python 3 is installed"
    var version = `python3 --version`.trim
    say version
}

# Check command success by output
var status = `systemctl is-active nginx 2>/dev/null`.trim
if (status == "active") {
    say "Nginx is running"
}

Capturing and Processing Output

# Get and parse date components
var date_parts = `date +%Y-%m-%d`.trim.split('-')
var (year, month, day) = date_parts...
say "Year: #{year}, Month: #{month}, Day: #{day}"

# Read file through command
var lines = `cat /etc/hosts`.lines.grep { !_.starts_with('#') }
lines.each { |line| say line }

# Process JSON output from command
# var json = `curl -s api.example.com/data`.trim
# var data = json.parse_json

Working with Files

# List files modified today
var today_files = `find . -type f -mtime 0`
say today_files

# Count lines in source files
var line_count = `find . -name '*.sidef' -exec wc -l {} + | tail -1`.trim
say "Total lines: #{line_count}"

# Search for pattern
var matches = `grep -r "TODO" . 2>/dev/null`
if (matches) {
    say "Found TODOs:"
    say matches
}

System Information

# Get system information
var os = `uname -s`.trim
var kernel = `uname -r`.trim
var arch = `uname -m`.trim

say "OS: #{os}"
say "Kernel: #{kernel}"
say "Architecture: #{arch}"

# Get memory info (Linux)
var mem = `free -h | grep Mem`.trim
say mem

# Get disk usage
var disk = `df -h /`.lines[1]
say "Root disk: #{disk}"

Advanced Usage

# Combine with error handling
var output = ""
try {
    output = `command 2>&1`.trim
} catch {
    say "Command failed"
}

# Use in array/hash construction
var env = Hash(
    user: `whoami`.trim,
    home: `echo $HOME`.trim,
    shell: `echo $SHELL`.trim
)
say env

# Filter and transform
var users = `cut -d: -f1 /etc/passwd`.lines.sort
say "System users: #{users.join(', ')}"

SECURITY CONSIDERATIONS

Backtick expressions execute arbitrary shell commands with the privileges of the running process. Extreme caution is required when using user input.

Command Injection Vulnerability

Never interpolate untrusted input into backtick expressions without proper validation or escaping. This can lead to command injection vulnerabilities where attackers can execute arbitrary commands.

Unsafe example (VULNERABLE):

# DANGEROUS - DO NOT USE
var filename = readln              # User input
var content = `cat #{filename}`    # EXPLOITABLE!

# If user enters: file.txt; rm -rf /
# The command becomes: cat file.txt; rm -rf /

Safer alternatives:

1. Validate input against a whitelist of allowed values
var filename = readln.trim
if (filename ~~ /^[a-zA-Z0-9._-]+$/) {
    var content = `cat #{filename}`
} else {
    die "Invalid filename"
}
2. Use File I/O instead of shell commands when possible
var filename = readln.trim
var content = File(filename).read   # Safer - no shell involved
3. Use Sys.run with array arguments for proper escaping
var filename = readln.trim
var content = Sys.run("cat", filename)
4. Escape shell metacharacters (complex and error-prone)
# Not recommended - easy to get wrong
var escaped = filename.escape
var content = `cat #{escaped}`

Other Security Concerns

  • Environment variables - The shell environment is inherited and can affect command behavior

  • PATH injection - Ensure PATH is controlled in security-sensitive contexts

  • Working directory - Commands execute in the current directory

  • Shell features - Wildcards, pipes, and redirections can have unexpected effects

Best Practices

  • Only use backticks with trusted, static commands when possible

  • Prefer File I/O and Sidef built-ins over shell commands

  • Use Sys.run or Sys.exec with array arguments for better control

  • Validate and sanitize all user input before using in commands

  • Use absolute paths for commands in security-sensitive code

  • Consider using restricted shells or sandboxing for untrusted commands

RETURN VALUES AND ERROR HANDLING

Backtick expressions return the captured STDOUT as a String. Error handling is limited:

  • Exit status is not returned or checked

  • STDERR is not captured (goes to parent's STDERR)

  • Failed commands may return empty strings or partial output

  • Command not found errors depend on the shell

If you need more control over command execution:

# For exit status
var (output, status) = Sys.run("command")
if (status == 0) {
    say "Success: #{output}"
}

# For STDERR capture
var output = `command 2>&1`  # Redirect STDERR to STDOUT

# For complex execution control
Sys.exec("command", "arg1", "arg2")

PLATFORM DIFFERENCES

Command execution behavior varies by platform:

  • Unix/Linux/macOS: Uses /bin/sh or $SHELL

  • Windows: Uses cmd.exe or PowerShell depending on configuration

  • Command syntax: Must match the target shell's syntax

  • Path separators: Unix uses /, Windows uses \

  • Available commands: Vary significantly between platforms

For portable code, either:

  • Use platform detection and conditional commands

  • Stick to commands available on all target platforms

  • Use Sidef built-ins instead of shell commands where possible

PERFORMANCE CONSIDERATIONS

Backtick expressions spawn a new shell process for each execution, which has overhead:

  • Process creation takes time (milliseconds)

  • Each execution creates a new shell + subprocess

  • Repeated execution in loops can be slow

For better performance:

# Slow - spawns 1000 shells
1000.times {
    var date = `date`
}

# Better - call once, reuse result
var date = `date`
1000.times {
    say date
}

# Best - use Sidef built-ins when available
var date = Time.now
1000.times {
    say date
}

COMPARISON WITH OTHER APPROACHES

Backticks vs File I/O

# Using backticks
var content = `cat file.txt`

# Using File I/O (preferred)
var content = File("file.txt").read

File I/O is generally preferred because it's:

  • Faster (no shell overhead)

  • More portable (no external commands)

  • Safer (no command injection risk)

  • More predictable error handling

Backticks vs Sys Methods

# Backticks - simple, returns output only
var output = `command arg`

# Sys.run - returns output and exit status
var (output, status) = Sys.run("command", "arg")

# Sys.exec - replaces current process
Sys.exec("command", "arg")

Use Sys methods when you need:

  • Exit status checking

  • Better error handling

  • Argument array passing (safer with user input)

  • More control over execution environment

IMPLEMENTATION NOTES

Backtick expressions are represented in the Sidef AST using Sidef::Types::Glob::Backtick objects, which store the command string and execute it when evaluated.

Implementation details:

  • Command execution is delegated to the host operating system shell

  • Output capture uses standard process I/O redirection

  • The command string is evaluated at runtime, allowing interpolation

  • No caching - each call executes the command again

DIAGNOSTICS

Common issues and solutions:

  • "Command not found" - Command is not in PATH or doesn't exist

  • Empty output - Command failed, has no output, or wrong redirect

  • Unexpected results - Shell interpretation of special characters

  • Permission denied - Insufficient permissions to execute command

Debugging tips:

# See the actual command
var cmd = Backtick("ls -la")
say cmd.to_s

# Capture errors
var output = `command 2>&1`

# Test command in shell first
# $ command arg1 arg2

SEE ALSO

AUTHOR

Daniel "trizen" Șuteu

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Sidef itself.