2

Я должен сделать некоторые замены во многих *.c файлах. Я хочу сделать замену так:
original: printf("This is a string! %d %d\n", 1, 2);
результат: print_record("This is a string! %d %d", 1, 2);
То есть замените « printf » на « print_record » и удалите завершающий « \n ».
Сначала я использую sed для выполнения этой задачи. Тем не менее, может быть, есть несколько таких случаев:

printf("This is a multiple string, that is very long"
 " and be separated into multiple lines. %d %d\n", 1, 2); 

В этом случае я не могу использовать sed чтобы легко удалить " \n ". Я слышал, что perl может делать эту работу хорошо. Но я новичок в perl . Так кто-нибудь может мне помочь? Как это сделать с помощью perl?
Спасибо большое!

1 ответ1

1

То, что вы хотите сделать, не тривиально. Требуется некоторый анализ, чтобы позаботиться о сбалансированных разделителях, кавычках и правиле C, чтобы смежные строковые литералы были объединены в один. К счастью, модуль Perl Text::Balanced обрабатывает многие из них (Text::Balanced доступен в стандартной библиотеке Perl). Следующий скрипт должен делать более или менее то, что вы хотите. Он принимает один аргумент командной строки и выводит на стандартный вывод. Вы должны будете обернуть его внутри сценария оболочки. Я использовал следующую обертку, чтобы проверить это:

#/bin/bash
find in/ -name '*.c' -exec sh -c 'in="$1"; out="out/${1#in/}"; perl script.pl "$in" > "$out"' _ {} \;
colordiff -ru expected/ out/

А вот и скрипт Perl. Я написал несколько комментариев, но не стесняйтесь спрашивать, если вам нужно больше объяснений.

use strict;
use warnings;
use File::Slurp 'read_file';
use Text::Balanced 'extract_bracketed', 'extract_delimited';

my $text = read_file(shift);

my $last = 0;
while ($text =~ /(          # store all matched text in $1
                  \bprintf  # start of literal word 'printf'
                  (\s*)     # optional whitespace, stored in $2
                  (?=\()    # lookahead for literal opening parenthesis
                 )/gx) {
    # after a successful match,
    #   1. pos($text) is on the character right behind the match (opening parenthesis)
    #   2. $1 contains the matched text (whole word 'printf' followed by optional
    #      whitespace, but not the opening parenthesis)
    #   3. $2 contains the (optional) whitespace

    # output up to, but not including, 'printf'
    print substr($text, $last, pos($text) - $last - length($1));
    print "print_record$2(";

    # extract and process argument
    my ($argument) = extract_bracketed($text, '()');
    process_argument($argument);

    # save current position
    $last = pos($text);
}

# output remainder of text
print substr($text, $last);

# process_argument() properly handles the situation of a format string
# consisting of adjacent string literals
sub process_argument {
    my $argument = shift;

    # skip opening parenthesis retained by extract_bracketed()
    $argument =~ /^\(/g;

    # scan for quoted strings
    my $saved;
    my $last = 0;
    while (1) {
        # extract quoted string
        my ($string, undef, $whitespace) = extract_delimited($argument, '"');
        last if !$string;       # quit if not found

        # as we still have strings remaining, the saved one wasn't the last and should
        # be output verbatim
        print $saved if $saved;
        $saved = $whitespace . $string;
        $last = pos($argument);
    }
    if ($saved) {
        $saved =~ s/\\n"$/"/;   # chop newline character sequence off last string
        print $saved;
    }

    # output remainder of argument
    print substr($argument, $last);
}

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .