Comparison of Escape class and String.shell escape

From WhyNotWiki

Jump to: navigation, search

[edit] Comparison of Escape class and String#shell_escape

Recall that if exec/system "is given a single argument, that argument is taken as a line that is subject to shell expansion before being executed. If multiple arguments are given, the second and subsequent arguments are passed as parameters to command with no shell expansion." (exec)

  exec "echo *"       # echoes list of files in current directory
  exec "echo", "*"    # echoes an asterisk
Escape class String#shell_escape (Facets)
Test: Given a string A of "special characters" as input, can the escaper produce a string that, when fed to a console app, will that program see the original string A exactly as it was input?
generated string
irb -> Escape.shell_command([ "echo", %q{!&'"`$0 |()<>} ])
    => "echo '!&'\\''\"`$0 |()<>'"
irb -> "echo " + %q{!&'"`$0 |()<>}.shell_escape
    => "echo !\\&\\'\\\"\\`$0\\ \\|\\(\\)\\<\\>"
results of executing
irb -> system Escape.shell_command([ "echo", %q{!&'"`$0 |()<>} ])
!&'"`$0 |()<>

Correct!

irb -> system("echo " + %q{!&'"`$0 |()<>}.shell_escape)
!&'"`sh |()<>

Incorrect! $0 was interpreted by sh as a special token (as a variable, which it ended up replacing with its value)

irb -> system("echo '" + %q{!&'"`$0 |()<>}.shell_escape + "'")
irb -> system("echo '" + %q{'}.shell_escape + "'")
sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file

Incorrect! sh thought it was invalid syntax (and sh is usually right when it comes to these matters!)

irb -> system('echo "' + %q{!&'"`$0 |()<>}.shell_escape + '"')
!\&\'"`sh\ \|\(\)\<\>

irb -> system("echo", %q{!&'"`$0 |()<>}.shell_escape)
!\&\'\"\`$0\ \|\(\)\<\>

Incorrect! Introduced unwanted \ characters in the output.

Question:

It looks like Escape.shell_command expects its output to be expanded by the shell. (system(line_subject_to_shell_expansion))

irb -> system Escape.shell_command([ "echo", %q{!&'"`$0 |()<>} ])
!&'"`$0 |()<>

Correct!

But not so much this:

irb -> system("echo", Escape.shell_command(%q{!&'"`$0 |()<>}))
'!&'\''"`$0 |()<>'

Incorrect!

Unlike with Escape.shell_command, which always encloses arguments with '' and always expects to be used in a system(line_subject_to_shell_expansion)-style calls, it is not clear whether the user of String#shell_escape is supposed to manually enclose all args when passing them to system/exec (and if so, with what kind of quotes), nor whether it expects its output to be used with system(line_subject_to_shell_expansion) or system(command, arg1_no_shell_expansion, ...).

"So how am I supposed to use .shell_escape??"

Test: Given as input 3 args, some of which contain spaces, the escaper produce a string that, when fed to a console app, will that program see exactly 3 args (rather than, say, > 3)?

generated string
irb -> Escape.shell_command(["./show_args.rb", 'arg1', 'multiple words for single argument', 'arg3'])
    => "./show_args.rb arg1 'multiple words for single argument' arg3"
irb -> ["./show_args.rb", 'arg1', 'multiple words for single argument'.shell_escape, 'arg3'].join(' ')
    => "./show_args.rb arg1 multiple\\ words\\ for\\ single\\ argument arg3"
results of executing
irb -> system Escape.shell_command(["./show_args.rb", 'arg1', 'multiple words for single argument', 'arg3'])
["arg1", "multiple words for single argument", "arg3"]
arg1
multiple words for single argument
arg3

Correct!

system ["./show_args.rb", 'arg1', 'multiple words for single argument'.shell_escape, 'arg3'].join(' ')
["arg1", "multiple words for single argument", "arg3"]
arg1
multiple words for single argument
arg3

Correct!

generated string
irb -> Escape.shell_command(["./show_args.rb", 'arg1', %q{'an arg that's got "quotes"}, 'arg3'])
    => "./show_args.rb arg1 \\''an arg that'\\''s got \"quotes\"' arg3"
irb -> ["./show_args.rb", 'arg1', %q{an arg that's 'got' "quotes"}.shell_escape, 'arg3'].join(' ')
    => "./show_args.rb arg1 an\\ arg\\ that\\'s\\ \\'got\\'\\ \\\"quotes\\\" arg3"
results of executing
irb -> system Escape.shell_command(["./show_args.rb", 'arg1', %q{'an arg that's got "quotes"}, 'arg3'])
["arg1", "'an arg that's got \"quotes\"", "arg3"]
arg1
'an arg that's got "quotes"
arg3

Correct!

irb -> system ["./show_args.rb", 'arg1', %q{an arg that's 'got' "quotes"}.shell_escape, 'arg3'].join(' ')
["arg1", "an arg that's 'got' \"quotes\"", "arg3"]
arg1
an arg that's 'got' "quotes"
arg3

Correct!

pros
  • the generated strings are more concise than Facets' (fewer \'s)
  • passed all of my tests
  • part of the Escape library, which provides other useful escaping facilities
  • part of the Facets library, which is a great library that I usually require anyway
cons
  • lacks unit tests
  • fails my tests
  • does more escaping than necessary, if one assumes that the result of this command is to be enclosed by marks (which protects most things from bash without the need for escaping)

Rules / Notes

  • Convention apparently followed: Produce the simplest output that will work. So omit the ' unless they are necessary.
conclusion winner
> cat show_args.rb
#!/usr/bin/ruby
p ARGV
puts ARGV


Ruby libraries  edit   (Category  edit)

Personal tools