Class: Ukiryu::Shell::UnixBase

Inherits:
Base
  • Object
show all
Defined in:
lib/ukiryu/shell/unix_base.rb

Overview

Base class for Unix-like shells (bash, zsh, fish, sh)

Unix shells use:

  • Single quotes for literal strings

  • $VAR for environment variables

  • shell -c ‘command’ for execution

Direct Known Subclasses

Bash, Dash, Fish, Sh, Zsh

Constant Summary collapse

PLATFORM =
:unix

Constants inherited from Base

Base::EXECUTABLE, Base::SHELL_NAME, Base::SPECIAL_CHARS_PATTERN, Base::WHITESPACE_PATTERN

Instance Method Summary collapse

Methods inherited from Base

#capabilities, detect_alias, #encoding, #env_var, #environment_to_h, #escape, #format_environment, #format_path, #headless_environment, #join, #name, #needs_quoting?, #quote, #supports?

Instance Method Details

#execute_command(executable, args, env, timeout, cwd = nil) ⇒ Hash

Execute a command using this shell

Uses the shell’s -c flag to execute the command string. This ensures the command is interpreted by the correct shell (bash, zsh, etc.) rather than /bin/sh (which might be dash on some systems).

Parameters:

  • executable (String)

    the executable path

  • args (Array<String>)

    command arguments

  • env (Environment)

    environment variables

  • timeout (Integer)

    timeout in seconds

  • cwd (String, nil) (defaults to: nil)

    working directory (nil for current directory)

Returns:

  • (Hash)

    execution result with :status, :stdout, :stderr keys

Raises:

  • (Timeout::Error)

    if command times out



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ukiryu/shell/unix_base.rb', line 44

def execute_command(executable, args, env, timeout, cwd = nil)
  # Build command string using this shell's quoting rules
  command_string = join(executable, *args)

  # Convert Environment to Hash ONLY at Open3 call site
  env_hash = environment_to_h(env)

  # Execute using the shell's -c flag
  # This ensures the command is interpreted by THIS shell, not /bin/sh
  Timeout.timeout(timeout) do
    execution = lambda do
      stdout, stderr, status = Open3.capture3(env_hash, shell_executable, '-c', command_string)
      {
        status: Ukiryu::Executor.extract_status(status),
        stdout: stdout,
        stderr: stderr
      }
    end

    if cwd
      Dir.chdir(cwd) { execution.call }
    else
      execution.call
    end
  end
rescue Timeout::Error, Timeout::ExitException
  # Re-raise with context
  raise Timeout::Error, "Command timed out after #{timeout}s: #{executable}"
end

#execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data) ⇒ Hash

Execute a command with stdin input using this shell

Parameters:

  • executable (String)

    the executable path

  • args (Array<String>)

    command arguments

  • env (Environment)

    environment variables

  • timeout (Integer)

    timeout in seconds

  • cwd (String, nil)

    working directory (nil for current directory)

  • stdin_data (String, IO)

    stdin input data

Returns:

  • (Hash)

    execution result with :status, :stdout, :stderr keys

Raises:

  • (Timeout::Error)

    if command times out



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/ukiryu/shell/unix_base.rb', line 84

def execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data)
  # Build command string using this shell's quoting rules
  command_string = join(executable, *args)

  # Convert Environment to Hash ONLY at Open3 call site
  env_hash = environment_to_h(env)

  # Execute using the shell's -c flag
  Timeout.timeout(timeout) do
    execution = lambda do
      Open3.popen3(env_hash, shell_executable, '-c', command_string) do |stdin, stdout, stderr, wait_thr|
        # Write stdin data
        begin
          if stdin_data.is_a?(IO)
            IO.copy_stream(stdin_data, stdin)
          elsif stdin_data.is_a?(String)
            stdin.write(stdin_data)
          end
        rescue Errno::EPIPE
          # Process closed stdin early (e.g., 'head' command)
        ensure
          stdin.close
        end

        # Read output
        out = stdout.read
        err = stderr.read

        # Wait for process to complete
        status = wait_thr.value

        {
          status: Ukiryu::Executor.extract_status(status),
          stdout: out,
          stderr: err
        }
      end
    end

    if cwd
      Dir.chdir(cwd) { execution.call }
    else
      execution.call
    end
  end
rescue Timeout::Error, Timeout::ExitException
  # Re-raise with context
  raise Timeout::Error, "Command timed out after #{timeout}s: #{executable}"
end

#shell_commandString

Get the shell command name to search for

Returns:

  • (String)

    the shell command name (e.g., ‘bash’, ‘zsh’)

Raises:

  • (NotImplementedError)


19
20
21
# File 'lib/ukiryu/shell/unix_base.rb', line 19

def shell_command
  raise NotImplementedError, "#{self.class} must implement #shell_command"
end

#shell_executableString

Get the shell executable path (found dynamically)

Returns:

  • (String)

    the shell executable path

Raises:

  • (RuntimeError)

    if shell is not found on the system



27
28
29
# File 'lib/ukiryu/shell/unix_base.rb', line 27

def shell_executable
  @shell_executable ||= find_shell_executable
end