Why does question mark get interpreted as "z" in Ruby - ruby

Why does this line output "z" instead of "?"
$ ruby -e 'puts %x[ echo #{"?"} ]'
Suppose the expression inside the #{...} is a variable that may have the value of "?". How should I modify this script so that the question mark is outputted instead of "z"?
(Please forgive the title of this question -- I don't yet understand what is going on here well enough to provide a more descriptive title.)

It's not ruby, it's your shell.
Many shells expand the ? character to match a single character in command line arguments.
It's useful, if you have a bunch of files name tempA,temp1,tempB,...,temp9 that you want to delete, but you don't want to delete 'temple'
% rm temp?
So I'm guessing you have a file or directory in your working directory named 'z', and the ? matches that, so it gets replaced by the shell.
Normally, when inside single quotes (like your ruby script) it wouldn't get expanded, but since you're passing the question mark to a shell command, it gets expanded there.
% ruby -e 'puts %x[ echo ? ]'
z
%
Should show you the same behaviour.
Also, if you touch a couple other single character filenames like a b c d, those should show up too:
% touch a b c
% ruby -e 'puts %x[ echo ? ]'
a b c z
%
If you want to avoid this when calling exterior shell commands from within ruby, you'll have to escape any strings you pass out. For most purposes String#inspect should give a good enough escaping.

It doesn't?
irb(main):001:0> puts %x[echo #{"?"}]
?
=> nil
Using #{} will give you the value of any variables inside - I'm not sure why you're using it instead of
puts %x[echo "?"]
or just
puts '?'

Related

Bash - meaning of a simple question mark (?)

I was playing around with some bash features and as I tried echo-ing some output, I noticed, that
echo what about in some more complex example ?
results in
what about in some more complex example \
I know that escaping the question mark or the whole line with quotes resolves the problem, but I am curious of why is it happening.
So my 2 questions are:
What is the meaning of a simple question mark in bash (I know for example about the '$?' special parameter and regex usage) ?
I suppose that is is a bash environment variable or some king of variable. How can I inspect a variable ? For example a command can be inspected with type keyword, i.e.
type cd
In that context it functions as a glob pattern. If there are files with one-character names in the current working directory, the shell expands an unquoted question mark to their names.
$ echo ? \? '?' "?"
? ? ? ?
$ touch a b c
$ echo ? \? '?' "?"
a b c ? ? ?
Similarly, ?? is expanded to two-character filenames, ??* to filenames longer than one character, and ??[ab] to three-character filenames ending with an a or a b, etc.
See Filename Expansion for further information.

Theory: who can explain the use of =

can someone explain me with this code
data=$(date +"%Y-%m-%dS%H:%M:%S")
name="/home/cft/"$data"_test.tar"
touch $name
works, creating a new .tar file but this code doesn't work
data=$(date +"%Y-%m-%dS%H:%M:%S")
name= "/home/cft/"$data"_test.tar"
touch $name
and gives me this error: no such file or directory?
why the space between = and inverted commas creates this error?
Shell allows you to provide per-command environment overrides by prefixing the command with one or more variable assignments.
name= "/home/cft/"$data"_test.tar"
asks the shell to run the program named /home/cft/2013-10-08S12:00:00_test.tar (for example) with the value of name set to the empty string in its environment.
(In your case, the error occurs because the named tar file either doesn't exist or, if it does, is not an executable file.)
A variable assignment is identified by having no whitespace after the equal sign.
(name = whatever, of course, is simply a command called name with two string arguments, = and whatever.)
You can't have whitespace between the equal sign and the definition.
http://www.tldp.org/LDP/abs/html/varassignment.html
There is no theory behind this. It's just a decision the language designers made, and which the parser enforces.
In BASH (and other Bourne type shells like zsh and Kornshell), the equal sign cannot have spaces around it when setting variables.
Good:
$ foo="bar"
Bad:
$ foo= "bar"
$ foo = "bar"
There's no real reason that would prevent spaces from being used. Other programming languages have no problems with this. It's just the syntax of the shell itself.
The reason might be related to the original Bourne shell parsing where the shell would break up a command line based upon whitespace. That would make foo=bar a single parameter instead of two or three (depending if you have white space on both sides or just one side of the equal sign). The shell could see the = sign, and know this parameter is an assignment.
The shell parameter parsing is very primitive in many ways. Whitespace is very important. The shell has to be small and fast in order to be responsive. That means stripping down unessential things like complex line parsing.
Inverted commas I believe you mean quotation marks. Double quotes are used to override the breaking out of parameters over white space:
Bad:
$ foo=this is a test
bash: is: command not found
Good:
$ foo="this is a test"
Double quotes allow interpolation. Single quotes don't:
$ foo="bar"
$ echo "The value of foo is $foo"
The value of foo is bar
$ echo 'The value of foo is $foo'
The value of foo is $foo.
If you start out with single quotes, you can put double quotes inside. If you have single quotes, you can put double quotes inside.
$ foo="bar"
$ echo "The value of foo is '$foo'"
The value of foo is 'bar'
$ echo 'The value of foo is "$foo"'
The value of foo is "$foo"
This means you didn't have to unquote $data. However, you would have to put curly braces around it because underscores are legal characters in variable names. Thus, you want to make sure that the shell understand that the variable is $data and not $data_backup:
name="/home/cft/${data}_test.tar"

Convert Hex STDIN / ARGV / gets to ASCII in ruby

my Question is how I can convert the STDIN of cmd ARGV or gets from hex to ascii
I know that if I assigned hex string to variable it'll be converted once I print it
ex
hex_var = "\x41\41\x41\41"
puts hex_var
The result will be
AAAA
but I need to get the value from command line by (ARGV or gets)
say I've this lines
s = ARGV
puts s
# another idea
puts s[0].gsub('x' , '\x')
then I ran
ruby gett.rb \x41\x41\x41\x41
I got
\x41\x41\x41\x41
is there a way to get it work ?
There are a couple problems you're dealing with here. The first you've already tried to address, but I don't think your solution is really ideal. The backslashes you're passing in with the command line argument are being evaluated by the shell, and are never making it to the ruby script. If you're going to simply do a gsub in the script, there's no reason to even pass them in. And doing it your way means any 'x' in the arguments will get swapped out, even those that aren't being used to indicate a hex. It would be better to double escape the \ in the argument if possible. Without context of where the values are coming from, it's hard to say with way would actually be better.
ruby gett.rb \\x41\\x41
That way ARGV will actually get '\x41\x41', which is closer to what you want.
It's still not exactly what you want, though, because ARGV arguments are created without expression substitution (as though they are in single quotes). So Ruby is escaping that \ even though you don't want it to. Essentially you need to take that and re-evaluate it as though it were in double quotes.
eval('"%s"' % s)
where s is the string.
So to put it all together, you could end up with either of these:
# ruby gett.rb \x41\x41
ARGV.each do |s|
s = s.gsub('x' , '\x')
p eval('"%s"' % s)
end
# => "AA"
# ruby gett.rb \\x41\\x41
ARGV.each do |s|
p eval('"%s"' % s)
end
# => "AA"
Backlashes entered in the console will be interpreted by the shell and will
not make it into your Ruby script, unless you enter two backlashes in a row,
in which case you script will get a literal backlash and no automatic
conversion of hexadecimal character codes following those backlashes.
You can convert these escaped codes to characters manually if you replace the last line of your script with this:
puts s.gsub(/\\x([[:xdigit:]]{1,2})/) { $1.hex.chr }
Then run it with double backlashed input:
$ ruby gett.rb \\x41\\x42\\x43
ABC
When fetching user input through gets or similar, only a single backslash will be need to be entered by the user for each character escape, since that will indeed be passed to your script as literal backslashes and thus handled correctly by the above gsub call.
An alternative way when parsing command line arguments would be to let the shell interpret the character escapes for you. How to do this will depend on what shell you are using. If using bash, it can be done
like this:
$ echo $'\x41\x42\x43'
ABC
$ ruby -e 'puts ARGV' $'\x41\x42\x43'
ABC

Ruby string containing ${...}

In the Ruby string :
"${0} ${1} ${2:hello}"
is ${i} the ith argument in the command that called this particular file.
Tried searching the web for "Ruby ${0}" however the search engines don't like non-alphanumeric characters.
Consulted a Ruby book which says #{...} will substitute the results of the code in the braces, however this does not mention ${...}, is this a special syntax to substitute argvalues into a string, thanks very much,
Joel
As mentioned above ${0} will do nothing special, $0 gives the name of the script, $1 gives the first match from a regular expression.
To interpolate a command line argument you'd normally do this:
puts "first argument = #{ARGV[0]}"
However, ARGV is also aliased as $* so you could also write
puts "first argument = #{$*[0]}"
Perhaps that's where the confusion arose?

Ruby escape ARGV argument or string as argument to shell command

Ok this is driving me crazy:
`ls #{"/media/music/Miles Davis"}`
fails because of the space between "Miles" and "Davis"
Say I write a ruby script and a user passes file path as an argument. How do I escape it and feed to a shell-out command. Yes, yes, I know, shelling out should be avoided. But this is a contrived example, I still need this.
I would do system("ls", ARGV[0]), but it doesn't return the stdout output of ls as a string, which is what backticks do well.
How do escape whatever you insert in a shellout?
Use require 'shellwords' and Shellwords.escape, which will fix this sort of stuff for you:
http://apidock.com/ruby/Shellwords/shellescape
Stay away from building shell strings
This is a fine vector for arbitrary code execution.
In this case, you could use popen, which does the escaping for you, e.g.:
#!/usr/bin/env ruby
IO.popen(['printf', 'a b']) do |f|
puts f.read
end
This outputs:
a b
just as if we had run on the terminal:
/usr/bin/printf 'a b'
If a b hadn't been escaped, we wouldn't get a b as expected, because running an unquoted:
/usr/bin/printf a b
in the terminal gives:
a/usr/bin/printf: warning: ignoring excess arguments, starting with ‘b’
Tested in Ubuntu 20.02, Ruby 2.6.
Double quotes also works:
`ls "#{'/media/music/Miles Davis'}"`
or
`ls "#{ARGV[0]}"`

Resources