TTY::Command

repository·master·Indexed 19 days ago

https://github.com/piotrmurach/tty-command

A Ruby component from the TTY toolkit for executing external shell commands. It provides enhanced features including automatic argument escaping to prevent shell injection, pretty logging with multiple printer options, output capturing, and error handling. It supports PTY for interactive commands, dry-run simulation, timeouts, and advanced stream redirection for stdout and stderr.

Tokens
6.4K
Snippets
31
Records
37
Agent score
56%

What's inside tty-command

  1. Use PTY (Pseudo Terminal) for interactive commands

    master

    Setting pty: true executes the command in a subprocess where each stream is a pseudo terminal. This is required for:

    • Commands that only emit colored output when running in a terminal.
    • Interactive programs that wait for user input.

    Warning: Using PTY can change command behavior. For example, git log will use a pager and wait for input in PTY mode, whereas it finishes immediately in non-PTY mode. Additionally, input may be echoed to stdout and some redirects might not work.

    # Global PTY
    cmd = TTY::Command.new(pty: true)
    
    # Per-command PTY
    cmd.run("echo 'hello'", pty: true)
  2. Basic usage of TTY::Command

    master

    To execute external commands, create an instance of TTY::Command and use the run method.

    Important Behavior:

    • run will throw a TTY::Command::ExitError if the command fails (returns a non-zero exit status).
    • Arguments passed as separate strings after the command name are automatically escaped, preventing shell injection issues.
    • You can capture stdout and stderr by using the return value of run.
    require "tty-command"
    
    cmd = TTY::Command.new
    
    # Basic execution
    cmd.run("ls -la")
    
    # Capturing output
    out, err = cmd.run("cat ~/.bashrc | grep alias")
    
    # Safe argument escaping
    path = "hello world"
    cmd.run("sum", path) # Automatically escapes the path
  3. Use TTY::Command to execute system commands

    master

    The TTY::Command library provides a powerful interface for executing system commands from Ruby. It allows you to run commands, capture their output, handle errors via exit statuses, and manage logging. The primary entry point is the TTY::Command class, which you can instantiate to create a command runner.

    require 'tty-command'
    
    cmd = TTY::Command.new
    cmd.run('echo Hello World')
  4. Implement a custom command output printer using TTY::Command::Printers::Abstract

    master

    If you need to customize how TTY::Command outputs command execution details (like the command start, stdout, stderr, or exit status), you can subclass TTY::Command::Printers::Abstract.

    To create a functional printer, you must implement the write(cmd, message) method, as the base class raises a NotImplemented error if it is called directly.

    Lifecycle Hooks

    The abstract printer provides several hooks that are called during the command lifecycle. You can override these to control how specific events are formatted:

    • print_command_start(cmd, *args): Called when the command begins. It uses cmd.to_command to get the command string.
    • print_command_out_data(cmd, *args): Called when standard output is received.
    • print_command_err_data(cmd, *args): Called when error output is received.
    • print_command_exit(cmd, *args): Called when the command exits.

    Initialization

    When initializing your printer, you can pass an output IO object and an options hash. The options hash supports a :color key to enable or disable ANSI color support via Pastel.

    • @output: The IO object where data is written.
    • @options: Configuration options.
    • @out_data: A string buffer for standard output data.
    • @err_data: A string buffer for error data.
    class MyCustomPrinter < TTY::Command::Printers::Abstract
      def write(cmd, message)
        # Your custom logic to handle the command and message
        @output.puts "[#{cmd}] #{message}"
      end
    end
  5. Configure command execution context (user, group, chdir, umask)

    master

    When initializing or updating a Cmd object, you can provide options that modify how the command is wrapped in the shell. These options are applied during the to_command call:

    • user: If provided, wraps the command in sudo -u <user> -- sh -c '<command>'.
    • group: If provided, wraps the command in sg <group> -c "<command>".
    • chdir: If provided, prepends cd <path> && to the command.
    • umask: If provided, prepends umask <value> && to the command.
    • env: A hash of environment variables to be exported before the command runs.
  6. Configure command logging and printers

    master

    By default, TTY::Command uses the :pretty printer to log command execution and output to stdout. You can configure the printer during initialization using the :printer option.

    Available Printers:

    • :null: No output.
    • :pretty: Colorful, detailed output (default).
    • :progress: Minimal output with a green dot for success or F for failure.
    • :quiet: Only outputs the actual command stdout and stderr.

    You can also redirect output to a logger object that responds to <<:

    logger = Logger.new("dev.log")
    cmd = TTY::Command.new(output: logger)
    cmd = TTY::Command.new(printer: :progress)
  7. Configure logging appearance (Color, UUID, and Verbosity)

    master

    You can fine-tune the logging output using the following initialization options:

    • :color: Set to true to force color output, or false to disable it.
    • :uuid: Set to false to disable the unique command run ID prefix in the logs.
    • :verbose: Set to false to suppress warnings (e.g., when pty is not supported on a platform).
    • :only_output_on_error: Set to true to hide command output if the command succeeds. Output is only shown at the end of the command execution.
  8. Initialize a TTY::Command object

    master

    Create a new TTY::Command instance to manage external command execution. You can configure how the command logs its activity using the printer and output options.

    Available initialization options:

    • output (IO): The stream to which the printer writes (defaults to $stdout).
    • printer (Symbol): The type of printer to use for output logging. Options include :pretty (default), :null, :progress, or :quiet.
    • dry_run (Symbol): If set, commands will be simulated rather than executed.
    • color (Boolean): Enables/disables color in output (defaults to true).
    • uuid (Boolean): Enables/disables UUIDs in output (defaults to true).
    • verbose (Boolean): Controls verbosity (defaults to true).
    • pty (Boolean): Whether to use a pseudo-terminal.
    • binmode (Boolean): Whether to use binary mode.
    • timeout (Integer): Global timeout for commands initialized with this object.
    cmd = TTY::Command.new(printer: :quiet, output: $stderr)
  9. Configure command timeouts and signals

    master

    You can prevent commands from running indefinitely by setting a :timeout (in seconds). You can also specify which signal to use to terminate the process if it exceeds the timeout (defaults to SIGTERM).

    Timeouts can be set per command or globally when initializing TTY::Command.

    # Per-command timeout and custom signal
    cmd.run("sleep 10", timeout: 5, signal: :KILL)
    
    # Global timeout for all commands
    cmd = TTY::Command.new(timeout: 5)