1

I was building a method to send me an email through mutt when my ruby scripts fail. It looks something like this:

begin
    UnknownFunction()
rescue
    subject = 'Error'
    to_array = ['email@email.com','email2@email.com']
    body = "An error occurred:\n#{$!}"
    %x[echo "#{body}" | mutt -s "#{subject}" #{to_array.join(",")}]
end

the command was throwing the following error:

sh: -c: line 1: unexpected EOF while looking for matching ``'
sh: -c: line 2: syntax error: unexpected end of file

I finally looked closely enough to see that $! contains a backtick before the undefined method's name followed by a single quote:

undefined method `UnknownFunction' for main:Object

I dug into the code and verified the method_missing method has a backtick before and single quote afterwards. Should the backtick be a single quote or vice versa? If not, what's the reasoning behind it?

raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1)
dukebd711
  • 15
  • 2
  • I have noticed that before, it's always messing up my inline code on SO when pasting errors since it includes one or more backticks. I have no idea what the reasoning can possibly be. – Daniël Knippers Jul 14 '14 at 19:07
  • are the double quotes not supposed to be like this -> mutt -s "#{subject} #{to_array.join(",")}" – nishu Jul 14 '14 at 19:13

3 Answers3

3

It's a substitute for an open single quote (‘) in plain text/pre-Unicode environments. See: Why do plain-text technical articles often enclose terms within backticks and single quotes?

Community
  • 1
  • 1
Martin Gordon
  • 36,329
  • 7
  • 58
  • 54
  • Interesting, but it looks ridiculous to mix a backtick with a regular single quote. Very surprising this caught on. – Daniël Knippers Jul 14 '14 at 19:20
  • In that situation, the backtick is visually similar to a left-curly-single quote character, just as the regular single-quote is its matching counterpart, and is why they use them. TTYs didn't have curly quotes. Not everything we see in programming and OSes is modern; A lot of it harkens back 40 years or more. – the Tin Man Jul 14 '14 at 20:07
2

The description of the NoMethodError is not meant to be code, so the use of a backtick there is purely for aesthetic reasons. If you want to pass an arbitrary string to the shell, use Shellwords.shellescape.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • my solution was to gsub any backticks to single quotes. the script isn't sophisticated nor elegant, I just need it to get the job done. – dukebd711 Jul 16 '14 at 19:47
  • @dukebd711: I can't imagine why, since there's really no benefit to doing it with gsub versus properly shell escaping — but hey, it's not my code, so I'm glad you found something that works for you. – Chuck Jul 16 '14 at 19:51
1

backticks are fine for simple commands, but once you start throwing data at the child process I think its better to use something more sophisticated than piping the text via echo. I'd use IO.popen:

IO.popen(
  "mutt -s '%s' %s" % [ subject, to_array.join(',') ],
  'w'
) do |mutt|
  mutt.puts body
end

That's untested but is what I'd start with. It's more readable because it gets rid of the backtick jungle of interpolated variables. It also avoids potential problems of the sub-shell trying to help by interpreting variables or looking for embedded backticks in the text being sent to mutt.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Like I said in my answer, I would definitely recommend shell-escaping the string properly rather than writing a potentially broken shell escape with `tr`. What if another exception that hasn't been encountered yet contains another character that makes the command barf? – Chuck Jul 14 '14 at 21:17
  • that's true. `Shellwords::shellescape` is more thorough, *and* it's a wheel that doesn't need to be reinvented. – the Tin Man Jul 15 '14 at 16:49