How to copy output of a function to your clipboard in Elixir or Ruby

<p>Having the ability to drive your development using just a keyboard is very
productive. However, when you are using a terminal and have to copy the output
of a command to use it somewhere else, it breaks your flow, you need to move
your hands away from your keyboard, use the mouse to select the text and then
copy it.</p>

<p>When I want to copy passwords to be used elsewhere from my browser, I usually
open the developer tools console, inspect element and click on the password
input box and then run the following code:</p>

<pre><code class="javascript language-javascript">copy($0.value)
</code></pre>

<p>Chrome sets <code>$0</code> to refer to the currently selected DOM element and <code>$0.value</code>
will give us the value of the password field and sending it to the <code>copy</code>
function copies this text to the OS clipboard.</p>

<p>I have a similar script set up for my terminal, when I want to copy the output
of a command like <code>rake secret</code> I run the following command:</p>

<pre><code class="bash language-bash">rake secret | xc # copies a new secret to the clipboard.
echo "Hello" | xc # copies the string `Hello` to the clipboard.
</code></pre>

<p><code>xc</code> is aliased to the following in my bashrc:</p>

<pre><code class="bash language-bash">alias xc='tee /dev/tty | xclip -selection clipboard'
</code></pre>

<p>This command prints the output to the terminal (using <code>tee /dev/tty</code>) and copies
it to the OS clipboard using the <code>xclip</code> package.</p>

<p>I wanted the same ability in my ruby and elixir REPLs. It was pretty
straightforward to do in ruby. Here is the annotated code:</p>

<pre><code class="ruby language-ruby">puts 'loading ~/.pryrc ...'

require 'open3'

# copy takes an argument and converts it into a string and copies it to the OS
# clipboard using the `xclip` command line package.
def copy(text)
# start running the `xclip` command to copy the stdin to the OS primary
# clipboard. Also pass the stdin and stdout, stderr to the block
Open3.popen3('xclip', '-selection', 'clipboard') do |stdin, _stdout, _stderr, _wait_thr|
# convert the input argument to a string and write it to the stdin of the
# spawned `xclip` process and the close the input stream
stdin.puts text.to_s
stdin.close
end

# print out an informational message to signal that the argument has been
# copied to the clipboard.
puts "copied to clipboard: #{text.to_s[0..10]}..."
end

# e.g. running `copy SecureRandom.uuid` will print the following
# pry(main)&gt; copy SecureRandom.uuid
# copied to clipboard: 14438d5c-62...
# and copies: `14438d5c-62b9-40a1-a324-5d2bd2205990` to the OS clipboard
</code></pre>

<p>Below is a similar script for Elixir:</p>

<pre><code class="elixir language-elixir">IO.puts("loading ~/.iex.exs")

# open a module called `H` as we can't have functions outside modules
defmodule H do
# copy function takes the input and converts it into a string before copying
# it to the OS clipboard.
def copy(text) do
# convert input argument to a string
text = to_s(text)

# spawn a new xclip process configured to copy the stdin to the OS's primary
# clipboard
port = Port.open({:spawn, "xclip -selection clipboard"}, [])
# send the input text as stdin to the xclip process
Port.command(port, text)
# close the port
Port.close(port)

# print out an informational message to signal that the text has been copied
# to the OS's clipboard"
IO.puts("copied to clipboard: #{String.slice(text, 0, 10)}...")
end

# to_s converts an elixir term to a string if it implements the `String.Chars`
# protocol otherwise it uses `inspect` to convert it into a string.
defp to_s(text) do
to_string(text)
rescue
_ -&gt; inspect(text)
end
end
</code></pre>

<pre><code class="elixir language-elixir">iex(2)&gt; :crypto.strong_rand_bytes(16) |&gt; Base.encode16 |&gt; H.copy
# copied to clipboard: 347B175C6F...
# it has also copied `347B175C6F397B2808DE7168444ED428` to the OS's clipboard
</code></pre>

<p>All these utilities (except for the browser's <code>copy</code> function) depend on the
<code>xclip</code> utility which can be installed on ubuntu using <code>sudo apt-get install
xclip</code>. You can emulate the same behaviour on a Mac using the <code>pbcopy</code> utility,
you might have to tweak things a little bit, but it should be pretty straightforward.</p>

<p>You can do the same in your favorite programming language too, just find the
right way to spawn an <code>xclip</code> process and send the text you want to be copied to
its' stdin. Hope this makes your development a little more pleasant :)</p>