Ruby - Can't write to file after checking if empty - ruby

The program should write "File Empty" if the file is empty; otherwise, it should write "File Full". Here's what I have so far:
fname = "fileTest.txt"
somefile = File.open(fname, "w")
if File.readlines(somefile).grep(/monitor/).size == 0
somefile.write("File Empty")
else
somefile.write("File Full.")
end
somefile.close
When I run this the first time, fileText.txt is empty, so the program writes "File Empty". When I run it a second time, the program should write "File Full", but the file still reads "File Empty".
The if statement should be checking if the file is empty, but I don't it is working correctly. What am I doing wrong?
EDIT - Problem Solved:
fname = "fileTest.txt"
somefile = File.open(fname, "a")
if File.zero?(somefile)
somefile.write("File Empty")
else
somefile.write("File Full.")
end
somefile.close

The "w" option in File.open(fname,"w") truncates the file if it exists or creates a new file if it does not exists- it will always be empty opened in this mode . See here for the options.
File.readlines does not need a file object, a string ("path/to/filename.txt"), or in this case fname will do.

Your code doesn't really match your question.
You say:
The program should write "File Empty" if the file is empty; otherwise,
it should write "File Full"
But you code says this:
if File.readlines(somefile).grep(/monitor/).size == 0
somefile.write("File Empty")
else
somefile.write("File Full.")
end
You are grepping for an RE that matches monitor and, if you don't find it, you're code says to write "File Empty".
"File Empty" does not contain "monitor", so when the program is run a second time, it once again reports "file empty"
If you are actually trying to solve the problem I quoted, then you can use File.zero?("filename") as a quick and easy solution.

Related

How to warn before opening certain filetypes in vim?

