Create a tempfile without opening it in Ruby - ruby

Is there a way to create a tempfile, without having it opened? I have to run an executable, redirect it's output to a file, and then read & parse that. Everything created by tempfile is already opened, and this triggers an error , because the file is locked.

You can also use Dir::Tmpname
Dir::Tmpname.create('your_application_prefix') { |path| puts path }
path will contain unique path
See https://github.com/ruby/ruby/blob/ruby_1_9_3/lib/tmpdir.rb#L116

I didn't get an error:
Andrew-Grimms-MacBook-Pro:~ agrimm$ irb
>> require "tempfile"
=> true
>> tempfile = Tempfile.new("temporary_file.txt", "/tmp")
=> #<File:/tmp/temporary_file.txt20110622-648-pkynjw-0>
>> tempfile.close
=> nil
>> system("echo foo > #{tempfile.path}")
=> true
>> system("cat #{tempfile.path}")
foo
=> true
>> tempfile.path
=> "/tmp/temporary_file.txt20110622-648-pkynjw-0"
>> exit
Andrew-Grimms-MacBook-Pro:~ agrimm$ cat /tmp/temporary_file.txt20110622-648-pkynjw-0
foo
Then again, the temporary file doesn't seem awfully temporary.
Does the error happen with all programs, or just a specific program? Also, can you post the code that causes the problem, and what error backtrace you get?

Is using FileUtils.touch acceptable solution? You can touch a file and delete it once you are done with whatever you want.

You may want to use pipes.
If the executable is started from your ruby program, consider using IO.popen.
If they're different processes, you can try named pipes.

The #timfjord's answer works. But if you don't need a block try:
Dir::Tmpname.create(['prefix-', '.ext']) {}
# => "/tmp/prefix-20190827-1-87n9iu.ext"

Related

How might I pass text data from the ruby console into my clipboard without saving to a file?

I'm trying to pass the array contained in a global variable I've created into my clipboard on my mac.
It is very long so I don't want to highlight, copy & paste on my console.
I want to use embedded unix code, specificially the pbcopy function for the mac laptop console that allows me to pass text into my computers clipboard, ready to paste.
Were I to do this with a file-save, I'd do something like this (in ruby):
stringdata = <<blah blah blah process, lets say it failed and the progress data is stored in this variable so we can skip forward to where the script screwed up in a process when we start up and handle the error instance(s)>>
File.open("temp.txt"){|f| f.write(stringdata)}
`cat temp.txt | pbcopy`
But could I possibly do this without creating a temporary file?
I'm sure this is possible. All things in text are possible. Thanks in advance for the solution
You can just echo it instead if there are no newline characters in the string; otherwise, use the IO class.
Using echo:
system "echo #{stringdata} | pbcopy"
OR
`echo #{stringdata} | pbcopy`
Ruby will then just rip the text from memory, inject it into the shell command which opens a pipe between the echo and pbcopy processes.
Using the IO class:
If you want to do it the Ruby way, we simply create a pipe with pbcopy using the IO class. This creates a shared files between the processes which we write to, and pbcopy will read from.
IO.popen("pbcopy", "w") { |pipe| pipe.puts "Hello world!" }
Here's a simple one-line method you can paste into your IRB console:
def pbcopy(arg); IO.popen('pbcopy', 'w') { |io| io.puts arg }; end
Once it's defined you can simply do
pbcopy stringdata
or copy the result of the last command with
pbcopy _
Of course, you can also put the method definition in an initializer or something, such as .irbrc or .pryrc if you use pry. Here's a prettier and slightly more intelligent version:
def pbcopy(arg)
out = arg.is_a?(String) ? arg : arg.inspect
IO.popen('pbcopy', 'w') { |io| io.puts out }
puts out
true
end
You can use my clipboard gem for a Ruby-API to the system clipboard (which is also platform independet, on macOS it will use the same pbcopy utility under the hood), so that you can use it from IRB:
require 'clipboard'
Clipboard.copy(stringdata);p
Usually, the copy method returns the string which was copied. This the reason for the ;p bit: It is a trick to return nil so that the console would not display the actual string data.

Ruby Dump all cron jobs to text file

