2009-06-26 2 views
4

디렉토리 구조의 깊이에 관계없이 Perl을 사용하여 .wma 및 .wmv 확장자가 .txt 확장자 인 드라이브의 모든 파일의 이름을 어떻게 바꿀 수 있습니까?Perl을 사용하면 드라이브의 모든 하위 디렉토리에있는 파일의 이름을 어떻게 바꿀 수 있습니까?

+2

신안의 대답은 그렇게하지만, 여기에서 다른 일각한다. 나는 처음에 File :: Find unintuitive를 찾았지만이 기사가 큰 도움이된다는 것을 알게되었다. http://www.stonehenge.com/merlyn/LinuxMag/col45.html – Telemachus

+1

랜달 슈왈츠 (Randal Schwartz)의 모든 기사는 반드시 IMO를 읽어야한다. ;-) –

답변

10

perldoc File::Find을 참조하십시오. 문서의 예제는 꽤 자명하며 거기에서 가장 많은 것을 얻을 것입니다. 시도가있을 때 더 많은 정보로 질문을 업데이트하십시오.

이것이 학습 학습이라면, 먼저 자신을 해보려고하면 더 잘 배우게됩니다.

UPDATE :

당신이 직접 및 계정으로 다양한 솔루션이, 내가 이런 짓을했는지 얼마나 게시하고 게시 된 사실을 복용하는 방법으로 볼 수있는 기회를 가지고 있다고 가정. ".wmv"와 같은 파일을 무시하도록 선택합니다. My regex에는 점 앞에 오는 것이 필요합니다.

#!/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__ 
2

그리고 당신은 초보자, 조언을 하나 개 더 유용한 조각 경우 : 파일의 이름을 변경하려면 "파일 :: 복사"에서 "이동()"메소드 모듈 를 사용 (항상 여부 이동 확인()

또한

, 실수로 이름이 .WMA로 끝나는 디렉토리 이름을 변경하는 않은 명백한 버그를 피하기) 실패/.WMV (콜백 파일 및 디렉토리)

PS 모두이라고 "원"이후 필자는 File :: Find advice와 확실히 동의한다. (또한, File :: Find :: Rule을 조사해 보자. this link에서 설명했다.) 그러나 Perl을 배우는 연습으로 자신 만의 재귀 파일 찾기 도구를 작성하는 것이 좋습니다. 재귀 적으로 파일을 찾기 위해 재귀 적으로 다시 검색 루프를 사용하는 것이 좋습니다. 목표를 작성하는 대신 학습하는 것이 좋습니다. 빠른 일회용.

0

최근에 비슷한 일을해야했습니다. 이 스크립트는 수정이 필요하지만, 모든 필수 요소가 것이다 : 그것은 파일과 디렉토리 (하위 같이 Recurse)를 통해 재귀

  1. 합니다.
  2. 디렉토리 (processDir) 및 ( (processFile) 파일을 처리하기위한 별도의 기능을 수행하는 기능이 있습니다.
  3. File :: Glob의 glob 함수의 대체 버전을 사용하여 파일 이름 의 공백을 처리합니다.
  4. 아무런 작업도 수행하지 않고 대신 출력 파일 (CSV, TAB 또는 perl 스크립트)을 작성하므로 큰 실수를하기 전에 제안 된 변경 사항을 검토 할 수 있습니다.
  5. 부분 결과가 으로 주기적으로 출력되므로 시스템이 부분적으로 작동하는 경우에 유용합니다.
  6. 깊이 우선으로 진행됩니다. 하위 디렉터리를 처리하기 전에 부모 디렉터리를 수정 (이동하거나 바꿉니다)하는 스크립트가있는 경우 잘못된 작업이 발생할 수 있으므로이 작업이 중요합니다.
  7. 건너 뛰기 목록 파일 에서 큰 디렉토리 인 및 마운트되지 않은 볼륨을 피할 수 있습니다 ( ).
  8. 종종 원형을 야기하는 기호 링크 을 따르지 않습니다.

processFile을 약간 수정하면 필요하지 않은 대부분의 기능과 필요하지 않은 기능을 제거 할 수 있습니다. 이 스크립트는 Windows에서 지원되지 않는 이름의 문자가있는 파일을 찾도록 설계되었습니다.

참고 : 결국 "열기"를 호출하여 MAC에서 기본 응용 프로그램에서 결과 파일을 엽니 다. Windows에서는 "start"를 사용하십시오. 다른 유닉스 시스템에는 비슷한 명령이있다.

#!/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`; 
} 
+0

프로토 타입을 사용하지 마십시오. 그들은 당신이 생각하는 것처럼 보이지 않습니다. http://www.perl.com/language/misc/fmproto.html ... 저는 OP가 그가 할 수없는 긴 스크립트에 의해 가장 잘 서비스되었다고 생각하지 않습니다. 그의 학습 수준에서 이해하십시오. 나는 당신의 스크립트가 무엇을하는지 모르지만 File :: Find (또는 그 파생물)를 사용하지 않는다는 사실은 붉은 깃발을 일으킨다. –

+0

'if (@ARGV <4)'는 완벽하게 괜찮습니다. 표현식이 스칼라 문맥에서 이미 평가 되었기 때문에 '스칼라'가 필요하지 않습니다. –

+0

또한,'readFile' 서브 루틴을 삭제하고 File :: Slurp에서'read_file'을 사용하십시오. 내 말은, 당신이 실제로 우리의 기사를 읽었습니까? 당신은 당신의 코멘트에서 그것을 인용하지만 그것은 당신이 요점을 놓친 것 같습니다. –

0

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); 
1
find . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \; 

확인하기 위해선, 위의 두 개의 기본적인 문제가있다. 우선, 그것은 펄 (Perl)이 아니라 찾기입니다. 둘째, 실제로는 .txt를 끝에 붙이기 만하면됩니다.

첫 번째 문제는 실제로 perl에서이 작업을 수행해야하는 경우에만 문제가됩니다. 아마 당신이 펄을 배우는 것을 의미 할 수도 있지만, 그것은 단지 첫 걸음이기 때문에 괜찮습니다. 두 번째 문제는 단지 일을 끝내고 언어에 신경 쓰지 않고 싶을 때만 문제가됩니다. 나는 첫 번째 문제 해결 있습니다 : 그냥 일을 얻을 수

find . -name '*.wm[va]' -a -type f | while read f; do mv $f ${f%.*}; done 

을하지만, 실제로는 펄 솔루션에서 떨어져 우리를 이동합니다.

find2perl . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \; > my.pl 

그것은이 포함되어

find . -name '*.wm[va]' -a -type f -exec mv '{}' '{}.txt' \; 

이것은 당신이 저장할 수있는 펄 스크립트를 출력 할 것이다 : 당신이 그것을 모두 찾기에 끝낼 경우 그것은 당신이 find2perl와 펄을 변환 할 수 있기 때문이다 doexec() 함수는 원하는 것을 수행하도록 수정할 수 있습니다. 먼저 File::Basename의 basename 함수를 사용하여 두 번째 인수를 올바른 이름으로 변경합니다. basename ($ command [2], qw/.wmv .wma /)) 두 번째 인수는 시스템에 대한 호출을 제거하는 것입니다. STDOUT munging 등을 호출하고 rename을 호출합니다. 그러나 이것은 적어도 당신에게 시작을 제공합니다.

0
# 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 - $!"); 
    } 
} 
3
 
#!/usr/bin/perl 

use strict; 
use warnings; 
use File::Find; 

my $dir = '/path/to/dir'; 

File::Find::find(
    sub { 
     my $file = $_; 
     return if -d $file; 
     return if $file !~ /(.*)\.wm[av]$/; 
     rename $file, "$1.txt" or die $!; 
    }, $dir 
); 
관련 문제