Заменить первый символ перед совпадением

Для каждой строки мне нужно добавить точку с запятой ровно за один символ до первого совпадения буквенно-цифрового знака, но только для буквенно-цифрового знака после первого появления точки с запятой.

Пример:

Вход:

00000001;Root;;
00000002;  Documents;;
00000003;    oracle-advanced_plsql.zip;file;
00000004;  Public;;
00000005;  backup;;
00000006;    20110323-JM-F.7z.001;file;
00000007;    20110426-JM-F.7z.001;file;
00000008;    20110603-JM-F.7z.001;file;
00000009;    20110701-JM-F-via-summer_school;;
00000010;      20110701-JM-F-via-summer_school.7z.001;file;

Желаемый результат:

00000001;;Root;;
00000002;  ;Documents;;
00000003;    ;oracle-advanced_plsql.zip;file;
00000004;  ;Public;;
00000005;  ;backup;;
00000006;    ;20110323-JM-F.7z.001;file;
00000007;    ;20110426-JM-F.7z.001;file;
00000008;    ;20110603-JM-F.7z.001;file;
00000009;    ;20110701-JM-F-via-summer_school;;
00000010;      ;20110701-JM-F-via-summer_school.7z.001;file;

Может ли кто-нибудь помочь мне создать регулярное выражение Perl для этого? Мне это нужно в программе, а не как oneliner.


person royskatt    schedule 07.07.2013    source источник
comment
Что вы пробовали? Пожалуйста, покажите нам свои собственные попытки. Так нам будет легче сказать вам, где вы ошиблись и как это исправить. Кроме того, у вас есть весь ввод в одной строке или вы читаете его построчно?   -  person Martin Ender    schedule 07.07.2013
comment
На самом деле я не знаю, как создать регулярное выражение, которое соответствует моим требованиям. Я знаю основные регулярные выражения и пытался получить больше знаний об этом. Тем не менее, учебные пособия очень подробные (perldoc.perl.org/perlre.html). Моя единственная идея заключалась в том, чтобы индексировать каждую строку таким образом, пока ( $result != -1 ) { $offset = $result + 1; $ результат = индекс ($ строка, $ символ, $ смещение ); } и проверить, когда пробелы закончатся. Я читаю ввод построчно, используя массив. Но это уже не регулярное выражение.   -  person royskatt    schedule 07.07.2013
comment
Попробуйте это руководство.   -  person Martin Ender    schedule 07.07.2013


Ответы (3)


Прежде всего, вот программа, которая, кажется, соответствует вашим требованиям:

#/usr/bin/perl -w
while(<>) {                                                           
  s/^(.*?;.*?)(\w)/$1;$2/;                                            
  print $_;                                                           
}                                                                     

Сохраните его в файле «program.pl», сделайте его исполняемым с помощью «chmod u+x program.pl» и запустите его на ваших входных данных следующим образом:

program.pl input-data.txt

Вот объяснение регулярного выражения:

s/        # start search-and-replace regexp
  ^       # start at the beginning of this line
  (       # save the matched characters until ')' in $1
    .*?;  # go forward until finding the first semicolon
    .*?   # go forward until finding... (to be continued below)
  )
  (       # save the matched characters until ')' in $2
    \w    # ... the next alphanumeric character.
  )
/         # continue with the replace part
  $1;$2   # write all characters found above, but insert a ; before $2
/         # finish the search-and-replace regexp.

Основываясь на вашем образце ввода, я бы использовал более конкретное регулярное выражение:

s/^(\d*; *)(\w)/$1;$2/;

Это выражение начинается в начале строки, пропускает числа (\d*), за которыми следует первая точка с запятой и пробел. Перед следующим символом слова ставится точка с запятой.

Берите то, что лучше всего соответствует вашим потребностям!

person Yaakov Belch    schedule 07.07.2013
comment
Большое спасибо за этот ответ! - person royskatt; 08.07.2013

Это способ вставить точку с запятой после первой точки с запятой и пробела, но перед первым не пробелом.

s/;\s*\K(?=\S)/;/

Если вы чувствуете необходимость, вы можете использовать \w вместо \S, но я чувствовал, что с этим вводом это была ненужная спецификация.

Экранирование \K (keep) похоже на утверждение просмотра назад в том смысле, что оно не удаляет то, что ему соответствует. То же самое касается утверждения с опережением, так что все, что делает эта замена, это вставляет точку с запятой в указанное место.

person TLP    schedule 07.07.2013
comment
Обратите внимание, что это работает, только если ввод читается построчно. В противном случае вы бы добавили ^[^;]* в начало шаблона и модификаторы mg. - person Martin Ender; 07.07.2013
comment
@ m.buettner Нет, вы не можете просто добавить ^, вам нужно вставить ^[^;]* или что-то в этом роде. Но поскольку мы можем решить не усложнять ситуацию, лучше этого не делать. - person TLP; 07.07.2013
comment
Вау, ты заглянул мне через плечо, когда я писал свой комментарий? :П - person TLP; 07.07.2013
comment
s/;\s*\K(?=\S)/;/ можно упростить до s/;\s*\K/;/, если нет строк, полностью состоящих из 00000009;. - person ikegami; 07.07.2013

Во-первых, спасибо за отличные ответы!

На самом деле мой фрагмент кода выглядит так:

 our $seperator=";" # at the beginning of the file
 #...
 sub insert {
    my ( $seperator, $line, @all_lines, $count, @all_out );
    $count     = 0;
    @all_lines = read_file($filename);

    foreach $line (@all_lines) {
        $count = sprintf( "%08d", $count );
        chomp $line;
        $line =~ s/\:/$seperator/;                          # works
        $line =~ s/\ file/file/;                            # works

        #$line=~s/;\s*\K(?=\S)/;/;                          # doesn't work
        $line =~ s/^(.*?$seperator.*?)(\w)/$1$seperator$2/; # doesn't work
        say $count . $seperator . $line . $seperator; 

        $count++; # btw, is there maybe a hidden index variable in a foreach-loop I could us instead of a new variable??
        push( @all_out, $count . $seperator . $line . $seperator . "\n" );
    }

    write_file( $csvfile, @all_out ); # using File::Slurp
}

Чтобы получить то, что я вам представил, я уже сделал несколько небольших замен, как вы можете видеть в начале цикла foreach.

Мне любопытно, почему регулярные выражения, представленные TLP и Yaakov не работает в моем коде. В целом они работают, но только если они написаны так, как в примере, который привел Яаков:

while(<>) {                                                           
  s/^(.*?;.*?)(\w)/$1;$2/;                                            
  print $_;                                                           
}      
person royskatt    schedule 07.07.2013
comment
Мне кажется, что когда вы говорите my($seperator,...) в начале функции insert(), вы скрываете объявление our $seperator=; с начала файла. Удалите переменную $seperator из списка my($seperator...) и попробуйте снова запустить программу. - person Yaakov Belch; 09.07.2013
comment
Некоторые общие советы по отладке: Запустите программу с ключом -w (например, написав в первой строке программы #!/usr/bin/perl -w). Это напечатает полезные предупреждающие сообщения, например, об использовании неопределенных значений. Кроме того, я настоятельно рекомендую использовать «строгий режим»; в верхней части вашего файла. Это вызовет много ошибок на этапе компиляции. - person Yaakov Belch; 09.07.2013
comment
Это была моя глупая ошибка, я нашел ошибку, спасибо, Яаков! - person royskatt; 05.08.2013