views:

148

answers:

3

I'm trying to pass a byte array from inside my rails app into another ruby script (still inside my rails app), for example:

`./app/animations/fade.sh "\x01\x01\x04\x00" &`

Yields ArgumentError (string contains null byte)

I suppose I'm stumped with how I can form this string and than pass it to my script, which will use it in this sort of fashion:

@sp.write ["#{ARGV[0]}", "f", "\x12"]

I'd like to form the string (on my rails app) like this if possible:

led = "\x01#{led.id}\x04\x00"

But I keep getting ArgumentError (string contains null byte) error. Is there a way I can form this string from elements in my rails app, then pass it to my external script?

+1  A: 

Can your script accept input from STDIN instead? Perhaps using read.

If you can't do this, you could encode your null and escape your encoding.
E.G. 48656c6c6f0020576f726c64 could be encoded as 48656c6c6f200102020576f726c64
which in turn would be decoded again if both sides agree 2020=20 and 2001=00

Update I think encoding is what you'll have to do because I tried using read and it turns out to be a little too difficult. There's probably another option, but I don't see it yet.

Here's my script and two test runs:

dlamblin$ cat test.sh
echo "reading two lines of input, first line is length of second."
read len
read ans
echo "C string length of second line is:" ${#ans}
for ((c=0; c<$len; c++))
do
    /bin/echo -n "${ans:$c:1},"
done
echo ' '
exit

dlamblin$ echo -e '12\0012Hello \0040World' | sh test.sh
reading two lines of input, first line is length of second.
C string length of second line is: 12
H,e,l,l,o, , ,W,o,r,l,d, 

dlamblin$ echo -e '12\0012Hello \0000World' | sh test.sh
reading two lines of input, first line is length of second.
C string length of second line is: 5
H,e,l,l,o,,,,,,,, 

#Octals \0000 \0012 \0040 are NUL NL and SP respectively
dlamblin
Well I need to generate it's input with rails. It works with just a good ol' fashion string. But not bytes for some reason.
Joseph Silvashy
I'm assuming you know that arguments are passed as strings and that C strings are null terminated, and UNIX uses C strings. The behavior you see is not mysterious.
dlamblin
ahh. I see, So I can pass it an ASCII string, then in the script I can create the byte array? What would your plan of action be with this? +1
Joseph Silvashy
why roll your own encoding? base64 is there, it works, and it's accessible both from ruby and from bash.
Martin DeMello
+1  A: 

you could use base64 to pass the bytestring around

$ cat > test.sh
echo $1 | base64 -d
$ chmod a+x test.sh

and then from ruby:

irb
>> require 'base64'
=> true
>> `./test.sh "#{Base64.encode64 "\x01\x01\x04\x00"}"`
=> "\x01\x01\x04\x00"
Martin DeMello
+1  A: 

You should just pass the data in through standard input, not the command line. You can use IO.popen for this purpose:

IO.popen("./app/animations/fade.sh", "w+") do |f| 
  f.write "\x01\x01\x04\x00"
end

And on the reading side:

input = $stdin.read
@sp.write [input, "f", "\x12"]

(By the way, it's more common to name Ruby scripts .rb instead of .sh; if fade.sh is meant to be a Ruby script, as I assume from the syntax you used in its example contents, you might want to name it fade.rb)

Brian Campbell
Oh, I was assuming `fade.sh` was a real `sh` script. Hence my test with `sh`. Clearly ruby can read bytes from `stdin` a little better.
dlamblin