I want to give the user input (multi-line) and print the multi-line output to a new file named by the user with
I've already tried
say "enter filename";
chomp(my $filename = <STDIN>);
open my $fn, '>', $filename;
say "enter contents";
print "> ";
chomp(my $contents = <STDIN>);
print {$fn} $contents;
close $fn;
but that only works with one line.
say "enter filename";
chomp(my $filename = <STDIN>);
open my $fn, '>', $filename;
say "enter contents";
print "> ";
chomp(my $contents = <STDIN>);
print {$fn} $contents;
close $fn;
but multi-line and creates a new file, not edits an old one
It either doesn't create a file, doesn't edit a file, or just doesn't do anything at all.
To type out a new file or add lines to an existing one
use warnings;
use strict;
use feature 'say';
my $filename = shift; # Better use a module (eg. Getopt::Long)
if (not $filename) {
$filename = get_filename();
}
elsif (-f $filename) {
say "Appending to file \"$filename\".";
}
elsif (-e $filename and not -f $filename) {
say "The \"$filename\" exists but is not a plain file.";
$filename = get_filename();
}
else {
say "Creating file \"$filename\"";
}
open my $fh, '>>', $filename or die "Can't open $filename: $!";
say "\nEnter lines to add. Press Ctrl-D when done.\n";
print "> ";
while (my $line = <STDIN>) {
print $fh $line;
print "> ";
}
sub get_filename {
print "Enter the name for a file to create/edit: ";
chomp (my $filename = <STDIN>);
while (-e $filename and not -f $filename) {
say "The \"$filename\" exists but is not a plain file.";
print "Enter the name for a file to create/edit: ";
chomp ($filename = <STDIN>);
}
usage() if not $filename;
return $filename;
}
sub usage {
say STDERR "Usage: $0 [filename]";
exit;
}
Note that the actual writing of user's input can be written simply as
print $fh $_ while <STDIN>;
All the rest is management of user input, checking etc. That can be organized in many ways, depending on details of possible uses. The above is a simple take, please change and tweak as suitable.
Here is an example that lets you exit the loop by pressing CTRL-C:
use feature qw(say);
use strict;
use warnings;
STDOUT->autoflush(1);
say "enter filename";
chomp(my $filename = <STDIN>);
open my $fh, '>', $filename or die "Could not open file '$filename': $!";
{
local $SIG{INT} = sub { die "SIGINT" };
say "enter contents (CTRL-C to exit)";
eval {
while (1) {
print "> ";
my $contents = <STDIN>; # do not need chomp here
print $fh $contents;
}
};
close $fh;
say "\nDone. Saved file '$filename'";
}
Related
I am currently trying to take user input in for a file name and then search for that file. The program has to terminate gracefully if it isn't found and then continue if it is. For some reason from the research I found, the "-e" function isn't working for me. I am on a mac if that makes a difference although I have the shabang.
#!/usr/bin/perl
use strict;
print "Enter the name of a file: ";
my $userInput = <STDIN>;
my $fileName = '/' . $userInput;
if(-e $fileName) {
print "File exist.\n";
die();
} else {
print "File doesnt exist.\n";
die();
}
Never ends up finding the file if it is named right or not.
The problem is that you are also getting the newline as part of the filename when you hit enter key. You can notice that if you print $filename
You can get rid of it by using chomp function after getting the input:
chomp($userInput);
Also, I'm not sure if you actually want to check for the file in the root directory or in current directory. If it is in the current maybe you missed a dot before the slash:
'./' . $userInput;
With this two changes your code should look like this:
#!/usr/bin/perl
use strict;
print "Enter the name of a file: ";
my $userInput = <STDIN>;
chomp($userInput);
my $fileName = './' . $userInput;
if(-e $fileName) {
print "File '$fileName' exist.\n";
die();
} else {
print "File '$fileName' doesnt exist.\n";
die();
}
i ve tried to write a simple function that takes two arguments, input directory and pattern, and returns an array with the matched files that contain the pattern(s) in their name.
my $dir = "/drives/D/Desktop/perlscripts";
sub getfiles
{
my ($dirName, #patterns) = #_;
opendir(my $dir, $dirName) or die "Can't open '$dirName': $!";
my #return;
for my $file (readdir($dir))
{
for my $pattern (#patterns)
{
if ($file =~ /$pattern/)
{
print "$file \n";
push #return, $file;
# This avoids having the file in the result twice
last;
}
}
}
return #return;
}
my #plscripts = getfiles($dir, "pl");
print "#plscripts \n";
I get nothing in STDOUT with the code as is, after some tests, i think the problem is with the.
Edited with changes : As you can see i commented out #last; but yes it matches every file name on every iteration then. So i end up with duplicate entries in the array.
The quick fix could be add a label for your first loop and then change the "last" to "next FILE":
my $dir = "/drives/D/Desktop/perlscripts";
sub getfiles
{
my ($dirName, #patterns) = #_;
opendir(my $dir, $dirName) or die "Can't open '$dirName': $!";
my #return;
FILE:
for my $file (readdir($dir))
{
for my $pattern (#patterns)
{
if ($file =~ /$pattern/)
{
print "$file \n";
push #return, $file;
# This avoids having the file in the result twice
# last
next FILE;
}
}
}
return #return;
}
my #plscripts = getfiles($dir, "pl");
print "#plscripts \n";
The purpose of the script is to process all words from a file and output ALL words that occur the most. So if there are 3 words that each occur 10 times, the program should output all the words.
The script now runs, thanks to some tips I have gotten here. However, it does not handle large text files (i.e. the New Testament). I'm not sure if that is a fault of mine or just a limitation of the code. I am sure there are several other problems with the program, so any help would be greatly appreciated.
#!/usr/bin/perl -w
require 5.10.0;
print "Your file: " . $ARGV[0] . "\n";
#Make sure there is only one argument
if ($#ARGV == 0){
#Make sure the argument is actually a file
if (-f $ARGV[0]){
%wordHash = (); #New hash to match words with word counts
$file=$ARGV[0]; #Stores value of argument
open(FILE, $file) or die "File not opened correctly.";
#Process through each line of the file
while (<FILE>){
chomp;
#Delimits on any non-alphanumeric
#words=split(/[^a-zA-Z0-9]/,$_);
$wordSize = #words;
#Put all words to lowercase, removes case sensitivty
for($x=0; $x<$wordSize; $x++){
$words[$x]=lc($words[$x]);
}
#Puts each occurence of word into hash
foreach $word(#words){
$wordHash{$word}++;
}
}
close FILE;
#$wordHash{$b} <=> $wordHash{$a};
$wordList="";
$max=0;
while (($key, $value) = each(%wordHash)){
if($value>$max){
$max=$value;
}
}
while (($key, $value) = each(%wordHash)){
if($value==$max && $key ne "s"){
$wordList.=" " . $key;
}
}
#Print solution
print "The following words occur the most (" . $max . " times): " . $wordList . "\n";
}
else {
print "Error. Your argument is not a file.\n";
}
}
else {
print "Error. Use exactly one argument.\n";
}
Your problem lies in the two missing lines at the top of your script:
use strict;
use warnings;
If they had been there, they would have reported lots of lines like this:
Argument "make" isn't numeric in array element at ...
Which comes from this line:
$list[$_] = $wordHash{$_} for keys %wordHash;
Array elements can only be numbers, and since your keys are words, that won't work. What happens here is that any random string is coerced into a number, and for any string that does not begin with a number, that will be 0.
Your code works fine reading the data in, although I would write it differently. It is only after that that your code becomes unwieldy.
As near as I can tell, you are trying to print out the most occurring words, in which case you should consider the following code:
use strict;
use warnings;
my %wordHash;
#Make sure there is only one argument
die "Only one argument allowed." unless #ARGV == 1;
while (<>) { # Use the diamond operator to implicitly open ARGV files
chomp;
my #words = grep $_, # disallow empty strings
map lc, # make everything lower case
split /[^a-zA-Z0-9]/; # your original split
foreach my $word (#words) {
$wordHash{$word}++;
}
}
for my $word (sort { $wordHash{$b} <=> $wordHash{$a} } keys %wordHash) {
printf "%-6s %s\n", $wordHash{$word}, $word;
}
As you'll note, you can sort based on hash values.
Here is an entirely different way of writing it (I could have also said "Perl is not C"):
#!/usr/bin/env perl
use 5.010;
use strict; use warnings;
use autodie;
use List::Util qw(max);
my ($input_file) = #ARGV;
die "Need an input file\n" unless defined $input_file;
say "Input file = '$input_file'";
open my $input, '<', $input_file;
my %words;
while (my $line = <$input>) {
chomp $line;
my #tokens = map lc, grep length, split /[^A-Za-z0-9]+/, $line;
$words{ $_ } += 1 for #tokens;
}
close $input;
my $max = max values %words;
my #argmax = sort grep { $words{$_} == $max } keys %words;
for my $word (#argmax) {
printf "%s: %d\n", $word, $max;
}
why not just get the keys from the hash sorted by their value and extract the first X?
this should provide an example: http://www.devdaily.com/perl/edu/qanda/plqa00016
I have read the directory with the files into an array. Now the problem is I would like to open the files and display their contents with a line in between them.
For example, I would like to open file1.txt and file2.txt, and display their contents like this:
Hello nice to meet you -- file 1
How are you? -- file 2
The code is :
sub openFile{
opendir(fol, "folder/details");
my #files= readdir(fol);
my #FilesSorted = sort(#files);
foreach my $EachFile (#FilesSorted) {
print $EachFile . "\n";
}
}
If you just want to display all the lines in the files (as your code seems to be trying to do), without any indication of which line(s) came from which file, there's a trick involving the pre-defined variable #ARGV:
sub openFile {
opendir(fol, "folder/details");
#ARGV = sort(readdir(fol));
close fol;
while (<>) {
print "$_\n";
}
}
If you need to print the file names, you'll have to open each file explicitly:
sub openFile {
opendir(fol, "folder/details");
my #files = sort(readdir(fol));
close fol;
while ($file = shift #files) {
open(FILE, $file);
while (<FILE>) {
print "$_\n";
}
close FILE;
}
}
Another example, with some error checking, and skipping sub dirs:
sub print_all_files {
my $dir = shift;
opendir(my $dh, $dir) || die "Can't read [$dir]: $!";
while(defined(my $file = readdir $dh)) {
next unless -f "$dir/$file"; # Ignore subdirs and . and ..
open(my $fh, "<", "$dir/$file") || die "Can't read [$dir/$file]: $!";
print while readline($fh);
print "\n"; # add an extra line
}
}
print_all_files("folder/details");
Try
sub openFile{
opendir(fol, "folder/details");
my #files= readdir(fol);
my #FilesSorted = sort(#files);
foreach my $EachFile (#FilesSorted) {
if($EachFile ne "." && $EachFile ne ".." && !(-d $EachFile)) { #important to skip the directories
open IT $EachFile || die "unable to read ".$EachFile."\n"; # open file
while($line = <IT>) { # print content in file
print "$line\n";
}
close(IT); # close file
print "-->$EachFile\n"; # print file name
}
}
Can this be a stand-alone program?
#! /usr/bin/env perl
use strict;
use warnings;
die "$0: no arguments allowed\n" if #ARGV;
my $dir = "folder/details";
opendir my $dh, $dir or die "$0: opendir $dir: $!";
while (defined(my $file = readdir $dh)) {
my $path = $dir . "/" . $file;
push #ARGV, $path if -f $path;
}
#ARGV = sort #ARGV;
while (<>) {
print;
}
continue {
print "\n" if eof && #ARGV;
}
Notes
The defined check on each value returned from readdir is necessary to handle a file whose name is a false value in Perl, e.g., 0.
Your intent is to write the contents of the files in folder/details, so it's also necessary to filter for plain files with the -f test.
Perl's built-in processing in while (<>) { ... } shifts arguments off the front of #ARGV. The eof—without parentheses!—test in the continue clause detects the end of each file and prints newlines as separators between files, not terminators.
You can simplify the directory reading code by using a module like File::Util. It, or some other module like it, will provide several conveniences: error checking; filtering out unwanted directory contents (like the . and .. subdirs); selecting contents by type (for example, just files or dirs); and attaching the root path to the contents.
use strict;
use warnings;
use File::Util qw();
my $dir = 'folder/details';
my $file_util = File::Util->new;
my #files = $file_util->list_dir($dir, qw(--with-paths --no-fsdots --files-only));
for my $f (#files){
local #ARGV = ($f);
print while <>;
print "\n";
}
If you prefer to avoid using other modules, you can get the file names like this:
opendir(my $dh, $dir) or die $!;
my #files = grep -f, map("$dir/$_", readdir $dh);
Following up Greg Bacon's answer (re: "Can this be a stand-alone program?"), if you still want this in an openFile subroutine, you could use the same loop there:
sub openFile{
opendir(fol, "folder/details");
my #files= readdir(fol);
my #FilesSorted = sort(#files);
local #ARGV = #FilesSorted;
while (<>) {
print;
}
continue {
print "\n" if eof && #ARGV;
}
}
Note use of local #ARGV (as in FMc's answer): this preserves any global argument list from outside the subroutine and restores that value on exit.
#!/usr/bin/perl
use strict;
use warnings;
sub paragraph
{
open my $file, "<", "dict.txt" or die "$!";
my #words = <$file>;
close $file;
print "Number of lines:";
my $lines = <>;
print "Max words per line:";
my $range = <>;
for(my $i = 0; $i<$lines; $i++){
my $wordcount = int(rand($range));
for(my $s = 0; $s<=$wordcount; $s++){
my $range2 = scalar(#words);
my $word = int(rand($range2));
print $words[$word]." ";
if($s==$wordcount){
print "\n";}
}
}
}
paragraph;
I'm trying to learn programming, so I just wrote this simple script.
When running this code, I am getting use of uninitialized value errors... I can't figure out why, but I sure I am just overlooking something.
These two lines open the dict.txt file for writing and then try to read from it.
open FILE, ">dict.txt" or die $!;
my #words = <FILE>;
Since you can't read from a write-only file, it fails. If the file was writable, then it is empty now - sorry about your nice word list. Suggestion:
open my $file, "<", "dict.txt" or die "$!";
my #words = <$file>;
close $file;
Also, please learn to indent your braces in an orthodox fashion, such as:
sub go
{
print "Number of lines:";
my $lines = <>;
print "Max words per line:";
my $range = <>;
for (my $i = 0; $i<$lines; $i++){
my $wordcount = int(rand($range));
for (my $s = 0; $s<$wordcount; $s++){
my $range2 = 23496;
my $word = int(rand($range2));
my $chosen = #words[$word];
print "$chosen ";
if ($s=$wordcount){
print "\n";
}
}
}
}
Also leave a space between 'if' or 'for' and the open parenthesis.
Your assignment if ($s = $wordcount) probably isn't what you intended; however, the condition if ($s == $wordcount) will always be false since it is in the scope of a loop with the condition $s < $wordcount. You need to rethink that part of your logic.
On average, you should choose a better name for your function than go. Also, it is probably better to invoke it as go();.
When I test compile your script, Perl warns about:
Scalar value #words[$word] better written as $words[$word] at xx.pl line 19.
You should fix such errors before posting.
You have:
my $range2 = 23496;
my $word = int(rand($range2));
Unless you have more than 23,496 words in your dictionary, you will likely be accessing an uninitialized word. You should probably use:
my $range2 = scalar(#words);
That then just leaves you with some logic problems to resolve.
Given 'dict.txt' containing:
word1
word2
word3
word4
nibelung
abyssinia
tirade
pearl
And 'xx.pl' containing:
#!/usr/bin/env perl
use strict;
use warnings;
open my $file, "<", "dict.txt" or die $!;
my #words = <$file>;
close $file;
sub go
{
print "Number of lines: ";
my $lines = <>;
print "Max words per line: ";
my $range = <>;
my $range2 = scalar(#words);
for (1..$lines)
{
for (1..$range)
{
my $index = int(rand($range2));
my $chosen = $words[$index];
chomp $chosen;
print "$chosen ";
}
print "\n";
}
}
go();
When I run it, I get:
$ perl xx.pl
Number of lines: 3
Max words per line: 4
word4 word3 word4 nibelung
abyssinia pearl word1 tirade
word3 word1 word3 word2
$
Some more bugs:
if($s=$wordcount){
You need == here.