Skip to content

Bi‐directional communication with a subprocess via a non‐standard file descriptor

James Couball edited this page Mar 8, 2025 · 1 revision

Parent Script

#!/usr/bin/env ruby
# parent.rb - Demonstrates custom file descriptor redirection with Process.spawn

require 'socket'

# Create a socket pair for communication
# This creates two connected socket file descriptors
parent_socket, child_socket = Socket.pair(:UNIX, :STREAM)

puts "Parent socket fd: #{parent_socket.fileno}"
puts "Child socket fd: #{child_socket.fileno}"

# Path to the child script
child_script = File.expand_path("./child.rb")

# Spawn the child process, passing the child socket as FD 3
pid = Process.spawn({"CUSTOM_FD" => "3"}, "ruby", child_script, 
                   3 => child_socket.fileno,   # Pass our child socket as FD 3 to the child
                   :out => $stdout,            # Standard redirections for reference
                   :err => $stderr)

# Close the child socket in the parent as we don't need it
child_socket.close

puts "Child process spawned with PID: #{pid}"

# Write some data to the socket
5.times do |i|
  message = "Message #{i} from parent process"
  parent_socket.puts(message)
  puts "Parent sent: #{message}"
  sleep 1
end

# Read the response from the child
puts "\nResponses from child process:"
5.times do
  response = parent_socket.gets.chomp
  puts "Parent received: #{response}"
end

# Close the socket and wait for the child to exit
parent_socket.close
Process.wait(pid)
puts "Child process exited with status: #{$?.exitstatus}"

Child Script

#!/usr/bin/env ruby
# child.rb - Child process receiving a custom file descriptor

# Get the custom file descriptor number from environment variable
custom_fd = ENV["CUSTOM_FD"].to_i

puts "Child process started with custom FD: #{custom_fd}"

# Convert the file descriptor number to an IO object
custom_io = IO.new(custom_fd)

# Read messages from the parent process
puts "Child process reading from custom file descriptor #{custom_fd}..."
messages = []
5.times do
  message = custom_io.gets.chomp
  messages << message
  puts "Child received: #{message}"
end

# Process the messages (as an example of doing something useful)
puts "\nProcessing messages..."
processed_messages = messages.map do |msg|
  # Simple processing: convert to uppercase and add timestamp
  timestamp = Time.now.strftime("%H:%M:%S")
  processed = "#{msg.upcase} [processed at #{timestamp}]"
  puts "Child processed: #{processed}"
  processed
end

# Send processed messages back to parent
puts "\nSending processed messages back to parent..."
processed_messages.each do |msg|
  custom_io.puts(msg)
  puts "Child sent: #{msg}"
  sleep 0.5
end

# Close the custom file descriptor
custom_io.close
puts "Child process completed"
exit 0
Clone this wiki locally