Usage Guide: Core Concepts

This guide covers the fundamental concepts of working with Net::SSH, including executing commands, working with channels, and managing the event loop.

Starting a Session

All interactions begin with Net::SSH.start. It can be used with or without a block.

With a block (recommended): The session is automatically closed when the block finishes.

Net::SSH.start('host', 'user', password: 'password') do |ssh|
  # ssh is a Net::SSH::Connection::Session object
  puts ssh.exec!('whoami')
end

Without a block: The method returns the session object, and you are responsible for closing it.

ssh = Net::SSH.start('host', 'user', password: 'password')
# ... do work ...
ssh.close

Executing Commands

Net::SSH provides two primary ways to execute commands: synchronously (exec!) and asynchronously (exec).

Synchronous Execution: exec!

The exec! method is the simplest way to run a command. It blocks until the remote command finishes and returns the collected standard output (stdout). Standard error (stderr) is discarded.

output = ssh.exec!('ls -l /etc')
puts output

The string returned by exec! is enhanced with an exitstatus method to retrieve the command's exit code.

result = ssh.exec!('exit 42')
puts result.exitstatus #=> 42

Asynchronous Execution and Channels

For more complex interactions, like handling stdout and stderr separately, providing input to a command, or running multiple commands in parallel, you need to use channels and the event loop.

Every command runs in its own channel. A channel is an independent, bidirectional stream of data multiplexed over a single SSH connection.

The exec Method and Callbacks

The exec method opens a channel and executes a command without blocking. You interact with the command's output through callbacks.

ssh.exec('find / -name "*.log"') do |channel, stream, data|
  if stream == :stdout
    puts "STDOUT: #{data}"
  elsif stream == :stderr
    puts "STDERR: #{data}"
  end
end

# The command runs in the background. We need to run the event loop
# to process the results.
ssh.loop

The Event Loop: ssh.loop

Because SSH is an asynchronous protocol, Net::SSH relies on an event loop to process data. The ssh.loop method runs this loop, waiting for and dispatching events (like incoming data) to the appropriate channel callbacks.

The loop will run forever unless you give it a condition. A common pattern is to loop as long as there are active channels.

# This will run the event loop until all channels have closed.
ssh.loop { ssh.busy? }

Advanced Channel Control

For maximum control, you can open a channel manually with open_channel and then attach callbacks to it. This is useful for interactive sessions or complex state machines.

channel = ssh.open_channel do |ch|
  # Request a pseudo-terminal, useful for interactive programs
  ch.request_pty

  ch.exec('sudo -p "sudo password: " ls /root') do |ch, success|
    abort "could not execute command" unless success

    # Callback for when the server sends data (stdout)
    ch.on_data do |c, data|
      puts "Got stdout: #{data}"
      if data =~ /sudo password: /
        # Send the password to the command's stdin
        c.send_data("my_sudo_password\n")
      end
    end

    # Callback for extended data (stderr)
    ch.on_extended_data do |c, type, data|
      puts "Got stderr: #{data}"
    end

    # Callback for when the remote process gives its exit status
    ch.on_request("exit-status") do |c, data|
      puts "Process finished with exit code: #{data.read_long}"
    end

    # Callback when the channel is closed
    ch.on_close do |c|
      puts "Channel is closing!"
    end
  end
end

# Wait for the channel to finish all its work.
channel.wait

This example demonstrates several key concepts:

  • open_channel: Manually creates a new channel.
  • request_pty: Requests a pseudo-terminal.
  • on_data, on_extended_data: The primary callbacks for handling output.
  • send_data: Sends data to the remote process's standard input.
  • on_request("exit-status"): A special request callback to capture the exit code.
  • channel.wait: A convenience method that runs ssh.loop { channel.active? }, blocking until this specific channel is closed.