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

Lo Monks. I have a script to change the time stamps on some files read from a csv. It does not work for any file names containing spaces. I tried subin spaces for "\ " but allas. This script works on all file except those with spaces. This is my script
#!/usr/bin/perl -w open(CSV, "/tmp/doctime.txt"); @title = <CSV>; for ($i = 0; $i < scalar(@title); $i++) # a for loop has three +fields. the first sets a counter ($i=0) { ($file[$i], $date[$i]) = split(",", $title[$i]); # split the line +into its 2 arrays } for ($a = 0; $a < scalar(@file); $a++) { $access=$date[$a]; $filez = $file[$a]; $filez =~ s/\ /\\ /g; $filez =~ s/.\s$//; print "File name # $filez\n"; print "Previous date # $access\n"; utime $access, $access, "$filez" || die print "couldn not set utime fo +r $filez\n"; }
Any ideas appreciated.

Replies are listed 'Best First'.
Re: spaces in filenames
by theguvnor (Chaplain) on Feb 03, 2003 at 01:35 UTC

    Two observations (potential problems):

    1. How about brackets around your utime arguments, to prevent the boolean OR operator from binding your error handling (die) to your third argument? I'd change || to or.
    2. Your second substitution regex squishes the last two characters in the filename if the last character is whitespace. Is this what you wanted?

    Jon

      brackets around the argument you mean like this? and for changing || to or.
      (utime $access, access, "/staff/cyp/share/shared/cr/Server Replacement + Schedule.doc" or die print "couldn not set utime for \n");
      I'll give it a go but I'm not sure thats what you ment. My perl code is poor as I steal bits of my other also badly written scripts that worked ;) I have a little trouble understanding comments at this stage but that helps with the learning so thanks.

        Well, you changed || to or so you're partially there, but I was suggesting brackets around your arguments, not around the whole call to utime(), like so:

        utime($access, $access, "/some/path/Some File.doc") or {...};

        Jon

        Update: I just saw too that you have die print ... and the print is unnecessary.

Re: spaces in filenames
by Coruscate (Sexton) on Feb 03, 2003 at 02:33 UTC

    It seems as though your problem has (hopefully) been solved, so I'll just throw in my two cents. For those for() loops, you should be using $#title and $#files where you currently have scalar(@title) and scalar(@files). '$#' will return the last index of the array, rather than the number of elements contained within the array. As well, your for() loops use the well-known version, coming from many other languages other than Perl. If you are planning on programming Perl more often than not and you want things to look cleaner, you might want to take a look at Perl's neat and clean-cut version of the for() loop. The two loops within your code could be modified to look like the following:

    for my $i (0 .. $#title) { ... } for my $a (0 .. $#file) { ... }

    Makes it simple to read, takes less typing, and has an overall positive effect. As well, tacking on 'use strict' right beneath the shebang line will do wonders for you. It would be worth the time to add it on and get the script working with strict enabled. :)


          C:\>shutdown -s
          >> Could not shut down computer:
          >> Microsoft is logged in remotely.
        

