Class: Ukiryu::Shell::Tcsh

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

Overview

Tcsh (TENEX C Shell) implementation

Tcsh is a C shell variant with enhancements including command-line editing, history expansion, and programmable completion.

Key differences from bash/sh:

  • Uses C shell syntax (not POSIX sh compatible)

  • History expansion with ! (major difference)

  • Variable assignment: set var = value

  • Environment variables: setenv VAR value

  • Arrays: set arr = (a b c)

  • Special characters: ! ^ ~ # $ * ? [ ] { } | ; & < > ( )

Tcsh cannot inherit from UnixBase because it uses different syntax and doesn’t support the same -c flag execution method.

Constant Summary collapse

SHELL_NAME =
:tcsh
PLATFORM =
:unix
EXECUTABLE =
'tcsh'

Constants inherited from Base

Base::SPECIAL_CHARS_PATTERN, Base::WHITESPACE_PATTERN

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#capabilities, #encoding, #environment_to_h, #format_environment, #needs_quoting?, #supports?

Class Method Details

.detect_alias(command_name) ⇒ Hash?

Detect if a command is a Tcsh alias

Tcsh uses the ‘alias’ builtin with its own format.

Parameters:

  • command_name (String)

    the command to check

Returns:

  • (Hash, nil)

    “…”, target: “…” or nil if not an alias



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ukiryu/shell/tcsh.rb', line 34

def self.detect_alias(command_name)
  # Tcsh's alias command returns: "alias ll ls -l"
  result = `tcsh -c "alias #{command_name}" 2>/dev/null`
  return nil unless result

  # Parse tcsh alias format
  # Format: "alias ll ls -l" or "ll: aliased to ls -l"
  if result =~ /^#{command_name}\s+(.+)$/
    { definition: result.strip, target: ::Regexp.last_match(1).split(/\s+/).first }
  elsif result =~ /^#{command_name}:\s+aliased to\s+(.+)$/
    { definition: result.strip, target: ::Regexp.last_match(1).split(/\s+/).first }
  end
  nil
end

Instance Method Details

#env_var(name) ⇒ String

Format an environment variable reference

Parameters:

  • name (String)

    the variable name

Returns:

  • (String)

    the formatted reference ($VAR)



97
98
99
# File 'lib/ukiryu/shell/tcsh.rb', line 97

def env_var(name)
  "$#{name}"
end

#escape(string) ⇒ String

Escape a string for Tcsh

Tcsh has complex escaping rules:

  • ! must always be escaped (history expansion)

  • Other special chars: ^ ~ # $ * ? [ ] { } | ; & < > ( )

  • Backslash escapes the next character

Parameters:

  • string (String)

    the string to escape

Returns:

  • (String)

    the escaped string



62
63
64
65
66
67
68
69
70
71
# File 'lib/ukiryu/shell/tcsh.rb', line 62

def escape(string)
  # Escape characters that have special meaning in tcsh
  # ! is the most important (history expansion)
  # $ is used for variables
  # ` for command substitution
  # " for quoting
  # \ for escaping
  # Other special chars that might need escaping in certain contexts
  string.to_s.gsub(/[!$`"\\]/) { "\\#{::Regexp.last_match(0)}" }
end

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

Execute a command using Tcsh

Tcsh uses -c flag for command execution, similar to other shells.

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



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ukiryu/shell/tcsh.rb', line 137

def execute_command(executable, args, env, timeout, cwd = nil)
  require 'open3'
  require 'timeout'

  # Build command string using this shell's quoting rules
  command_string = join(executable, *args)

  # Find tcsh executable
  tcsh_path = Ukiryu::Executor.find_executable('tcsh')
  raise 'tcsh not found in system PATH' unless tcsh_path

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

  # Execute using tcsh's -c flag
  Timeout.timeout(timeout) do
    execution = lambda do
      stdout, stderr, status = Open3.capture3(env_hash, tcsh_path, '-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
  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 Tcsh

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



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/ukiryu/shell/tcsh.rb', line 182

def execute_command_with_stdin(executable, args, env, timeout, cwd, stdin_data)
  require 'open3'
  require 'timeout'

  # Build command string using this shell's quoting rules
  command_string = join(executable, *args)

  # Find tcsh executable
  tcsh_path = Ukiryu::Executor.find_executable('tcsh')
  raise 'tcsh not found in system PATH' unless tcsh_path

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

  # Execute using tcsh's -c flag
  Timeout.timeout(timeout) do
    execution = lambda do
      Open3.popen3(env_hash, tcsh_path, '-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
  raise Timeout::Error, "Command timed out after #{timeout}s: #{executable}"
end

#format_path(path) ⇒ String

Format a file path for Tcsh Tcsh on Unix uses forward slashes

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

    the formatted path



115
116
117
# File 'lib/ukiryu/shell/tcsh.rb', line 115

def format_path(path)
  path
end

#headless_environmentHash

Tcsh doesn’t need DISPLAY variable

Returns:

  • (Hash)

    empty hash



122
123
124
# File 'lib/ukiryu/shell/tcsh.rb', line 122

def headless_environment
  {}
end

#join(executable, *args) ⇒ String

Join executable and arguments into a command line

Parameters:

  • executable (String)

    the executable path

  • args (Array<String>)

    the arguments

Returns:

  • (String)

    the complete command line



106
107
108
# File 'lib/ukiryu/shell/tcsh.rb', line 106

def join(executable, *args)
  [quote(executable), *args.map { |a| quote(a) }].join(' ')
end

#nameObject



49
50
51
# File 'lib/ukiryu/shell/tcsh.rb', line 49

def name
  :tcsh
end

#quote(string) ⇒ String

Quote an argument for Tcsh

Tcsh supports both single and double quotes. Single quotes are literal (except for ‘ which ends the quote). Double quotes allow variable expansion and command substitution.

However, ! (history expansion) can still occur even in quotes! The safest approach is to escape ! with backslash first.

Parameters:

  • string (String)

    the string to quote

Returns:

  • (String)

    the quoted string



84
85
86
87
88
89
90
91
# File 'lib/ukiryu/shell/tcsh.rb', line 84

def quote(string)
  # For tcsh, we need to:
  # 1. Escape ! characters (history expansion)
  # 2. Escape ' characters if present
  # 3. Wrap in single quotes
  escaped = string.to_s.gsub(/!/) { '\\!' }.gsub("'") { "'\\''" }
  "'#{escaped}'"
end