уникально переименовать каждый из многих файлов, используя perl

У меня есть папка, содержащая 96 файлов, которые я хочу переименовать. Проблема в том, что для каждого имени файла требуется уникальное изменение... не то, что добавление нуля перед каждым именем или изменение расширений. Выполнять поиск и замену нецелесообразно. Вот пример имен, которые я хочу изменить:

newSEACODI-sww2320H-sww24_07A_CP.9_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07B_CP.10_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07C_CP.11_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07D_CP.12_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07E_R.1_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07F_R.3_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07G_R.4_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07H_R.5_sww2320H_sww2403F.fsa

Я хотел бы использовать perl, чтобы изменить приведенные выше имена на приведенные ниже имена соответственно:

SEACODI_07A_A.2_sww2320H_2403F.fsa
SEACODI_07B_A.4_sww2320H_2403F.fsa
SEACODI_07C_H.1_sww2320H_2403F.fsa
SEACODI_07D_H.3_sww2320H_2403F.fsa
SEACODI_07E_H.6_sww2320H_2403F.fsa
SEACODI_07F_H.7_sww2320H_2403F.fsa
SEACODI_07G_Rb.4_sww2320H_2403F.fsa
SEACODI_07H_Rb.9_sww2320H_2403F.fsa

Можно ли такое сделать? У меня есть смутное представление о том, что я мог бы создать текстовый файл со списком новых имен и назвать этот список @newnames. Я бы сделал еще один массив из текущих имен файлов и назвал бы его @oldnames. Затем я бы сделал какой-то цикл for, в котором каждый элемент $i в @oldnames заменяется соответствующим $i в @newnames.

Однако я не знаю, как сделать массив из моих текущих имен файлов, и поэтому я не уверен, что эта расплывчатая идея находится на правильном пути. Я храню свои файлы с перепутанными именами в каталоге под названием «oldnames». Ниже приведена моя попытка создать массив из имен файлов в этом каталоге:

#!/usr/bin/perl -w
use strict; use warnings;

my $dir = 'oldnames';
opendir ('oldnames', $dir) or die "cannot open dir $dir: $!";
my @file = readdir 'oldnames';
closedir 'oldnames';

print "@file\n";

Вышеупомянутое, похоже, ничего не делало. Я потерялся. Помощь?


person user2585169    schedule 15.07.2013    source источник
comment
Разделите проблему на 3 части: (1) проанализируйте существующие имена файлов, чтобы извлечь компоненты; (2) разработать преобразование, которое преобразует старые имена файлов в желаемый новый формат, и заполнить карту преобразованиями; (3) написать код для переименования. Если вы не можете придумать алгоритмическое преобразование, то единственным вариантом является файл параметров со старым и новым именами файлов, который вы создаете вручную.   -  person Jim Garrison    schedule 16.07.2013
comment
Чтобы узнать, как использовать readdir, посмотрите руководство.   -  person Jim Garrison    schedule 16.07.2013
comment
Говоря о стратегиях, я бы посоветовал вашей программе выводить сценарий с одной строкой на файл, как в mv oldname newname, а затем вы можете запустить его, когда он будет выглядеть правильно.   -  person Bill Ruppert    schedule 16.07.2013
comment
Билл: Я бы не стал делать mv для каждого файла, так как всего их около сотни.   -  person user2585169    schedule 17.07.2013
comment
Джим: К сожалению, нет алгоритма, который я мог бы использовать. У меня есть электронная таблица со старыми и новыми именами файлов рядом. Это то, что вы подразумеваете под файлом параметров? Также: не могли бы вы объяснить, что подразумевается под заполнением карты конверсиями?   -  person user2585169    schedule 17.07.2013


Ответы (3)


Здесь:

#!/usr/bin/perl
use warnings;
use strict;

use autodie;
use File::Copy;