I want a ruby script that will dump all the existing cron jobs to a text file using "crontab -l" or anything else that will achieve the same objective. Also the text file should be possible to use with crontab txtfile to create the cron jobs again.
Below is the code I already wrote:
def dump_pre_cron_jobs(file_path)
begin
cron_list = %x[crontab -l]
if(cron_list.size > 0)
cron_list.each do |crl|
mymethod_that_writes_tofile(file_path, crl) unless crl.chomp.include?("myfilter")
end
end
rescue Exception => e
raise(e.message)
end
end
Why does this need to be a Ruby script?
As you say, you can dump the crontab to a file with crontab -l > crontab.txt.
To read them back in again, simply use crontab crontab.txt, or cat crontab.txt | crontab -
I agree with #Vortura that you do not need to create a Ruby script to do this.
If you really want to, here is a probable way:
File.open('crontab.txt', 'w') do |crontab|
crontab << `crontab -l`
end
NOTE: Running this as root, or using sudo should capture all the cron jobs on a system, not just a single users' jobs. Run it as yourself or as that user and it might capture just those jobs. I haven't test that aspect of it.
Trying to run crontab -l to capture crontab files for all the users and packages seems the indirect way to do the task and could have the hassle of dealing with password requests hanging your code. I'd write code to comb through the directories that store them, rather than mess with prompts. Run the code using sudo and you shouldn't have any problems accessing the files.
Take a look at the discussion at: http://www.linuxquestions.org/questions/linux-newbie-8/etc-crontab-vs-etc-cron-d-vs-var-spool-cron-crontabs-853881/ for information on where the actual cron tab files are stored on disk.
Also https://superuser.com/questions/389116/how-to-recover-crontab-jobs-from-filesystem/389137 has similar information.
Mac OS varies a little from Linux in where Apple puts the cron files. Run man cron at the command-line for the definitive details on either OS.
Here's slightly-tested code for how I'd back up the files. How you restore them is for you to figure out, but it shouldn't be hard to figure out:
require 'fileutils'
BACKUP_PATH = '/path/to/some/safe/storage/directory'
CRONTAB_DIRS = %w[
/usr/lib/cron/tabs
/var/spool/cron
/etc/anacrontab
/etc/cron.d
]
CRONTAB_FILES = %w[
/etc/cron_list
]
def dump_pre_cron_jobs(file_path)
full_backup_path = File.join(
BACKUP_PATH,
File.dirname(file_path)
)
FileUtils.mkdir_p(full_backup_path) unless Dir.exist?(full_backup_path)
File.write(
File.join(
full_backup_path,
file_path
),
File.read(file_path)
)
rescue Exception => e
STDERR.puts e.message
end
CRONTAB_DIRS.each do |ct|
next unless Dir.exist?(ct)
begin
Dir.entries(File.join(ct, '*')).each { |fn| dump_pre_cron_jobs(fn) }
rescue Errno::EACCES => e
STDERR.puts e.message
end
end
CRONTAB_FILES.each do |fn|
dump_pre_cron_jobs(fn)
end
You'll need to run this as root via sudo to access the directories and files as they're usually locked down from unauthorized prying eyes.
The code creates a repository of crontabs, in BACKUP_PATH, based on their original file paths. No changes are made to the file contents so they can be restored as-is by copying them back via cp or writing code to reverse this process.

How to create a file in Ruby

I'm trying to create a new file and things don't seem to be working as I expect them too. Here's what I've tried:
File.new "out.txt"
File.open "out.txt"
File.new "out.txt","w"
File.open "out.txt","w"
According to everything I've read online all of those should work but every single one of them gives me this:
ERRNO::ENOENT: No such file or directory - out.txt
This happens from IRB as well as a Ruby script. What am I missing?
Use:
File.open("out.txt", [your-option-string]) {|f| f.write("write your stuff here") }
where your options are:
r - Read only. The file must exist.
w - Create an empty file for writing.
a - Append to a file.The file is created if it does not exist.
r+ - Open a file for update both reading and writing. The file must exist.
w+ - Create an empty file for both reading and writing.
a+ - Open a file for reading and appending. The file is created if it does not exist.
In your case, 'w' is preferable.
OR you could have:
out_file = File.new("out.txt", "w")
#...
out_file.puts("write your stuff here")
#...
out_file.close
Try
File.open("out.txt", "w") do |f|
f.write(data_you_want_to_write)
end
without using the
File.new "out.txt"
Try using "w+" as the write mode instead of just "w":
File.open("out.txt", "w+") { |file| file.write("boo!") }
OK, now I feel stupid. The first two definitely do not work but the second two do. Not sure how I convinced my self that I had tried them. Sorry for wasting everyone's time.
In case this helps anyone else, this can occur when you are trying to make a new file in a directory that does not exist.
If the objective is just to create a file, the most direct way I see is:
FileUtils.touch "foobar.txt"
The directory doesn't exist. Make sure it exists as open won't create those dirs for you.
I ran into this myself a while back.
File.new and File.open default to read mode ('r') as a safety mechanism, to avoid possibly overwriting a file. We have to explicitly tell Ruby to use write mode ('w' is the most common way) if we're going to output to the file.
If the text to be output is a string, rather than write:
File.open('foo.txt', 'w') { |fo| fo.puts "bar" }
or worse:
fo = File.open('foo.txt', 'w')
fo.puts "bar"
fo.close
Use the more succinct write:
File.write('foo.txt', 'bar')
write has modes allowed so we can use 'w', 'a', 'r+' if necessary.
open with a block is useful if you have to compute the output in an iterative loop and want to leave the file open as you do so. write is useful if you are going to output the content in one blast then close the file.
See the documentation for more information.
data = 'data you want inside the file'.
You can use File.write('name of file here', data)
You can also use constants instead of strings to specify the mode you want. The benefit is if you make a typo in a constant name, your program will raise an runtime exception.
The constants are File::RDONLY or File::WRONLY or File::CREAT. You can also combine them if you like.
Full description of file open modes on ruby-doc.org

