Hi, The below code works fine.
No, it does not. It actually is dangerously wrong.
chdir '/user/perl_monks' || die "cannot change dir: $!";
open(my $fh, "ls|") || die "cannot open the file handle:$!";
my @files = <$fh>;
chomp @files;
for ( @files) {
system ( "scp $_ username\@host:/any/folder/" );
}
Here is what's wrong with this code:
- Newlines are legal in filenames. So, ls may return a single filename splitted over two or more lines, resulting in a filename splitted over two or more elements of @files.
- At least GNU ls behaves differently depending on environment variables. It may quote and/or escape special characters in filenames (see GNU ls documentation), but your code assumes sane defaults.
- Various characters that have a special meaning to the shell are legal in filenames. (In fact, all characters except ASCII NUL and / are legal in filenames.) So you may legally name a file $(rm -rf /) or $(rm -rf *). Because your code use the single-argument form of system, and it does not clean up $_ before interpolation, the shell will be invoked, and it will interpret those filenames according to shell rules. So, with these filenames, you will likely delete a lot of files.
- You blindly assume that system and scp succeed.
- Shells are different. Quoting rules change from shell to shell, so there is no safe way to handle the shell.
And here is how to fix it:
- Don't use the shells to read filenames from a directory. glob will do, as will opendir, readdir, closedir, and IO::Dir. For a simple "ls" with sane defaults, glob('*') should do the job.
- See above. Avoiding ls also avoids all quoting and escaping issues of ls.
- Use the multiple-argument form of system, i.e. system('/path/to/scp',$_,'username@host:/any/folder');.
- Check the return value of system to see if system or scp failed. Also look at $!.
- Avoid the shells. It just makes life harder. If you want a harder life, read http://www.in-ulm.de/~mascheck/various/.
I also found that special characters like @ inside the system qq needs to be escaped.
This has nothing to do with system, you always have to escape @ inside double quoted strings.
Also note that the qx// operator, also in the backticks form, can also be used in list context. There, it returns one list element per line of output. (This is documented in I/O Operators.) You don't have to use pipe-open and readline (<$handle>), chomp(my @files=`ls`) would be sufficient. But see above for why this is at least problematic, if not dangerous.
Alexander
--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)