# capture script name, in case we are running the script from the 
# same directory we working on. 
my $this_file = (split(/\//, $0))[-1];
print "skipping file: $this_file\n";

my $oldnames = "/some/path/to/oldnames";
my $newnames = "/some/path/to/newnames";

# open the directory
opendir(my $dh, $oldnames); 

# grep out all directories and possibly this script. 
my @files_to_rename = grep { !-d && $_ ne $this_file } readdir $dh;
closedir $dh;

### UPDATED ###
# create hash of file names from lists:
my @old_filenames = qw(file1 file2 file3 file4);
my @new_filenames = qw(onefile twofile threefile fourfile);
my $filenames = create_hash_of_filenames(\@old_filenames, \@new_filenames);
my @missing_new_file = ();  


# change directory, so we don't have to worry about pathing
# of files to rename and move... 
chdir($oldnames);
mkdir($newnames) if !-e $newnames;


### UPDATED ###
for my $file (@files_to_rename) {
    # Check that current file exists in the hash,
    # if true, copy old file to new location with new name
    if( exists($filenames->{$file}) ) { 
        copy($file, "$newnames/$filenames->{$file}");
    } else {
        push @missing_new_file, $file;
    } 
}


if( @missing_new_file ) { 
    print "Could not map files:\n", 
        join("\n", @missing_new_file), "\n";
}

# create_hash_of_filenames: creates a hash, where
# key = oldname, value = newname
# input: two array refs 
# output: hash ref 
sub create_hash_of_filenames {
    my ($oldnames, $newnames) = @_; 
    my %filenames = (); 

    for my $i ( 0 .. (scalar(@$oldnames) - 1) ) { 
        $filenames{$$oldnames[$i]} = $$newnames[$i];
    }

    # see Dumper output below, to see data structure
    return \%filenames;
}

Результат самосвала:

$VAR1 = {
  'file2' => 'twofile',
  'file1' => 'onefile',
  'file4' => 'fourfile',
  'file3' => 'threefile'
};

Запуск скрипта:

$ ./test.pl 
skipping file: test.pl
Could not map files:
a_file.txt
b_file.txt
c_file.txt

Результат файла:

$ ls oldnames/
a_file.txt
b_file.txt
c_file.txt
file1
file2
file3
file4

$ ls newnames/
fourfile
onefile
threefile
twofile
person chrsblck    schedule 16.07.2013
comment
Спасибо, что показали мне, как использовать File::Copy! Это очень пригодится. Извините, я неточно выразился о том, какие изменения необходимо внести. Мне нужно изменить что-то другое в каждом имени файла. Я не могу применить одно и то же изменение ко всем именам. У меня есть список того, каким должно быть каждое имя, и для них нет шаблона. Это похоже на попытку изменить список имен (Бетти, Сью, Билли, Боб) на список (Эндрю, Софи, Реджинальд, Эбигейл), чтобы Бетти стала Эндрю, Сью стала Софи и так далее. - person user2585169; 17.07.2013
comment
@user2585169 user2585169 Обновлено, это решает вашу проблему с именами. - person chrsblck; 17.07.2013
comment
+1 за серьезное решение. Он даже проверяет имя сценария и не копирует его! Хорошая работа. Я добавил грязный однострочник как небезопасную альтернативу :-) - person G. Cito; 17.07.2013
comment
Спасибо Спасибо! Я бы проголосовал за тебя, если бы у меня была правильная репутация, chrsblck. - person user2585169; 18.07.2013

Ваш код немного странный, но он должен работать. Вы запускаете его в каталоге «oldnames» или в каталоге над ним? Вы должны быть в каталоге над ним. Более стандартный способ записи будет таким:

#!/usr/bin/perl -w
use strict; use warnings;

my $dir = 'oldnames';
opendir ( my $oldnames, $dir) or die "cannot open dir $dir: $!";
my @file = readdir $oldnames;
closedir $oldnames;

print "@file\n";

Это заполнит @files всеми файлами в oldnames, включая '.' и '..'. Возможно, вам придется отфильтровать их в зависимости от того, как вы выполняете переименование.

person dms    schedule 15.07.2013

Можно ли сделать это с помощью rename? Это позволяет вам использовать perl код и выражения в качестве аргументов, насколько я помню.

Настоящий ответ - это ответ @chrsblck, он выполняет некоторые проверки и не создает беспорядка.

Для сравнения вот грязный лайнер, которого может хватить. Он зависит от того, предоставите ли вы список эквивалентных новых имен файлов, которые переименуют ваш список старых файлов в правильном порядке. Возможно, в вашей ситуации (когда вы не хотите программно преобразовывать имена файлов) вы могли бы просто использовать цикл оболочки (см. конец этого поста), читая списки новых и старых имен из файла. Лучшим perl решением было бы поместить оба из этих списков имен файлов в два столбца, а затем этот файл с помощью переключателя -a , @F и затем использоватьFile::Copy для копирования файлов.

В любом случае, ниже приведены некоторые предложения.

Сначала настройте вещи:

 % vim newfilenames.txt # list new names one per line corresponding to old names.
 % wc -l newfilenames.txt # the same number of new names as files in ./oldfiles/
  8 newfilenames.txt
 % ls -1 oldfiles  # 8 files rename these in order to list from newfilenames.txt
 newSEACODI-sww2320H-sww24_07A_CP.9_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07B_CP.10_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07C_CP.11_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07D_CP.12_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07E_R.1_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07F_R.3_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07G_R.4_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07H_R.5_sww2320H_sww2403F.fsa

С файлами, расположенными, как указано выше, скопируйте все:

perl -MFile::Copy -E 'opendir($dh , oldfiles); @newfiles=`cat newfilenames.txt`; chomp @newfiles; @oldfiles = sort grep(/^.+\..+$/, readdir $dh); END {for $i (0..$#oldfiles){copy("oldfiles/$oldfiles[$i]", "newfiles/$newfiles[$i]"); }}'

Некрасиво: вам нужно grep и sort на @oldfiles избавиться от . .. и упорядочить элементы массива. И всегда есть риск того, что опечатка может создать беспорядок, и ее будет трудно понять.
Если вы поместите старое и новое имена в пару файлов, вы можете просто сделать это с помощью сценария оболочки:

for i in `cat ../oldfilenames.txt` ; do ; done; for n in `cat ../newfilenames.txt`; do cp $i $n;

или просто cd в директорию со старыми файлами и делаем:

mkdir new
for i in * ; do ; done; for n in `cat ../newfilenames.txt`; do cp $i new/$n;

Удачи!

person G. Cito    schedule 16.07.2013
comment
Я посмотрел на rename. Проблема в том, что я не совсем знаю, какое правило использовать. Нет шаблона для изменения имени, для которого я могу составить алгоритм. По сути, мне нужно заменить строки символов в пронумерованном массиве строками в соответствующих позициях другого массива равной длины... имеет ли это смысл? - person user2585169; 17.07.2013
comment
Да - я неправильно понял. Тогда ваша проблема может быть проще - нет необходимости в регулярных выражениях :) - person G. Cito; 17.07.2013
comment
Действительно - и все же, я не знаю, как это сделать. - person user2585169; 17.07.2013
comment
@user2585169 user2585169 Я добавил несколько примеров perl одного вкладыша и цикла оболочки, которые должны выполнить вашу задачу. Вы должны принять ответы @chrsblck или мои, но ВЫ ДОЛЖНЫ голосовать за нас обоих, что бы вы ни делали :-) - person G. Cito; 17.07.2013
comment
Я бы проголосовал за вас, если бы мог - похоже, у меня недостаточно очков репутации ^-^ - person user2585169; 18.07.2013
comment
ну тогда либо запомни на потом, либо @chrsblck должен проголосовать за меня от твоего имени :-) ура - person G. Cito; 18.07.2013