Interpolate string to make a hex value

I want to interpolate an int value into hex, but crystal is not allowing it

for example I have a hex command

"\x01\x20"

and want to attach another hex value at the end based on a string value to end up with

"\x01\x20\x06"

so I have the value

str_val="06"

but then I can’t interpolate this

so all variations of this attempt fail

"\x01\x20\x#{str_val}"

with errors equivalent to Error: invalid hex escape

is there a way to do this?

thank you

I’m pretty sure the \x syntax is only for string literals. Can you share more about what you’re ultimately wanting to do? There might be a better way.

nothing fancy I’m just trying to concatenate the string of escaped hex values

"\x01\x20" to create "\x01\x20\x06" with “06” as input parameter

I mean beyond this immediate problem. What’s the end goal of all this?

As I mentioned, I’m pretty sure you can’t do this as the escape sequence is only valid for string literals, i.e. "\x01\x20" not interpolated values.

there’s not much beyond this really. it’s really just to concat the above and send the value "\x01\x20\x06" to a function . .

Could you not do something like:

str_val = "\x06"

str = "\x01\x20#{str_val}"

str.bytes # => [1, 32, 6]

The problem is the escape sequence can’t be a dynamic value.

EDIT: You also could get away with like

str_val = "06"

str = "0120#{str_val}"

str.hexbytes => Bytes[1, 32, 6]

As if the string is dynamic, you don’t need to use \x to work with hex values.

that would be nice, but I’m getting the “06” from a function

pseudo:

def set_pos(pos: String | Int32)
   val = pos if @some_range.includes(pos.to_i32)
   use_pos_to_create_command(pos)
end

def use_pos_to_create_command(pos)
   return "\x01\x20\x#{pos}"
end

thanks…
i’ll try .hexbytes … painful - will need to refactor everything because of this

If you share more of what you’re trying to do we can probably help more… Like why the need for raw hex escapes? Do you ultimately just need Bytes? Could you leverage an IO to write the bytes/hex values to then generate the full hex string at the end?

i’m using @channel = Channel(String).new to send this byte string via transport and also using tokeniser to attach \r at the end of the transported data Tokenizer.new("\r") so not sure that .hexbytes will help
pain

In that case just don’t use \x? return "0120#{pos}". tada? If you need to write the string in various places, you could use an IO.

str_val = "06"

io = IO::Memory.new

io << "01"

io << "20"

def append_pos(io : IO, pos : String) : Nil
  # This is moreso to show you can just pass the `IO` around.
  io << pos
end

append_pos io, str_val

puts io # => 012006

Would these string/int/base converters be applicable?:

You just need to parse the value as an integer codepoint and add the equivalent character:

str_val="06"
"\x01\x20#{str_val.to_i.chr}" # => "\x01\x20\x06"
1 Like

Right. And it’s better to use unsafe_chr in case it’s not ascii

What? Why would unsafe_chr be better?
chr works with any Unicode codepoint, not just the ASCII range. And it raises if the value happens to be an invalid codepoint.

P.S. Depending on the exact input format, you might need something like to_i(16) to parse a hexadecimal value.

Because presumably if you get a value like FF you’d like a byte with that value, not the char with unicode value 255. After all they want to use it with \x which is just 0-255

Oh, unsafe_chr doesn’t work.

I guess OP should explain why they receive such input and who is sending that value.

This worked nicely thank you @straight-shoota

For context, this is a migration of some Ruby code which used escaped concatenated ASCII to send commands, In Crystal, their equivalent in binary strings are intended to be used.

I was going to go through the pain of refactoring to use straight Bytes , but this workaround saved the day. Cheers!