0

I'd like to call a command line utility from a ruby script like so:

#!/env/ruby
json = {"key" => "value that \"has\" quotes"}.to_json
`aws events put-targets --cli-input-json #{json}`

Such that the resultant call should look like:

aws events put-targets --cli-input-json "{\"key\": \"value that \"has\" quotes\"}"

However, the result in this string interpolation results in a clean looking json structure without the quotes escaped and so results in error at the command line. Eg.

aws events put-targets --cli-input-json {"key": "value that \"has\" quotes"}

I need all the quotes properly escaped so that a string to the command line can be parsed as proper json.

I've tried doing string manipulation to manually escape quotes with things like:

json.gsub(/\"/,'\"')

But that doesn't work either.

This seems like it's harder than it should be. How can I render a properly escaped json string to the command line call?

I do have a rails environment that I can run this through if there are utilities that ActiveSupport provides that would facilitate this.

Peter P.
  • 3,221
  • 2
  • 25
  • 31

2 Answers2

2

In this case it is simpler and more effective to make a system call without a shell. If you use the multi-argument form of Kernel#system to invoke the external command directly:

system('aws', 'events', 'puts-targets', '--cli-input-json', json)

No shell, no quoting or escaping problems with json.

If you need to do more complicated things (such as capture output or errors), look into the various methods in Open3.

If you absolutely must go through a shell there's always Shellwords.shellescape. But really, when you use the shell, you're:

  1. Building a command line with a bunch of Ruby string operations.
  2. Invoking a shell.
  3. Letting the shell parse the command line (i.e. undo everything you did in (1)).
  4. Letting the shell invoke the command with your arguments.

Why not go straight to (4) yourself?

Garrett Motzner
  • 3,021
  • 1
  • 13
  • 30
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Sometimes you just gotta use the shell. I had high hopes for Shellwords.shellescape but that didn't work for json as it caused too much escaping of things like the { } curly braces. However, this led me to search for "shell escape json" which led me to the winning answer which I'll post shortly. – Peter P. Jan 30 '19 at 22:38
1

Thanks to @mu-is-too-short, I came across Shellwords which is a neat utility. This didn't solve the problem however, but led me to search for "shell escape json" which in turn led me to: Best way to escape and unescape strings in Ruby?

So:

json = {"key" => "value that \"has\" quotes"}.to_json.dump

Properly gets the escaped string that bash will understand. Tada.

UPDATE: Don't use this in production code. You're better off following @mu-is-too-short's advice in the comments or using a higher level library.

Peter P.
  • 3,221
  • 2
  • 25
  • 31
  • Luckily this is just a maintenance script. It doesn't take random user input. The json is highly structured valid input that actually comes straight from AWS with some small modification. There is no way in hell I would use a shell command for input that comes from the web. Using a shell allows me to get the response of the command and collate it in the script (ie associate errors with a particular invocation). I looked at Open3 but it seemed more trouble than its worth for this quick script. If I was writing code that were to be more robust/prod worthy, I would use a ruby AWS library. – Peter P. Jan 31 '19 at 00:18
  • Regardless of the production worthiness, I can see other potential valid use cases for getting strings into Bash compatible format using the dump approach above. – Peter P. Jan 31 '19 at 00:21