It is easy to accidentally open a large binary or data file with vim when relying on command line autocomplete.
Is it possible to add an interactive warning when opening certain file types in vim?
For example, I'd like to add a warning when opening files without an extension:
> vim someBinary
Edit someBinary? [y/N]
Or maybe:
> vim someBinary
# vim buffer opens and displays warning about filetype,
# giving user a chance to quit before loading the file
This could be applied to a range of extensions, such as .pdf, .so, .o, .a, no extension, etc.
There is a related question on preventing vim from opening binary files, but it is primarily about modifying autocomplete to prevent accidentally opening the files in the first place.
Below is the solution I came up with, using vim autocommands with the BufReadCmd event. It's a lot of vimscript, but it's pretty robust. It issues a warning if the file being opened is a non-ascii file or has a blacklisted extension (.csv and .tsv for this example):
augroup bigfiles
" Clear the bigfiles group in case defined elsewhere
autocmd!
" Set autocommand to run before reading buffer
autocmd BufReadCmd * silent call PromptFileEdit()
augroup end
" Prompt user input if editing an existing file before reading
function! PromptFileEdit()
" Current file
let file = expand("%")
" Whether or not we should continue to open the file
let continue = 1
" Skip if file has an extension or is not readable
if filereadable(file) && (IsNonAsciiFile(file) || IsBlacklistedFile())
" Get response from user
let response = input('Are you sure you want to open "' . file . '"? [y/n]')
" Bail if response is a 'n' or contains a 'q'
if response ==? "n" || response =~ "q"
let continue = 0
if (winnr("$") == 1)
" Quit if it was the only buffer open
quit
else
" Close buffer if other buffers open
bdelete
endif
endif
endif
if continue == 1
" Edit the file
execute "e" file
" Run the remaining autocommands for the file
execute "doautocmd BufReadPost" file
endif
endfunction
" Return 1 if file is a non-ascii file, otherwise 0
function! IsNonAsciiFile(file)
let ret = 1
let fileResult = system('file ' . a:file)
" Check if file contains ascii or is empty
if fileResult =~ "ASCII" || fileResult =~ "empty" || fileResult =~ "UTF"
let ret = 0
endif
return ret
endfunction
" Return 1 if file is blacklisted, otherwise 0
function! IsBlacklistedFile()
let ret = 0
let extension = expand('%:e')
" List contains ASCII files that we don't want to open by accident
let blacklistExtensions = ['csv', 'tsv']
" Check if we even have an extension
if strlen(extension) == 0
let ret = 0
" Check if our extension is in the blacklisted extensions
elseif index(blacklistExtensions, extension) >= 0
let ret = 1
endif
return ret
endfunction
To read with syntax highlighting enabled, see this gist.
Maybe not super elegant, but I enjoyed learning some vimscript along the way.
I am not too experienced with vimscript so I'm sure there is room for improvements -- suggestions and alternative solutions welcome.
Note: This is not expected to work on Windows systems outside of WSL or Cygwin, due to calling file.
You can use a function like this
promptvim() {
grep -Fq "." <<< "$1" || read -p "Edit $1? [y/N]" && [[ $REPLY == "y" ]] || return
/usr/bin/vim "$1"
}
You can choose a different function name. When you use vim other scripts might fail.
EDIT:
When you like this construction (wrapper, not vim settings), you can make the function better with more tests:
promptvim() {
if [ $# -eq 0 ]; then
echo "arguments are missing"
return
fi
local maybe_dot=$(grep -Fq "." <<< "$1")
for file in $*; do
# skip tests when creating a new file
if [ -f "${file}" ]; then
maybe_dot=$(grep -F "." <<< "${file}")
if (( ${#maybe_dot} == 0 )); then
read -p "Edit ${file}? [y/N]"
# check default response variable REPLY
# Convert to lowercase and check other ways of confirming too
if [[ ! "${REPLY,,}" =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then
continue
fi
fi
fi
echo /usr/bin/vim "${file}"
done
}
This still not covers all special cases. You might want to add support of the vim parameters on the commandline, check for an interactive session and think what you want with here documents.

How can I edit a .conf file easily?

So I read the easiest way to use .conf files for bash scripts is to use source to load such files. Now, what if I want to edit this file ?
Some code I found does a really good job :
function set_config(){
sed -i "s/^\($1\s*=\s*\).*\$/\1$2/" $conf_file
}
But, if the variable is not yet defined, it doesn't define it, nor does it check if the parameters are passed well, isn't secure, doesn't handle default values etc...
Does reliable tools/code already exists to edit .conf file which contain key="value" pairs ? For instance, I would like to be able to do things like this :
$conf_file="my_script.conf"
conf_load $conf_file #should create the file if it doesn't exist !
read=$(conf_get_value "data" "default_value") #should read the value with key "data", defaulting to "default_value"
if [[ $? = 0 ]] #we should be able to know if the read was successful
then
echo "Successfully read value for field \"data\" : $read"
else
echo "Default value for field \"data\" : $read"
fi
conf_set "something_new" "a great value!" #should add the key "something_new" as it doesn't exist
conf_set "data" "new_value" #should edit the value with key "data"
if [[ $? = 0 ]]
then
echo "Edit successful !"
else #something went wrong :-/
echo "Edit failed !"
fi
before running this code, the conf file would contain
data="some_value"
and after it would be
data="new_value"
something_new="a great value!"
and the code should output
Successfully read value for field "data" : some_value
Edit successful !
I am using bash version 4.3.30 .
Thanks for your help.
I'd to that with awk since it's rather good at tokenizing:
# overwrite config's entries for KEY with VALUE or else appends the definition
# Usage: set_config KEY VALUE
set_config() {
[ -n "$1" ] && awk -F= -v key="$1" -v new="$1=\"$2\"" '
$1 == key { $0 = new; key_found = 1; }
{ print }
END { if (!key_found) { print new; }
' "$conf_file" > "$conf_file.new" \
&& cat "$conf_file.new" > "$conf_file" && rm "$conf_file.new"
}
If run without arguments, set_config() will do nothing and return false. If run with only one argument, it will create an empty value (outputting KEY="").
The awk command parses the .conf file line by line, looking for each definition of the given key and altering it to the new value. All lines are then printed (with or without modification), preserving the original order. If the key hasn't yet been found by the end of the file, this appends the new definition.
Because you can't pipe a file atop itself, this gets saved with a ".new" extension and then copied atop the original in a manner that preserves permissions. The ".new" copy is then removed. I used && to ensure that these never happen if an error occurred earlier in the function.
Also note that the type of ".conf file" you're referring to (the type you source with a POSIX shell) will never have spaces around its equals signs, so the \s* parts of your sed command aren't needed.

File.exist? always returns false even when file does exist

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.

Ruby: No such file or directory

Why do i get this error 'ruby: No such file or directory -- Readingfile.rb (LoadError)' when i run my ruby program to read files?
My code:
filename = ARGV.first
txt = open(filename)
puts "Heres your file#{filename}:"
print txt.read
print "TYpe the filename again"
file_again = $stdin.gets.chomp
txt_again = open(file_again)
print txt_again.read
'ruby: No such file or directory -- Readingfile.rb (LoadError)'
Without a stack trace, I can only infer that this error originates from the line txt = open(filename). What's most likely is that the filename (first arg passed into $ ruby file.rb) either does not exist or was unspecified (and therefore nil).
In order to ensure that your program is resilient to different kinds of input, you should check and handle cases where no valid filename is passed. This can be done with File#exist?:
puts "Missing filename" and exit! unless filename = ARGV.first

Multiple files as command line arguments?

I wrote a script to read IP addresses from a file and print the amount in the file. I wasn't fully satisfied so I attempted to modify it to allow reading multiple files, and I would specify the files via cmd arguments. The problem that I'm having is that it seems to read multiple files as one argument.
def host_count(*files)
begin
files.each do
files = files.join(' ')
read = IO.read(files)
reg = read.scan(/(?:\d{1,3}\.){3}\d{1,3}/).size
puts "There are " << reg.to_s << " IP addresses in #{files}."
end
rescue Errno::ENOENT
puts "File #{files} does not exist!"
rescue TypeError
puts "Usage: #{$0} [file]"
puts "Example: #{$0} /home/user/ipfile.txt"
end
end
host_count(ARGV)
Running this script with multiple files gives me this error:
File file1 file2 does not exist!
They aren't separated by commas or anything, so it's not reading my arguments as: ["file1","file2"], which was my original problem. What am I not understanding?
You wrote
files.each do
files = files.join(' ')
Why would you do that?
you're changing the array..
the "files" array is already an array, you don't have to join it with strings.
edit1:
to get the specific file for each run, you should write:
files.each do |file|
puts file # will print "file 1", and in the next iteration will print "file 2".
end

Resources