How to read an open file in Ruby

I want to be able to read a currently open file. The test.rb is sending its output to test.log which I want to be able to read and ultimately send via email.
I am running this using cron:
*/5 * * * /tmp/test.rb > /tmp/log/test.log 2>&1
I have something like this in test.rb:
#!/usr/bin/ruby
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
When I run this code, it only gives me this output:
Start
End
I would expect the output to be something like this:
Start
Start (from the reading of the test.log since it should have the word start already)
End
Ok, you're trying to do several things at once, and I suspect you didn't systematically test before moving from one step to the next.
First we're going to clean up your code:
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
can be replaced with:
puts "Start"
puts File.read("./test.log")
puts "End"
It's plain and simple; There's no need for a method or anything complicated... yet.
Note that for ease of testing I'm working with a file in the current directory. To put some content in it I'll simply do:
echo "foo" > ./test.log
Running the test code gives me...
Greg:Desktop greg$ ruby test.rb
Start
foo
End
so I know the code is reading and printing correctly.
Now we can test what would go into the crontab, before we deal with its madness:
Greg:Desktop greg$ ruby test.rb > ./test.log
Greg:Desktop greg$
Hmm. No output. Something is broken with that. We knew there was content in the file previously, so what happened?
Greg:Desktop greg$ cat ./test.log
Start
End
Cat'ing the file shows it has the "Start" and "End" output of the code, but the part that should have been read and output is now missing.
What happening is that the shell truncated "test.log" just before it passed control to Ruby, which then opened and executed the code, which opened the now empty file to print it. In other words, you're asking the shell to truncate (empty) it just before you read it.
The fix is to read from a different file than you're going to write to, if you're trying to do something with the contents of it. If you're not trying to do something with its contents then there's no point in reading it with Ruby just to write it to a different file: We have cp and/or mv to do those things for us witout Ruby being involved. So, this makes more sense if we're going to do something with the contents:
ruby test.rb > ./test.log.out
I'll reset the file contents using echo "foo" > ./test.log, and cat'ing it showed 'foo', so I'm ready to try the redirection test again:
Greg:Desktop greg$ ruby test.rb > ./test.log.out
Greg:Desktop greg$ cat test.log.out
Start
foo
End
That time it worked. Trying it again has the same result, so I won't show the results here.
If you're going to email the file you could add that code at this point. Replacing the puts in the puts File.read('./test.log') line with an assignment to a variable will store the file's content:
contents = File.read('./test.log')
Then you can use contents as the body of a email. (And, rather than use Ruby for all of this I'd probably do it using mail or mailx or pipe it directly to sendmail, using the command-line and shell, but that's your call.)
At this point things are in a good position to add the command to crontab, using the same command as used on the command-line. Because it's running in cron, and errors can happen that we'd want to know about, we'd add the 2>&1 redirect to capture STDERR also, just as you did before. Just remember that you can NOT write to the same file you're going to read from or you'll have an empty file to read.
That's enough to get your app working.
class FileLineRead
File.open("file_line_read.txt") do |file|
file.each do |line|
phone_number = line.gsub(/\n/,'')
user = User.find_by_phone_number(line)
user.destroy unless user.nil?
end
end
end
open file
read line
DB Select
DB Update
In the cron job you have already opened and cleared test.log (via redirection) before you have read it in the Ruby script.
Why not do both the read and write in Ruby?
It may be a permissions issue or the file may not exist.
f = File.open("test","r")
puts f.read()
f.close()
The above will read the file test. If the file exists in the current directory
The problem is, as I can see, already solved by Slomojo. I'll only add:
to read and print a text file in Ruby, just:
puts File.read("/tmp/log/test.log")

System call in Ruby

I'm a beginner in ruby and in programming as well and need help with system call for moving a file from source to destination like this:
system(mv "#{#SOURCE_DIR}/#{my_file} #{#DEST_DIR}/#{file}")
Is it possible to do this in Ruby? If so, what is the correct syntax?
system("mv #{#SOURCE_DIR}/#{my_file} #{#DEST_DIR}/#{file})
can be replaced with
system("mv", "#{#SOURCE_DIR}/#{my_file}", "#{#DEST_DIR}/#{file}")
which reduces the chances of a command line injection attack.
Two ways
Recommended way
You can use the functions in the File Utils libary see here to move your files e.g
mv(src, dest, options = {})
Options: force noop verbose
Moves file(s) src to dest. If file and dest exist on the different disk
partition, the file is copied instead.
FileUtils.mv 'badname.rb', 'goodname.rb'
FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
Naughty way
Use the backticks approach (run any string as a command)
result = `mv "#{#SOURCE_DIR}/#{my_file} #{#DEST_DIR}/#{file}"`
Ok, that's just a variation of calling the system command but looks much naughtier!
system("mv #{#SOURCE_DIR}/#{my_file} #{#DEST_DIR}/#{file})
should be the correct call
I recommend you to use Tanaka akira's escape library
Here is example from one my app:
cmd = Escape.shell_command(['python', Rails::Configuration.new.root_path + '/script/grab.py']).to_s
system cmd

Resources