I'm trying to rewrite a command to replace output files with the same files in a different directory, building up a translation map. In Perl I could do something like this:
s{(-o|--out)\s((?:\S+\/)?(\S+))}{ "$1 " . ($m{$2}="foo/$3") }eg
I'm not clear how to do the same under Ruby, because:
"-out AAA -out yeah/BBB".
gsub(/((?:\s|^)-out)\s+((?:\S+\/)?(\S+))/) { |f|
"#{f[1]} #{m[f[2]] = "temp/#{f[3]}"}"
}
results in
"o temp/t- temp/u"
because 'm' is the matched string, not a match object, so #{m[1]} is just the second character of the matched string.
I don't want to use $1, $2 because Rubocop says they're evil, and I'd prefer not to use 'Regexp.last_match' because it's quite remarkably verbose and turns this one-liner into a do-block.
Is there no built-in that gives me the match object as the parameter?
Once you do not want to use $1 as you did in perl, you might use named matches:
"-out AAA -out BBB".gsub(/(?<leading>\s|^)(?<out>-out)\s+(?<rest>\S+)/) do
"#{$~[:leading]}#{$~[:out]} BLOCK"
end
#⇒ "-out BLOCK -out BLOCK"
Another option would be to shut rubocop up, since you know what you are doing:
#rubocop:disable Style/PerlBackrefs
"-out AAA -out BBB".gsub(/(\s|^)(-out)\s+(\S+)/) do
"#{$1}#{$2} BLOCK"
end
#rubocop:enable Style/PerlBackrefs
Related
I have a program that tries to open a file:
Dir.chdir(File.dirname(__FILE__))
puts "Enter file name: ";
relPath = gets;
absPath = Dir.pwd << "/" << relPath;
if File.exist?(absPath) then
puts "File exists";
file = File.open(absPath, "r");
other code...
else
puts "File does not exist";
end
It always prints "File does not exist" even when the current directory exists and the file also exists. The file and script are in the same directory.
I am running it on Mac OS X Yosemite (10.10.3) and Ruby 2.2.0p0.
I can't explain why (albeit I have strong belief that it's for some whitespace characters) but with this little contribution it works ok.
Dir.chdir(File.dirname(__FILE__))
print "Enter file name:";
relPath = gets.chomp; #intuitively used this, and it wroked fine
absPath = File.expand_path(relPath) #used builtin function expand_path instead of string concatenation
puts absPath
puts File.file?(absPath)
if File.exist?(absPath) then
puts "File exists";
puts File.ctime(absPath) #attempting a dummy operation :)
else
puts "File does not exist";
end
runnning code
$ ls -a anal*
analyzer.rb
$ ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]
ziya#ziya:~/Desktop/code/ruby$ ruby fileexists.rb
Enter file name:analyzer.rb
/home/ziya/Desktop/code/ruby/analyzer.rb #as a result of puts absPath
true #File.file?(absPath) => true
File exists
2015-06-11 12:48:31 +0500
That code has syntax error ("if" doesnt need "then"), and you dont have to put ";" after each line.
try
Dir.chdir(File.dirname(__FILE__))
puts "Enter file name: "
relPath = gets
absPath = "#{Dir.pwd}/#{relPath.chop}"
if File.exist?(absPath)
puts "File exists"
file = File.open(absPath, "r")
else
puts "File does not exist"
end
remember that gets will add a new line character so you will need to do a chomp, and that way to concatenate string won't work on ruby.
Your code is not idiomatic Ruby. I'd write it something like this untested code:
Dir.chdir(File.dirname(__FILE__))
puts 'Enter file name: '
rel_path = gets.chomp
abs_path = File.absolute_path(rel_path)
if File.exist?(abs_path)
puts 'File exists'
File.foreach(abs_path) do |line|
# process the line
end
else
puts 'File does not exist'
end
While Ruby supports the use of ;, they're for use when we absolutely must provide multiple commands on one line. The ONLY time I can think of needing that is when using Ruby to execute single-line commands at the command-line. In normal scripts I've never needed ; between statements.
then is used with if when we're using a single line if expression, however, we have trailing if which removes the need for then. For instance, these accomplish the same thing but the second is idiomatic, shorter, less verbose and easier to read:
if true then a = 1 end
a = 1 if true
See "What is the difference between "if" statements with "then" at the end?" for more information.
Instead of relPath and absPath we use snake_case for variables, so use rel_path and abs_path. It_is_a_readability AndMaintenanceThing.
File.absolute_path(rel_path) is a good way to take the starting directory and return the absolute path given a relative directory.
File.foreach is a very fast way to read a file, faster than slurping it using something like File.read. It is also scalable whereas File.read is not.
I'm trying to generate a custom fact called domains.
the idea is to list all the directories within /home but remove some default directory's such as centos, ec2-user, myadmin.
I'm using bash as I don't know ruby. so far my script outputs the list into a txt file which it then cats the answer for factors. but it is treated as one long answer and not multiple like an array?
My script is as follows:
#!/bin/bash
ls -m /home/ | sed -e 's/, /,/g' | tr -d '\n' > /tmp/domains.txt
cat /tmp/domains.txt | awk '{gsub("it_support,", "");print}'| awk '{gsub("ec2-user,", "");print}'| awk '{gsub("myadmin,", "");print}'| awk '{gsub("nginx", "");print}'| awk '{gsub("lost+found,", "");print}' > /tmp/domains1.txt
echo "domains={$(cat /tmp/domains1.txt)}"
exit
Foremans sees my domains as
facts.domains = "{domain1,domain2,domain3,domain4,lost+found,}"
I also need to remove lost+found, some how.
Any help or advice would be appreciated
Kevin
I'm also not familiar with ruby, but I have an idea for some workaround:
Please look at the following example about returning an array of network interfaces. Now to create domain_array fact use the following code:
Facter.add(:domain_array) do
setcode do
domains = Facter.value(:domains)
domain_array = domains.split(',')
domain_array
end
end
You can put a parser function to do this. Parser functions go inside:
modules/<modulename>/lib/puppet/parser/functions/getdomain.rb
Note: Parser function will compile only in the puppet master. See below for a custom fact that will run on the agent.
getdomain.rb can contain something like the following for your purpose:
module Puppet::Parser::Functions
newfunction(:getdomain, :type => :rvalue) do |args|
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
domainlist=dnames.join(',')
return domainlist
end
end
You can call it from a manifest and assign to a variable:
$myhomedomains=getdomain()
$myhomedomains should return something similar to this : user1,user2,user3
.......
For a custom fact with similar code. You can put it in :
modules/<modulename>/lib/facter/getdomain.rb
Content of getdomain.rb :
Facter.add(:getdomain) do
setcode do
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
getdomain=dnames.join(',')
getdomain
end
end
You can call the getdomain fact in any manifest, for example, calling it from the same module's init.pp :
notify { "$::getdomain" : }
will result in something similar :
Notice: /Stage[main]/Testmodule/Notify[user1,user2,user3]
I want to parse user input using named captures for readability.
When they type a command I want to capture some params and pass them. I'm using RegExps in a case statement and thus I can't assign the return of /pattern/.named_captures.
Here is what I would like to be able to do (for example):
while command != "quit"
print "Command: "
command = gets.chomp
case command
when /load (?<filename>\w+)/
load(filename)
end
end
named captures set local variables when this syntax.
regex-literal =~ string
Dosen't set in other syntax. # See rdoc(re.c)
regex-variable =~ string
string =~ regex
regex.match(string)
case string
when regex
else
end
I like named captures too, but I don't like this behavior.
Now, we have to use $~ in case syntax.
case string
when /(?<name>.)/
$~[:name]
else
end
This is ugly but works for me in Ruby 1.9.3:
while command != "quit"
print "Command: "
command = gets.chomp
case command
when /load (?<filename>\w+)/
load($~[:filename])
end
end
Alternatively you can use the English extension of $~, $LAST_MATCH_INFO.
In puppet, if define command is > 80 characters, how can I wrap into two line to do it?
exec { 'create_domain':
command => "some command exceed 80 character...........................................................how to do how to do?.......",
}
It's sort of ugly, but if the last character in a string is a \ followed by a newline, then the string is continued on the next line. My sample.pp manifest is below:
exec { 'wrapped_string_example':
command => "/bin/echo 12345678901234567890123456789012345678901234567890\
wrapped > /var/tmp/test.txt";
}
Running this with puppet apply sample.pp gives the following output
$ puppet apply sample.pp
notice: /Stage[main]/Exec[wrapped_string_example]/returns: executed successfully
notice: Finished catalog run in 0.10 seconds
And catting the created file shows the lines have wrapped:
$ cat /var/tmp/test.txt
12345678901234567890123456789012345678901234567890wrapped
See https://github.com/puppetlabs/puppet/blob/9fbb36de/lib/puppet/parser/lexer.rb#L537 (as of Puppet v2.7.0)
Also this is sort of a known issue: http://projects.puppetlabs.com/issues/5022
For big chunks of data, heredocs are the best way of dealing with long lines in Puppet manifests. The /L interpolation option is particularly useful. /L causes \ at the end of a line to remove newlines. For example, the following does what you'd expect, stripping indentation and newlines, including the trailing newline.
sshkey { 'example.com':
ensure => present,
type => 'ssh-rsa',
key => #(KEY/L),
RfrXBrU1T6qMNllnhXsJdaud9yBgWWm6OprdEQ3rpkTvCc9kJKH0k8MNfKxeBiGZVsUn435q\
e83opnamtGBz17gUOrzjfmpRuBaDDGmGGTPcO8Dohwz1zYuir93bJmxkNldjogbjAWPfrX10\
8aoDw26K12sK61lOt6GTdR9yjDPdG4zL5G3ZjXCuDyQ6mzcNHdAPPFRQdlRRyCtG2sQWpWan\
3AlYe6h6bG48thlo6vyNvOD8s9K0YBnwl596DJiNCY6EsxnSAhA3Uf9jeKqlVqqrxhEzHufx\
07iP1nXIXCMUV
|-KEY
target => '/home/user/.ssh/authorized_keys',
}
Or to keep the final newline, leave out the - before the end text:
exec { 'create_domain':
command => #(CMD/L),
/bin/echo 123456789012345678901234567890123456789012345678901234567890123456\
wrapped > /var/tmp/test.txt
| CMD
}
As of Puppet 3.5 you have a couple of options that i have used. Ruby allows you to concat strings over a couple of lines.
string = "line #1"\
"line #2"\
"line #3"
p string # => "line #1line #2line #3"
Another option, as of Puppet 3.5 they have added HereDoc functionality. This will allow you to put the string in a section of a source code file that is treated as if it were a separate file.
$mytext = #(EOT)
This block of text is
visibly separated from
everything around it.
| EOT
The puppet documentation is here: https://docs.puppet.com/puppet/4.9/lang_data_string.html#heredocs
If you really care about the 80cols limit you can always abuse a template to achieve that goal
exec {'VeryLongExec':
command => template("${module}/verylongexec")
}
Then put the actual command in that template file
Credits should go to Jan Vansteenkiste to figure
I want to parse user input using named captures for readability.
When they type a command I want to capture some params and pass them. I'm using RegExps in a case statement and thus I can't assign the return of /pattern/.named_captures.
Here is what I would like to be able to do (for example):
while command != "quit"
print "Command: "
command = gets.chomp
case command
when /load (?<filename>\w+)/
load(filename)
end
end
named captures set local variables when this syntax.
regex-literal =~ string
Dosen't set in other syntax. # See rdoc(re.c)
regex-variable =~ string
string =~ regex
regex.match(string)
case string
when regex
else
end
I like named captures too, but I don't like this behavior.
Now, we have to use $~ in case syntax.
case string
when /(?<name>.)/
$~[:name]
else
end
This is ugly but works for me in Ruby 1.9.3:
while command != "quit"
print "Command: "
command = gets.chomp
case command
when /load (?<filename>\w+)/
load($~[:filename])
end
end
Alternatively you can use the English extension of $~, $LAST_MATCH_INFO.