You have mistakes and good practices clarified in tinita answer .
I would like to comment on a possible improvement in order to create a list of files with full paths to begin with.
Use map on readdir output to add a directory to each file, then filter
my @files = grep { -f } map { "$dir/$_" } readdir $dh;
or by implementing grep (filtering) in the map block
my @files = map { my $fp = "$dir/$_"; -f $fp ? $fp : () } readdir $dh;
Here, if $fp not -f , we return an empty list that is smoothed out, thus disappearing.
Use glob , which gives you the full path & dagger;
use File::Glob ':bsd_glob'; my @files = grep { -f } glob "$dir/*";
where File :: Glob bsd_glob() overrides glob and nicely handles spaces in names.
Note the difference: * in glob does not select records starting with a period ( . ). If you also want to, change it to glob "$dir/{*,.*}" . See Link File::Glob docs.
Update fix editing (adding) to a question also discussed in comments
After the full path file names have been read in @files
foreach my $file (@files) { my $out_file = 'new_' . $file; open my $writeto '>', $outfile or die "Can't open $outfile: $!"; open my $fh, '<', $file or die "Can't open $file: $!"; while (my $line = <$fn>) {
(Or, if the file names are not the full path, create the full path in the open call.)
When you open it for the first time, the file descriptors are closed, so for the latest files explicitly close is required. You can then declare the variables my ($fh, $writeto); before the loop, use open $fh ... (no my ) and close the file descriptors after it. But then you have these variables outside the foreach scope.
Documentation:
& dagger; glob "$dir/*" has the ability to inject, for very specific directory names. Although this can only happen with rather unusual names, it is safer to use an escape sequence
my @files = grep { -f } glob "\Q$dir\E/*"
Since it also runs through spaces, File::Glob with its bsd_glob() not required.