Re: spaces in filenames
by fokat (Deacon) on Feb 03, 2003 at 01:39 UTC

    Update: Thanks to MarkM for pointing out that split() accepts a scalar as its first argument just fine, provided you escape the \'s

    I think the problem lies in your call to split(). You should pass a pattern as its first argument, but instead you're passing a scalar. Later I provide a proper example. This is a test of utime() on my system, just so we know this is not culprit.

    bash-2.05a$ touch "my file"
    bash-2.05a$ ls -l "my file"
    -rw-r--r--  1 lem  staff  0 Feb  2 21:28 my file
    bash-2.05a$ sleep 60; perl -e 'print utime(undef, undef, "my file"), "\n";'
    1
    bash-2.05a$ ls -l "my file"
    -rw-r--r--  1 lem  staff  0 Feb  2 21:29 my file
    

    Also, you should be checking the return value of utime(). When it fails, returns a false value. You could then use the special variable $! to see a (hopefully) meaningful error message. See perlvar for more info on this variable.

    Your code is otherwise fine (I guess) but I would like to offer a shorter and untested alternative...

    open(CSV, "/tmp/doctime.txt") or die "Failed to open CSV: $!\n"; while (my $line = <CSV>) { my @val = split(/,/, $line, 2); unless (utime $val[0], $val[0], $val[1]) { warn "Failed to utime $val[1]: $!\n"; } } close CSV;

    ++ for -w, but you should also use strict.

    Best regards

    -lem, but some call me fokat

      You say:
      You should pass a pattern as its first argument, but instead you're passing a scalar.

      Yes, split does expect a pattern. But no, it does not have to be delimited with forward slashes. In fact, Perl doesn't even require delimiters here. A scalar value in a variable will be interpreted as a pattern also. Either of the following cases works fine...

      my $str = 'this_is_a_group_of_words'; my @ary = split "_", $str; print "[$_]\n" for @ary; my $str = 'this_is_a_group_of_words'; my $pat = '_'; my @ary = split $pat, $str; print "[$_]\n" for @ary;
      perldoc -f split says "The pattern "/PATTERN/" may be replaced with an expression to specify patterns that vary at run-time." but then confuses things by going on with, "(To do runtime compilation only once, use "/$variable/o".)" which makes it look like you still need the slashes. (Which you do if you want to add the '/o'.)

      Camel 3, page 796, is more clear on this point when it says "if you supply a string instead of a regular expression, it will be interpreted as a regular expression anyway."

      ------------------------------------------------------------
      "Perl is a mess and that's good because the
      problem space is also a mess.
      " - Larry Wall

        dvergin said:

        A scalar value in a variable will be interpreted as a pattern also.

        I beg to differ. Take a look at the code below:

        bash-2.05a$ perl -e 'print scalar(split("\W", "one two three")), "\n";'
        1
        bash-2.05a$ perl -e 'print scalar(split("\\W", "one two three")), "\n";'
        3
        bash-2.05a$ perl -e 'print scalar(split(/\W/, "one two three")), "\n";'
        3
        

        As you see, both versions produce a different result. My interpretation of your quote to The Camel (and to the fact that the original code compiles at all), is that split() is using the literal string as exactly that, a literal string that must occur verbatim.

        This might have changed at some time, though. I have vague recollections of using a literal with split(), but that was way back I guess. I am running 5.8.0 here, BTW.

        ++ for dvergin for researching his answer :)

        Update: As pointed out by MarkM, the literal string is indeed interpreted as a regular expression. It's just that \ must be escaped in my example, so my conclusion turned out to be wrong. ++ to MarkM for showing me the light...

        Best regards

        -lem, but some call me fokat

Re: spaces in filenames
by ironpaw (Novice) on Feb 05, 2003 at 02:54 UTC
    I realise some of my perl was not as technically correct as it could have been. With some help from this thread though all is now working. Several issues lead to this 1> utime in windows will only change dates to files you own (even if you have write access). This is the same in unix but given root owns everything root will do them all. Unix does not like spaces, fair enough put "" around the word. This does not work with utime if you use it like this:
    $oldtime = 1043205456; $filename = /share/shared/path/somepath/doc with space.doc; utime ($oldtime, $oldtime, "$filename");
    but it will work like this
    chdir("/share/shared/path/somepath"); $filename = doc with space.doc; utime ($oldtime, $oldtime, "$filename");
    So finally this is the perl for setting a bunch of docs with spaces as root in unix.
    #!/usr/bin/perl -w open(CSV, "/tmp/doctime.txt"); #contains comma seperated path,filename +,olddate @title = <CSV>; # I know this is lame but it works and I understand it for ($i = 0; $i < scalar(@title); $i++) { # some of you dont like this but it works fine ($dironly[$i], $filename[$i], $date[$i]) = split(",", $title[$i]); + } for ($a = 0; $a < scalar(@filename); $a++) { $access= $date[$a]; $file = $filename[$a]; $dir = $dironly[$a]; chdir("$dir"); print "$dir $file $access\n"; #use this for a log print utime ($access, $access, "$file"), "\n"; }
    this is a snippet from the file it reads the values from
    /staff/cy/share/shard/cr,MS_CR_New diensions server.xls,1020001208 /staff/cy/share/shard/cr,MS_CR_keon route modification 180902.xls,1044 +234302 /staff/cy/share/shard/cr,MS_CR Key route addition001.xls,1044234302 /staff/cy/share/shard/cr,CY_CR_Memory upgrade.doc,1034234102
    Results mean more than tidy code <G> (still tidy code would be nice). Three scripts, one to record all dates, one to change the document properties of office docs, one to set the dates back (as the second script makes all file dates the present).