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 runsssh.loop { channel.active? }
, blocking until this specific channel is closed.