41

So I found this question on here, but I'm having an issue with the output and how to handle it with an if statement. This is what I have, but it's always saying that it's true even if the word monitor does not exist in the file

if File.readlines("testfile.txt").grep(/monitor/)
  do something
end

Should it be something like == "nil"? I'm quite new to ruby and not sure of what the outputs would be.

asheeshr
  • 4,088
  • 6
  • 31
  • 50
Danny
  • 5,180
  • 6
  • 26
  • 29
  • 2
    Note: Using `readlines` can really cause problems if the file is larger than the available memory. See "[Why is slurping a file bad?](http://stackoverflow.com/questions/25189262/why-is-slurping-a-file-bad/25189286#25189286)" for more information. – the Tin Man Dec 11 '14 at 22:27

5 Answers5

68

I would use:

if File.readlines("testfile.txt").grep(/monitor/).any?

or

if File.readlines("testfile.txt").any?{ |l| l['monitor'] }

Using readlines has scalability issues though as it reads the entire file into an array. Instead, using foreach will accomplish the same thing without the scalability problem:

if File.foreach("testfile.txt").grep(/monitor/).any?

or

if File.foreach("testfile.txt").any?{ |l| l['monitor'] }

See "Why is "slurping" a file not a good practice?" for more information about the scalability issues.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • 6
    +1 for `any?`, since this will end as soon as it finds the first match instead of processing every line just to see if any matched. – Phrogz Dec 07 '11 at 16:03
  • Keep in mind that this is not idempotent. Running it twice in a row will return `true` on the first run and `false` on the second. You need to `rewind` the file before running it again. – David Tuite Aug 25 '14 at 12:02
  • 1
    There is no need to `rewind`. `readlines` will open and close the file automatically so it always starts at the beginning and reads to the end. – the Tin Man Dec 11 '14 at 22:23
  • 2
    Again with the +1 for `any?`, though to quickly clarify, the approach to return as soon as you have a match is `any? { ... }` rather than `grep(...).any?`. Learned this the hard way, should pay closer attention :) – SRack Sep 12 '18 at 17:02
  • @SRack You're not alone. I did the same thing. I edited the answer to put the best method first and then show the alternatives with reasonings about why each alternative is inferior to the top method. – Joshua Pinter Jan 05 '20 at 16:13
  • @JoshuaPinter: I rolled your changes back because you edited code that I wrote. You are welcome to comment on code, but it's considered bad form to edit code. Please read "[Suggested Edits That Change Code](https://meta.stackoverflow.com/questions/266467/)" and "[Don't edit code in questions unless you know exactly what you're doing](https://meta.stackoverflow.com/q/303219/128421)". Add an answer if you want to note the differences as a possible solution. – the Tin Man Jan 05 '20 at 19:28
  • @theTinMan No worries. I thought you might do so. I made sure not to change your code, as it is the correct solution. Just wanted to elevate the best answer to the top and summarize the deficiencies of the other methods. All good. :) – Joshua Pinter Jan 05 '20 at 20:08
39

Enumerable#grep does not return a boolean; it returns an array (how would you have access to the matches without passing a block otherwise?).

If no matches are found it returns an empty array, and [] evaluates to true. You'll need to check the size of the array in the if statement, i.e.:

if File.readlines("testfile.txt").grep(/monitor/).size > 0
  # do something
end

The documentation should be your first resource for questions like this.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • 1
    Thanks for this post. you have help me a lot with that. but I was wondering if I need to search for a path? – MZaragoza Sep 17 '15 at 19:09
  • @MZaragoza: Depends on what kind of path. I'm sure if you search for regular expressions to match a path you'll find quite a few examples. – Ed S. Sep 18 '15 at 04:25
  • I am looking for a string in a file that looks like "/www/assests/2015/09/18/someimage.png" the problem is that there are other images names 'someimage.png' in other directories – MZaragoza Sep 18 '15 at 12:38
  • 1
    For paths, you may want to use a regular expression in grep. For eg ; File.readlines("/home/#{os_user}/.bash_profile").grep(%r{JAVA_HOME=\/opt\/Oracle\/jdk}).empty? – Abhishek Prusty Aug 23 '17 at 11:25
7

Grep will give you an array of all found 'monitor's. But you don't want an array, you want a boolean: is there any 'monitor' string in this file? This one reads as little of the file as needed:

if File.open('test.txt').lines.any?{|line| line.include?('monitor')}
  p 'do something'
end

readlines reads the whole file, lines returns an enumerator which does it line by line.

update

#lines are deprecated, Use #each_line instead

  if File.open('test.txt').each_line.any?{|line| line.include?('monitor')}
    p 'do something'
  end
CanCoder
  • 1,073
  • 14
  • 20
steenslag
  • 79,051
  • 16
  • 138
  • 171
0

if anyone is looking for a solution to display last line of a file where that string occurs just do

File.readlines('dir/testfile.txt').select{|l| l.match /monitor/}.last 

example

file:

monitor 1
monitor 2
something else

you'll get

monitor 2
equivalent8
  • 13,754
  • 8
  • 81
  • 109
0

I generally skip ruby for the command-line utilities as they tend to be faster.

`grep "monitor" "testfile.txt" > /dev/null`
$?.success #=> true if zero exit status, false otherwise.
spyle
  • 1,960
  • 26
  • 23