ディレクトリ構造の深さに関係なく、Perl を使用して .wma および .wmv 拡張子を持つドライブ上のすべてのファイルの名前を .txt 拡張子に変更するにはどうすればよいですか?
7 に答える
perldoc File::Find を参照してください。ドキュメント内の例は一目瞭然であり、ほとんどの方法を理解することができます。試行したら、より多くの情報で質問を更新してください。
これが学習演習である場合は、最初に自分でやろうとすることで、よりよく学習できます。
アップデート:
自分でこれを行う方法を調べる機会があり、さまざまな解決策が投稿されているという事実を考慮して、私がこれを行う方法を投稿しています. 「.wmv」などのファイルを無視することを選択することに注意してください。私の正規表現では、ドットの前に何かが必要です。
#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
my ($dir) = @ARGV;
find( \&wanted, $dir );
sub wanted {
return unless -f;
return unless /^(.+)\.wm[av]$/i;
my $new = "$1.txt";
rename $_ => $new
or warn "'$_' => '$new' failed: $!\n";
return;
}
__END__
#!/usr/bin/perl 厳密に使用します。 警告を使用します。 File::Find を使用します。 私の $dir = '/path/to/dir'; ファイル::検索::検索( サブ { 私の $file = $_; -d $file の場合に戻ります。 return if $file !~ /(.*)\.wm[av]$/; $file、「$1.txt」の名前を変更するか、$! を削除します。 }、$ディレクトリ );
そして、あなたが初心者なら、もう 1 つ役立つアドバイスがあります。ファイルの名前を変更するには、「File::Copy」モジュールの「move()」メソッドを使用します (そして常に move() が失敗したかどうかを確認してください)。
また、名前が .wma/.wmv で終わるディレクトリの名前を誤って変更するという明らかでないバグを回避します (「必要な」コールバックがファイルとディレクトリの両方で呼び出されるため)。
PS私は間違いなく上記のFile::Findのアドバイスに同意します(また、このリンクで説明されているように、File::Find::Ruleを調べることを検討してください)。ただし、Perl を学習するための演習として、独自の再帰的ファイル ファインダーを作成する (または、より良い方法として、再帰的から幅優先検索ループに変更する) ことは、目的が簡単なファイルを単に記述するのではなく学習することである場合に検討することです。ワンオフ。
find . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \;
わかりました、上記には 2 つの基本的な問題があります。まず、perl ではなく find です。第二に、それは実際には .txt を最後に置くだけであり、あなたが望んでいたものではありません.
最初の問題は、本当に perl でこれを行う必要がある場合にのみ問題になります。これはおそらく perl を学んでいることを意味しますが、それは最初のステップに過ぎないので問題ありません。2 番目の問題は、単に仕事を終わらせたいだけで、言語を気にしない場合にのみ問題になります。最初に 2 番目の問題を解決します。
find . -name '*.wm[va]' -a -type f | while read f; do mv $f ${f%.*}; done
これで仕事は完了しますが、実際には perl ソリューションから離れてしまいます。これは、find ですべて完了した場合、find2perl で perl に変換できるためです。
find . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \;
これにより、保存できるperlスクリプトが出力されます。
find2perl . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \; > my.pl
これには、必要に応じて変更できる doexec() 関数が含まれています。1 つ目は、2 番目の引数を正しい名前に変更することです ( File::Basenameの basename 関数: basename($command[2], qw/.wmv .wma/) を使用)。システム、STDOUT 変更などに変更し、rename を呼び出すだけです。しかし、これで少なくとも開始できます。
私は最近似たようなことをしなければなりませんでした。このスクリプトには変更が必要ですが、必要なものはすべて含まれています。
- ファイルとディレクトリを再帰します (サブ再帰)。
- ディレクトリを操作する機能 (processDir) と、ファイルを操作する別の機能 (processFile) があります。
- File::Glob の glob 関数の代替バージョンを使用して、ファイル名のスペースを処理します。
- アクションは実行しませんが、代わりに出力ファイル (CSV、TAB、または perl スクリプト) を書き込み、ユーザーが大きな間違いを犯す前に提案された変更を確認できるようにします。
- 部分的な結果を定期的に出力します。これは、システムが途中でダウンした場合に役立ちます。
- 深さ優先で進んでいきます。サブディレクトリとファイルを処理する前に親ディレクトリを変更 (名前変更または移動) するスクリプトがあると、問題が発生する可能性があるため、これは重要です。
- スキップ リスト ファイルから読み取ります。これにより、アクセスしたくない巨大なディレクトリやマウントされたボリュームを回避できます。
- しばしば循環を引き起こすシンボリック リンクをたどりません。
processFile を少し変更するだけで、ほとんどの作業が必要になります。さらに、不要な機能を削除することもできます。(このスクリプトは、Windows でサポートされていない文字を名前に含むファイルを検索するように設計されています。)
注: 最後に「open」を呼び出します。これにより、MAC 上で結果のファイルがデフォルトのアプリケーションで開かれます。Windows では、「開始」を使用します。他の Unix システムにも同様のコマンドがあります。
#!/usr/bin/perl -w
# 06/04/2009. PAC. Fixed bug in processDir. Was using $path instead of $dir when forming newpath.
use strict;
use File::Glob ':glob'; # This glob allows spaces in filenames. The default one does not.
sub recurse(&$);
sub processFile($);
sub stem($);
sub processXMLFile($);
sub readFile($);
sub writeFile($$);
sub writeResults($);
sub openFileInApplication($);
if (scalar @ARGV < 4) {
print <<HELP_TEXT;
Purpose: Report on files and directories whose names violate policy by:
o containing illegal characters
o being too long
o beginning or ending with certain characters
Usage: perl EnforceFileNamePolicy.pl root-path skip-list format output-file
root-path .... Recursively process all files and subdirectories starting with this directory.
skip-list .... Name of file with directories to skip, one to a line.
format ....... Output format:
tab = tab delimited list of current and proposed file names
csv = comma separated list of current and proposed file names
perl = perl script to do the renaming
output-file .. Name of file to hold results.
Output: A script or delimited file that will rename the offending files and directories is printed to output-file.
As directories are processed or problems found, diagnostic messages will be printed to STDOUT.
Note: Symbolic links are not followed, otherwise infinite recursion would result.
Note: Directories are processed in depth-first, case-insensitive alphabetical order.
Note: If \$CHECKPOINT_FREQUENCY > 0, partial results will be written to intermediate files periodically.
This is useful if you need to kill the process before it completes and do not want to lose all your work.
HELP_TEXT
exit;
}
########################################################
# #
# CONFIGURABLE OPTIONS #
# #
########################################################
my $BAD_CHARACTERS_CLASS = "[/\\?<>:*|\"]";
my $BAD_SUFFIX_CLASS = "[. ]\$";
my $BAD_PREFIX_CLASS = "^[ ]";
my $REPLACEMENT_CHAR = "_";
my $MAX_PATH_LENGTH = 256;
my $WARN_PATH_LENGTH = 250;
my $LOG_PATH_DEPTH = 4; # How many directories down we go when logging the current directory being processed.
my $CHECKPOINT_FREQUENCY = 20000; # After an integral multiple of this number of directories are processed, write a partial results file in case we later kill the process.
########################################################
# #
# COMMAND LINE ARGUMENTS #
# #
########################################################
my $rootDir = $ARGV[0];
my $skiplistFile = $ARGV[1];
my $outputFormat = $ARGV[2];
my $outputFile = $ARGV[3];
########################################################
# #
# BEGIN PROCESSING #
# #
########################################################
my %pathChanges = (); # Old name to new name, woth path attached.
my %reasons = ();
my %skip = (); # Directories to skip, as read from the skip file.
my $dirsProcessed = 0;
# Load the skiplist
my $skiplist = readFile($skiplistFile);
foreach my $skipentry (split(/\n/, $skiplist)) {
$skip{$skipentry} = 1;
}
# Find all improper path names under directory and store in %pathChanges.
recurse(\&processFile, $rootDir);
# Write the output file.
writeResults(0);
print "DONE!\n";
# Open results in an editor for review.
#WARNING: If your default application for opening perl files is the perl exe itself, this will run the otput perl script!
# Thus, you may want to comment this out.
# Better yet: associate a text editor with the perl script.
openFileInApplication($outputFile);
exit;
sub recurse(&$) {
my($func, $path) = @_;
if ($path eq '') {
$path = ".";
}
## append a trailing / if it's not there
$path .= '/' if($path !~ /\/$/);
## loop through the files contained in the directory
for my $eachFile (sort { lc($a) cmp lc($b) } glob($path.'*')) {
# If eachFile has a shorter name and is a prefix of $path, then stop recursing. We must have traversed "..".
if (length($eachFile) > length($path) || substr($path, 0, length($eachFile)) ne $eachFile) {
## if the file is a directory
my $skipFile = defined $skip{$eachFile};
if( -d $eachFile && ! -l $eachFile && ! $skipFile) { # Do not process symbolic links like directories! Otherwise, this will never complete - many circularities.
my $depth = depthFromRoot($eachFile);
if ($depth <= $LOG_PATH_DEPTH) {
# Printing every directory as we process it slows the program and does not give the user an intelligible measure of progress.
# So we only go so deep in printing directory names.
print "Processing: $eachFile\n";
}
## pass the directory to the routine ( recursion )
recurse(\&$func, $eachFile);
# Process the directory AFTER its children to force strict depth-first order.
processDir($eachFile);
} else {
if ($skipFile) {
print "Skipping: $eachFile\n";
}
# Process file.
&$func($eachFile);
}
}
}
}
sub processDir($) {
my ($path) = @_;
my $newpath = $path;
my $dir;
my $file;
if ($path eq "/") {
return;
}
elsif ($path =~ m|^(.*/)([^/]+)$|) {
($dir, $file) = ($1, $2);
}
else {
# This path has no slashes, hence must be the root directory.
$file = $path;
$dir = '';
}
if ($file =~ /$BAD_CHARACTERS_CLASS/) {
$file =~ s/($BAD_CHARACTERS_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectDir($path, $newpath, "Illegal character in directory.");
}
elsif ($file =~ /$BAD_SUFFIX_CLASS/) {
$file =~ s/($BAD_SUFFIX_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectDir($path, $newpath, "Illegal character at end of directory.");
}
elsif ($file =~ /$BAD_PREFIX_CLASS/) {
$file =~ s/($BAD_PREFIX_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectDir($path, $newpath, "Illegal character at start of directory.");
}
elsif (length($path) >= $MAX_PATH_LENGTH) {
rejectDir($path, $newpath, "Directory name length > $MAX_PATH_LENGTH.");
}
elsif (length($path) >= $WARN_PATH_LENGTH) {
rejectDir($path, $newpath, "Warning: Directory name length > $WARN_PATH_LENGTH.");
}
$dirsProcessed++;
if ($CHECKPOINT_FREQUENCY > 0 && $dirsProcessed % $CHECKPOINT_FREQUENCY == 0) {
writeResults(1);
}
}
sub processFile($) {
my ($path) = @_;
my $newpath = $path;
$path =~ m|^(.*/)([^/]+)$|;
my ($dir, $file) = ($1, $2);
if (! defined ($file) || $file eq '') {
$file = $path;
}
if ($file =~ /$BAD_CHARACTERS_CLASS/) {
$file =~ s/($BAD_CHARACTERS_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectFile($path, $newpath, "Illegal character in filename.");
}
elsif ($file =~ /$BAD_SUFFIX_CLASS/) {
$file =~ s/($BAD_SUFFIX_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectFile($path, $newpath, "Illegal character at end of filename.");
}
elsif ($file =~ /$BAD_PREFIX_CLASS/) {
$file =~ s/($BAD_PREFIX_CLASS)/$REPLACEMENT_CHAR/g;
$newpath = $dir . $file;
rejectFile($path, $newpath, "Illegal character at start of filename.");
}
elsif (length($path) >= $MAX_PATH_LENGTH) {
rejectFile($path, $newpath, "File name length > $MAX_PATH_LENGTH.");
}
elsif (length($path) >= $WARN_PATH_LENGTH) {
rejectFile($path, $newpath, "Warning: File name length > $WARN_PATH_LENGTH.");
}
}
sub rejectDir($$$) {
my ($oldName, $newName, $reason) = @_;
$pathChanges{$oldName} = $newName;
$reasons{$oldName} = $reason;
print "Reason: $reason Dir: $oldName\n";
}
sub rejectFile($$$) {
my ($oldName, $newName, $reason) = @_;
$pathChanges{$oldName} = $newName;
$reasons{$oldName} = $reason;
print "Reason: $reason File: $oldName\n";
}
sub readFile($) {
my ($filename) = @_;
my $contents;
if (-e $filename) {
# This is magic: it opens and reads a file into a scalar in one line of code.
# See http://www.perl.com/pub/a/2003/11/21/slurp.html
$contents = do { local( @ARGV, $/ ) = $filename ; <> } ;
}
else {
$contents = '';
}
return $contents;
}
sub writeFile($$) {
my( $file_name, $text ) = @_;
open( my $fh, ">$file_name" ) || die "Can't create $file_name $!" ;
print $fh $text ;
}
# writeResults() - Compose results in the appropriate format: perl script, tab delimited, or comma delimited, then write to output file.
sub writeResults($) {
my ($checkpoint) = @_;
my $outputText = '';
my $outputFileToUse;
my $checkpointMessage;
if ($checkpoint) {
$checkpointMessage = "$dirsProcessed directories processed so far.";
}
else {
$checkpointMessage = "$dirsProcessed TOTAL directories processed.";
}
if ($outputFormat eq 'tab') {
$outputText .= "Reason\tOld name\tNew name\n";
$outputText .= "$checkpointMessage\t\t\n";
}
elsif ($outputFormat eq 'csv') {
$outputText .= "Reason,Old name,New name\n";
$outputText .= "$checkpointMessage,,\n";
}
elsif ($outputFormat eq 'perl') {
$outputText = <<END_PERL;
#/usr/bin/perl
# $checkpointMessage
#
# Rename files and directories with bad names.
# If the reason is that the filename is too long, you must hand edit this script and choose a suitable, shorter new name.
END_PERL
}
foreach my $file (sort {
my $shortLength = length($a) > length($b) ? length($b) : length($a);
my $prefixA = substr($a, 0, $shortLength);
my $prefixB = substr($b, 0, $shortLength);
if ($prefixA eq $prefixB) {
return $prefixA eq $a ? 1 : -1; # If one path is a prefix of the other, the longer path must sort first. We must process subdirectories before their parent directories.
}
else {
return $a cmp $b;
}
} keys %pathChanges) {
my $changedName = $pathChanges{$file};
my $reason = $reasons{$file};
if ($outputFormat eq 'tab') {
$outputText .= "$reason\t$file\t$changedName\n";
}
elsif ($outputFormat eq 'csv') {
$outputText .= "$reason,$file,$changedName\n";
}
else {
# Escape the spaces so the mv command works.
$file =~ s/ /\\ /g;
$changedName =~ s/ /\\ /g;
$outputText .= "#$reason\nrename \"$file\", \"$changedName\"\n";
}
}
$outputFileToUse = $outputFile;
if ($checkpoint) {
$outputFileToUse =~ s/(^.*)([.][^.]+$)/$1-$dirsProcessed$2/;
}
writeFile($outputFileToUse, $outputText);
}
# Compute how many directories deep the given path is below the root for this script.
sub depthFromRoot($) {
my ($dir) = @_;
$dir =~ s/\Q$rootDir\E//;
my $count = 1;
for (my $i = 0; $i < length($dir); $i++) {
if (substr($dir, $i, 1) eq "/") { $count ++; }
}
return $count;
}
#openFileInApplication($filename) - Open the file in its default application.
#
# TODO: Must be changed for WINDOWS. Use 'start' instead of 'open'???
sub openFileInApplication($) {
my ($filename) = @_;
`open $filename`;
}
# include the File::Find module, that can be used to traverse directories
use File::Find;
# starting in the current directory, tranverse the directory, calling
# the subroutine "wanted" on each entry (see man File::Find)
find(\&wanted, ".");
sub wanted
{
if (-f and
/.wm[av]$/)
{
# when this subroutine is called, $_ will contain the name of
# the directory entry, and the script will have chdir()ed to
# the containing directory. If we are looking at a file with
# the wanted extension - then rename it (warning if it fails).
my $new_name = $_;
$new_name =~ s/\.wm[av]$/.txt/;
rename($_, $new_name) or
warn("rename($_, $new_name) failed - $!");
}
}
renameを見てください。
find -type f -name '*.wm?' -print0 | xargs -0 rename 's/\.wm[av]$/.txt/'
また
find -type f -name '*.wm?' -exec rename 's/\.wm[av]$/.txt/' {} +
または独自のスクリプトを作成する
#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
find( sub {
return unless -f;
my $new = $_;
return unless $new =~ s/\.wm[av]$/.txt/;
rename $_ => $new
or warn "rename '$_' => '$new' failed: $!\n";
}, @ARGV );