catfish1116 has asked for the wisdom of the Perl Monks concerning the following question:

using 'Learning Perl' and I'm trying to figure out a way to remove multiple files. I created a script that would remove 1 file, (like a rm function), but not more than 1. Below is the code

#! /usr/bin/perl use v5.16.3; use strict; use warnings; #### # Program works like rm function for just one file # 11/20/19 # #### print "Please enter the files to be removed: \n"; chomp(my @removed_files = <STDIN>); foreach (@ARGV) { unlink $_ or warn "Cant unlink '$_': $!, continuing ... \n"; }

TIA the catfish

Replies are listed 'Best First'.
Re: remove files
by hippo (Archbishop) on Nov 20, 2019 at 16:22 UTC

    Are you expecting this program to take the list of files to delete from its arguments or to read them from standard input? It currently seems to confuse the two which is likely where the problem lies.

      remove from standarad input. Thanks !

        In that case, consider looping over @removed_files instead of @ARGV and see if that does the trick.

Re: remove files
by ogrp (Initiate) on Nov 21, 2019 at 18:18 UTC
    Hi, quick snippet of code, that expands foreach so that you can see what is happening.
    my @files = glob("myfile*.txt"); foreach my $file (@files) { print "FILE: $file \n"; unlink($file); }
Re: remove files
by Don Coyote (Hermit) on Nov 21, 2019 at 17:31 UTC

    I created a script that would remove 1 file

    The script you created will remove as many files you supply as arguments to your script.

    my @removed_files = <STDIN> will create an array over the supplied input, split on $/ IFS (Input Field Separator).

    changing the array sigil @ to a scalar sigil $ will take only the next line of input based on the $/ value. my $removed_files = <STDIN>.

    This may not gaurantee that only one file will be passed to unlink, depending on how your code evolves. You should validate the data to ensure you are getting only the number of files you expect.

    update 26 Nov I did not clearly explain why it was your script is not doing what you think it is doing.

    The reason that your script will remove as many files you supply as arguments to your script is that your foreach loop uses the Perl special variable @ARGV, while your filelist is input from the STDIN filehandle. ++hippo

    The distinction is that the arguments in @ARGV are recieved from the command line when you run the script. On some systems the terminal is called a command line, so this may be a source of confusion. The command line here, is the line that you pass to the terminal to invoke the command.

    #command line passed to the terminal > cl_args.pl myfile1.txt myfile2.txt myfile3.txt
    # cl_args.pl use strict; use warnings; @ARGV == 3 and print @ARGV; # @ARGV is an array that can be be assigned to within your script @ARGV = ( 'myfile4.txt' ); @ARGV == 1 and print @ARGV;

    The STDIN filehandle is the input that the process has access to once it has been invoked. Again command and process can be synonyms so take care with these terms. When you access STDIN from within your program using the input operator < >, you are setting the process input to come from the terminal.

    While it is pertinent to validate data before doing anythng with it, command line arguments (from @ARGV), are processed by the shell before entering the process, whereas the program is responsible for validating data recieved from STDIN

    There is some crossover between the two, but this hopefully helps the reader to understand what is going on here a little better.

      I tried the split function, but got an error

      print "Please enter the files to be removed: \n"; my @removed_files = split, <STDIN>; foreach my $file (@removed_files) { print "FILE: $file \n"; unlink ($file); } ~

      Here is the error thrown

      Please enter the files to be removed: Use of uninitialized value $_ in split at ./Chapter13_Exer4a line 14.

      TIA the Catfish

        my @removed_files =  split, <STDIN>;

        You have a comma after the split, so Perl is reading that as split(), <STDIN>, which will try to operate on the special variable $_, which is why you're getting that warning. If you want to read all the lines from STDIN and have each element of the array be one line, it's enough to say:

        my @removed_files = <STDIN>; chomp(@removed_files);

        I added the chomp to remove the newline that each line will have at the end. Also, I might suggest a different variable name for clarity, such as @files_to_remove, and also you should check the return value of unlink to see if it actually removed a file. For example:

        my $removed = unlink($file); print "Removed $removed file(s).\n"; # - or - unlink($file) == 1 or die "Failed to unlink $file: $!";

        You should validate the data to ensure you are getting only the number of files you expect.

        #!/Perl -T $what_i_am_about_to_type_at_prompt = 'file to remove.gah'; print "Please enter the file to be removed: \n"; my @file_I_intend_to_remove = <STDIN>; @file_I_intend_to_remove == 1 or die 'incorrect number of files sup +plied'; my $file_i_am_about_to_unlink = shift @file_I_intend_to_remove; unless( $file_i_am_about_to_unlink eq $what_i_am_about_to_type_at_p +rompt ){ printf "%s\n", $file_i_am_about_to_unlink; die 'for the love of ice-cream, don't do an unlink operation jus +t yet'; } # even if it matches you still need to untaint it. $file_i_am_about_to_unlink =~ m/\A(file\sto\sremove\.gah)\Z/ or die 'taint is a pain in the hyperbola, but I enjoy having a wor +king operating system'; $file_i_am_about_to_unlink = $1; # now i can unlink the file as it is the one i am expecting to unli +nk; printf "unlinking %s\n", $file_i_am_about_to_unlink; # unlink $file_i_am_about_to_unlink;

        The context you supply to the input operator, determines whether it returns the first field, or all fields. The input operator splits on IFS the Input Field Separator, $/. You do not need to call split on the input operator.

        You do need to check what your input is once it is in the array, and before you pass that input to a function. This is data-validation, more commonly known as untainting. For why we do this see perldoc perlsec. To scrub the data, you need to substitute a pattern match back into your scalar.

        perlsec is dense. keep it simple and only use straightforward file names while testing.

        my $filename = 'filename.fxt'; my $scrubbed = s/\A(filename\.fxt)\Z/$1/ or die 'need to read up on regex a bit more';

        Common error is to forget to chomp the string, or to try and use filenames with non-printable characters in them, such as spaces. Review chapter 8 to cover what you will need for this.

        edit removed split, re Re^4: remove files