shell cd command in Ruby - ruby

Im trying to execute shell commands using ruby, but i cant change directory to PATH with blank spaces.
variable = %x[cd #{ENV["HOME"]}/Virtual\\ VMs/]
This is not working.
Thank you

To be absolutely safe:
path = File.join [ENV["HOME"], 'Virtual VMs']
variable = %x[cd '#{path}']
Please note, that cd has empty output, so to make sure it works one probably wants to do smth like:
path = File.join [ENV["HOME"], 'Virtual VMs']
variable = %x[cd '#{path}' && ls -la]
#⇒ "total 32\ndrwxr-xr-x ....."

What is ist supposed to do? You try to chdir into a directory, but then don't do anything in it. Your variable will be empty in any case. Aside from the fact that it is pointless to do, you can not reliably execute a cd by itself in this way, because it is not an executable file. You can see this if you just execute %x[cd]. You will get an Errno::ENOENT exception.
Maybe you should first describe in a broader context, what you want to achieve with your code. Where would you like to change the working directory? Within the Ruby process - in which case you have to use Dir.chdir - or in the child process - in which case you have to execute some command after the cd.

Related

Dir.chdir(File.dirname(__FILE__)) throws Errno::ENOENT

I got a method where I use Dir.chdir(File.dirname(__FILE__)) inside. I am using it so that I can run the ruby file from anywhere and not get this error: No such file or directory # rb_sysopen - 'filename' (Errno::ENOENT).
Using the method for the first time works fine but using it for the second time throws an error. See method and exact error below.
def meth(string)
Dir.chdir(File.dirname(__FILE__))
hash = JSON.parse(File.read("file.json"))
# do something with hash and string
# return some value
end
meth("first string") # this returns what is expected
meth("second string") # this second usage of the method throws the error
Error sample pinpointing the line where I used Dir.chdir(File.dirname(__FILE__)):
dir/decoder.rb:44:in `chdir': No such file or directory # dir_s_chdir - lib (Errno::ENOENT)
Not sure if OS plays a role here, I am using an m1 BigSur on version 11.2.3.
Why is this happening?
What needs to be done so that the method` can be used as much as needed without running into the error?
Your problem here seems to be that __FILE__ is a relative path like dir/decoder.rb and that path becomes invalid after the first time Dir.chdir is used, because that command changes the working directory of your entire Ruby process. I think the solution would be to do something like this in your decoder.rb file:
DecoderDir = File.realpath(File.dirname(__FILE__))
def meth
Dir.chdir(DecoderDir)
# ...
end
I'm guessing that the first time the Ruby interpreter processes the file, that is early enough that the relative path in __FILE__ still refers to the right place. So, at that time, we generate an absolute path for future use.
By the way, a well-behaved library should not run Dir.chdir because it will affect all use of relative paths throughout your entire Ruby process. I pretty much only run Dir.chdir once and I run it near the beginning of my top-level script. If you're making a reusable library, you might want to do something like this to calculate the absolute path of the file you want to open:
DecoderJson = File.join(DecoderDir, 'file.json')

Ruby Project - Prevent a ruby file from directly being called from OS command line

I am doing a demo command line project in Ruby. The structure is like this:
/ROOT_DIR
init.rb
/SCRIPT_DIR
(other scripts and files)
I want users to only go into the application using init.rb, but as it stands, anyone can go into the sub-folder and call other ruby scripts directly.
Questions:
What ways can above scenario be prevented?
If I was to use directory permissions, would it get reset when running the code from a Windows machine to on Linux machine?
Is there anything that can be included in Ruby files itself to prevent it from being directly called from OS command line?
You can't do this with file permissions, since the user needs to read the files; removing the read permission means you can't include it either. Removing the execute permission is useful to signal that these file aren't intended to be executed, but won't prevent people from typing ruby incl.rb.
The easiest way is probably to set a global variable in the init.rb script:
#!/usr/bin/env ruby
FROM_INIT = true
require './incl.rb'
puts 'This is init!'
And then check if this variable is defined in the included incl.rb file:
unless defined? FROM_INIT
puts 'Must be called from init.rb'
exit 0
end
puts 'This is incl!'
A second method might be checking the value of $PROGRAM_NAME in incl.rb; this stores the current program name (like argv[0] in many other languages):
unless $PROGRAM_NAME.end_with? 'init.rb'
puts 'Must be called from init.rb'
exit 0
end
I don't recommend this though, as it's not very future-proof; what if you want to rename init.rb or make a second script?

System vs Local Environment in Rakefile

We're using rake for some setup steps. We need to make sure that a particular directory is in the PATH, and we've used this in Rakefile to do so:
hasImageMagic = ENV["Path"] =~ /ImageMagick/
if not hasImageMagic ...
When run Rake under git-bash, this works fine.
When we run Rake run under Powershell however, this fails to find the element in our Path. We've found that it's looking at the user's local Path rather than the system path.
Does ruby have a way to look at the system path variable?
You can use the GetEnvironmentVariable() method to specify that it's a machine-wide environment variable you're interested in:
$PathVar = [Environment]::GetEnvironmentVariable("Path",[System.EnvironmentVariableTarget]::Machine)
if($PathVar -notmatch "ImageMagick"){
# error handling here
}
#StevenV sorry, this is my fault. I had originally written this code and only tested it in gitbash:
hasImageMagic = ENV["Path"] =~ /c:\\Program Files\\ImageMagick-/
if not hasImageMagic ...
While that worked fine in gitbash, I was later alerted by another team member that this did not work as expected in powershell. I then pushed the code you posted in your question, and it does seem to work with powershell:
hasImageMagic = ENV["Path"] =~ /ImageMagick/
if not hasImageMagic ...
I am personally still in the dark as to why gitbash liked the original regex but powershell did not. This is my first ruby code ever, so mea culpa.

How to prevent capistrano replacing newlines?

I want to run some shell scripts remotely as part of my capistrano setup. To test that functionality, I use this code:
execute <<SHELL
cat <<TEST
something
TEST
SHELL
However, that is actually running /usr/bin/env cat <<TEST; something; TEST which is obviously not going to work. How do I tell capistrano to execute the heredoc as I have written it, without converting the newlines into semicolons?
I have Capistrano Version: 3.2.1 (Rake Version: 10.3.2) and do not know ruby particularly well, so there might be something obvious I missed.
I think it might work to just specify the arguments to cat as a second, er, argument to execute:
cat_args = <<SHELL
<<TEST
something
TEST
SHELL
execute "cat", cat_args
From the code #DavidGrayson posted, it looks like only the command (the first argument to execute) is sanitized.
I agree with David, though, that the simpler way might be to put the data in a file, which is what the SSHKit documentation suggests:
Upload a file from a stream
on hosts do |host|
file = File.open('/config/database.yml')
io = StringIO.new(....)
upload! file, '/opt/my_project/shared/database.yml'
upload! io, '/opt/my_project/shared/io.io.io'
end
The IO streaming is useful for uploading something rather than "cat"ing it, for example
on hosts do |host|
contents = StringIO.new('ALL ALL = (ALL) NOPASSWD: ALL')
upload! contents, '/etc/sudoers.d/yolo'
end
This spares one from having to figure out the correct escaping sequences for something like "echo(:cat, '...?...', '> /etc/sudoers.d/yolo')".
This seems like it would work perfectly for your use case.
The code responsible for this sanitization can be found in SSHKit::Command#sanitize_command!, which is called by that class's initialize method. You can see the source code here:
https://github.com/capistrano/sshkit/blob/9ac8298c6a62582455b1b55b5e742fd9e948cefe/lib/sshkit/command.rb#L216-226
You might consider monkeypatching it to do nothing by adding something like this to the top of your Rakefile:
SSHKit::Command # force the class to load so we can re-open it
class SSHKit::Command
def sanitize_command!
return if some_condition
super
end
end
This is risky and could introduce problems in other places; for example there might be parts of Capistrano that assume that the command has no newlines.
You are probably better off making a shell script that contains the heredoc or putting the heredoc in a file somewhere.
Ok, so this is the solution I figured out myself, in case it's useful for someone else:
str = %x(
base64 <<TEST
some
thing
TEST
).delete("\n")
execute "echo #{str} | base64 -d | cat -"
As you can see, I'm base64 encoding my command, sending it through, then decoding it on the server side where it can be evaluated intact. This works, but it's a real ugly hack - I hope someone can come up with a better solution.

Ruby fileutils: Forcing symlink creation from within cp_r

I'm attempting to recursively copy directories with Ruby's cp_r method in fileutils. However, it crashes in the (silly but out of my control) case where in the directory I copy there is a file and a symbolic link to that file. I get the following error:
/usr/lib/ruby/1.8/fileutils.rb:1223:in `symlink': File exists - real_file_name or symbolic_link_filename (Errno::EEXIST)
Now, I might be wrong, but it seems like something that symlink's :force option should take care of; however, cp_r cannot take a :force option, and I see no way to make it pass that option to its internal calls to symlink. Also, catching the EEXISTS error doesn't seem to be a solution since the run of cp_r would still be interrupted.
Is there a clean way to get around this problem?
You can use FileUtils.cp_r and pass it the option remove_destination: true to accomplish this.

Resources