paulgorman.org/technical

Ruby as a Glue Language

(March 2016)

Get command line arguments

first_arg = ARGV[0]
second_arg = ARGV[1]
final_arg = ARGV[-1]

ARGV[0] is not the name of the script. The full path to the current script is in $0. Get the script name only with ‘File.basename($0)’.

Reading a file line by line by line

File.foreach('/path/to/my/file') do |line|
	puts line
end

Slurp in an entire (not too large) file at once

puts File.read('/path/to/my/file')

Check if a file exists

File.exists?('/path/to/my/file')

Write to a file

f = File.new('/path/to/my/file', 'w')
f.puts('The rain in Spain stays mainly in the plain.')
f.close

(…or, more idiomatically:)

File.open('/path/to/my/file', 'w') do |f|
	f.write('The rain in Spain stays mainly in the plain.')
end

(Appending works as above, but with ‘a’ instead of ‘w’. ‘w+’ opens the file for both reading and writing.)

Recurse through directory tree

Dir['/path/to/recurse'].each do |f|
	puts f
end

… or, using find:

require 'find'

Find.find('/path/to/recurse') do |f|
   # print file and path to screen if filename ends in .txt
   puts f if f.match(/\.txt/)
end

All text files in a directory non-recursively

Dir.glob('/path/to/files/*.txt').each do |f|
	puts f
end
STDERR.puts "DANGER, Will Robinson!"

Escape a string for the shell

require 'shellwords'
Shellwords.escape("All the king's horses & all the king's men")

…which is equivalent to:

require 'shellwords'
"All the king's horses & all the king's men".shellescape

Run a shell command (system)

system 'echo "Hello, $USER"'
puts $?    # $? is the return value; 0 for OK, non-zero for error.

Get output of a shell command (backticks)

now = `date`
put $?.success?    # true or false
put $?.to_i

(%x{date} is the same, but allows an alternate delimeter to backticks.)

Hook up to the shell’s standard handles (popen3)

require 'open3'
cmd = 'dc'
stdin, stdout, stderr = Open3.popen3(cmd)
stdin.puts(2)
stdin.puts(2)
stdin.puts('+')
stdin.puts('p')
stdout.gets
stdin.puts('asdf')
stderr.gets
stdin.puts('p')
stdin.puts('q')

Spawn a new process, and don’t wait for it to exit

pid = spawn('tar xf foo.tar')
Process.wait pid

(Use “Process.wait” or “Process.detatch” to avoid zomblie children.)

Fork a Copy and Communicate via Unix Sockets

(Forks share all existing objects, including sockets created before the fork.)

require 'socket'

parent_socket, child_socket = UNIXSocket.pair

fork do
	parent_socket.close
	child_socket.send("Sent from child (#{$$})", 0)
	from_parent = child_socket.recv(100)
	puts from_parent
end

child_socket.close
parent_socket.send("Sent from parent (#{$$}), 0)
from_child = parent_socket.recv(100)
puts from_child

Read from and Write to a Unix Named Pipe (FIFO)

% mkfifo /tmp/my_pipe

(Our writing process:)

w_pipe = open('/tmp/my_pipe', 'w+')    # w+ doesn't block (?)
w_pipe.puts 'A very important message!'
w_pipe.flush

(Our reading process:)

r_pipe = open('/tmp/my_pipe', 'r+')
puts r_pipe.gets    # Blocks if the pipe is empty!

Web serve the current directory

% ruby -run -e httpd -- -p 8000 .

Web serve a simple message

require 'socket'
httpd = TCPServer.new('localhost', 8000)
while (session = httpd.accept)
	msg = (`date` + "\nHello, world.")
	session.print("HTTP/1.1 200 OK\r\n" +
		"Content-Type: text/plain\r\n" +
		"Content-Length: #{msg.bytesize}\r\n" +
		"Connection: close\r\n")
	session.print("\r\n" + msg + "\r\n")
	session.close
end

Die!

Write to STDERR and exit with non-zero status using “abort”:

abort 'Fatal error: lock file exists!' if File.exists?(lock_file)

Regular Expressions

if (my_string =~ /^Jan\s+(\d+).*/)
	puts $1
end

my_matches = /in\s+(\w+)\s+falls/.match('The rain in Japan falls mainly...')
place = my_matches[1]    # ["in Japan falls", "Japan"]
puts place    # "Japan".

MY_RE = Regexp.new('\wlue')    # Omit /slashes/!
puts 'We found a clue in the parlor!'.scan MY_RE    # "clue"
if ('glue the vase back together'.match MY_RE)
	puts "Found glue."
end