sed скопировать подстроку в следующей строке

У меня есть файл .po. Мне нужно скопировать значение msgid в значение msgstr, если msgstr пуст.

Например

msgid "Hello"
msgstr ""

msgid "Dog"
msgstr "Cane"

Должен стать

msgid "Hello"
msgstr "Hello"

msgid "Dog"
msgstr "Cane"

В настоящее время, в целях тестирования, я работаю с другим файлом, но окончательный скрипт будет работать встроенным.

#!/bin/bash
rm it2.po
sed $'s/^msgid.*/&\\\n---&/' it.po > it2.po
sed -i '/^msgstr/d' it2.po
sed -i 's/^---msgid/msgstr/' it2.po

Этот скрипт имеет 2 проблемы (как минимум):

  1. копирует msgid в msgstr, даже если msgstr не пуст;
  2. Я почти уверен, что существует одна строка или более элегантное решение.

Любая помощь будет оценена по достоинству. Заранее спасибо.


person assistbss    schedule 01.06.2021    source источник
comment
Проблема не работает для строк длиннее 70 символов, вероятно, не воспроизводима. sed из коробки ничего подобного не делает, хотя некоторые очень старые реализации sed могут иметь максимальную длину строки (хотя, вероятно, значительно больше).   -  person tripleee    schedule 01.06.2021
comment
@tripleee вы правы, проблема не была связана с sed. Это произошло из-за команд xgettext, msginit и msgmerge, вызванных до sed. Вопрос был обновлен   -  person assistbss    schedule 01.06.2021
comment
Не беспокойтесь о final script will works inline - что -i в любой команде (sed, perl, ruby, gawk, что угодно) - это просто синтаксический сахар, который на самом деле не выполняет встроенное редактирование, он использует временный файл за кулисами. Вы можете так же легко сделать tmp=$(mktemp) && sed 's/old/new/' file > "$tmp" && mv "$tmp" file, если ваша sed или любая другая команда не имеет -i для редактирования псевдо-на месте.   -  person Ed Morton    schedule 01.06.2021


Ответы (7)


Вы можете рассмотреть лучший инструмент gnu awk вместо sed:

awk -i inplace -v FPAT='"[^"]*"|\\S+' '$id != "" && $1 == "msgstr" && (NF==1 || $2 == "\"\"") {$2=id} $1 == "msgid" {id=$2} 1' file

msgid "Hello"
msgstr "Hello"

msgid "Dog"
msgstr "Cane"

-v FPAT='"[^"]*"|\\S+' делает строку в кавычках или любое поле без пробелов отдельным полем.

Более читаемая форма:

awk -i inplace -v FPAT='"[^"]*"|\\S+' '
$id != "" && $1 == "msgstr" && (NF==1 || $2 == "\"\"") {$2=id}
$1 == "msgid" {id=$2}
1' file
person anubhava    schedule 01.06.2021

Это может сработать для вас (GNU sed):

sed -E 'N;s/(msgid "(.*)".*msgstr )""/\1"\2"/;P;D' file

Откройте двухстрочное окно и, если первая строка содержит msgid, а вторая msgstr "", замените значение msgstr значением msgid. Распечатайте/удалите первую строку и повторите.

person potong    schedule 01.06.2021

Только с GNU awk и показанными примерами мы могли бы попробовать следующее.

awk -v RS='"[^"]*"|\n+' '
RT=="\n"{ next }
$0~/^msgstr/{
  if(RT=="\"\""){ $0=$0 val }
  else          { $0=$0 RT  }
}
$0~/^msgid/     { val=RT
                  $0=$0 RT  }
RT
'  Input_file


2-е решение: Решение, немного отличающееся от приведенного выше, требует только 1 или 2 вхождений ", но это будет работать до тех пор, пока новая строка не появится из 1-го вхождения " в строке. тогда поможет следующее, снова написанное и протестированное с показанными образцами.

awk  -v RS='"[^\n]*|\n+' '
RT=="\n"{ next }
$0~/^msgstr/{
  if(RT=="\"\""){ $0=$0 val }
  else          { $0=$0 RT  }
}
$0~/^msgid/     { val=RT
                  $0=$0 RT  }
RT
'  Input_file

Объяснение: добавлено подробное объяснение вышеизложенного.

awk  -v RS='"[^"]*"|\n+' '    ##Starting awk program from here and setting record separator as " till " comes or new lines.
RT=="\n"{ next }              ##If RT is newline then take cursor to next line.
$0~/^msgstr/{                 ##Checking if line starts from msgstr then:
  if(RT=="\"\""){ $0=$0 val } ##Checking if RT us "" then add val to current line.
  else          { $0=$0 RT  } ##Else simply add RT.
}
$0~/^msgid/     { val=RT      ##Checking if line starts from msgid then make val to RT
                  $0=$0 RT  } ##Adding RT to $0.
RT                            ##Printing line if RT is not null.
' Input_file                  ##Mentioning Input_file name here.
person RavinderSingh13    schedule 01.06.2021

Поскольку структура входного файла настолько проста и последовательна, я думаю, что следующего должно быть достаточно (это работает с тремя приведенными вами примерами):

sed -zE 's/(msgid "([^"]+)"\nmsgstr ")"/\1\2"/g' your_file
  • -z превращает файл в длинную строку ввода со встроенными \n, поэтому нам не нужны такие команды, как N, D или другие, потому что весь файл уже находится в пространстве шаблонов;
  • -E позволяет нам использовать (, ) и + вместо \(, \) и \+ (а также другие подобные вещи)
  • самый внешний () захватывает msgid "Hello"\nmsgstr " (закрывающий " соответствует, но не захватывается);
  • самый внутренний () захватывает первую строку в двойных кавычках;
  • \1\2" объединяет совпадающий текст (кроме последнего ", как я отметил выше), с текстом между первыми двумя " и закрывающим ",
  • флаг g применит замену ко всему файлу.

Если начальные строки не так важны (например, они всегда одинаковы, а строки всегда отображаются как msgid, за которыми следует msgstr), вы можете немного сжать приведенную выше команду:

sed -zE 's/(([^"]+)"\n[^\n]*")"/\1\2"/g' your_file
person Enlico    schedule 01.06.2021

Вы можете использовать пространство ожидания:

sed '
    /^msgid[\t ]*/ {
        p
        s///
        x
        d
    }
    /^msgstr[\t ]*""/ {
        x
        s/^/msgstr /
    }
' <in.po >out.po
  • if line starts with msgid
    • print it
    • удалить ключевое слово
    • сохранить строку для хранения
    • перейти к следующей строке
  • else if lines starts with msgstr and has empty value
    • retrieve string from hold
    • добавьте ключевое слово
  • неявная печать
person jhnc    schedule 01.06.2021

Вот простой скрипт sed, который хранит последний msgid в области хранения (h), затем возвращает его (x) и изменяет его на msgstr, если видит пустой msgstr.

sed -e '/^msgid "/h' -e '/^msgstr ""/!b' \
    -e x -e 's/^msgid/msgstr/' it.po >it2.po

Обратите также внимание на то, как обычно вы комбинируете несколько операторов sed с -e, а не создаете новый файл, а затем повторно запускаете для него sed -i. sed — язык сценариев; изучите его, если вы хотите его использовать.

(Некоторые варианты sed не допускают такого расположения; возможно, объедините скрипт в одну строку с точками с запятой между операторами, если у вас возникли проблемы с этим.)

Сказав это, sed в значительной степени является языком только для записи. Возможно, вам будет лучше использовать простое решение Awk (или Python или т. д.).

awk '/^msgid "/ { s=$0; sub(/^msgid/, "", s) }
    /^msgstr ""/ { $0 = $1 s } 1' it.po >it2.po
person tripleee    schedule 01.06.2021
comment
или даже sed '/^msgid/h; /^msgstr ""/{x;s/id/str/}' - person jhnc; 01.06.2021
comment
Да, я стараюсь избегать фигурных скобок, потому что в разных диалектах есть немного разные правила их сочетания с другими утверждениями. Ваша формулировка дает мне плохой флаг в команде замены в macOS. Добавление точки с запятой перед } исправляет это, но... то, что я сказал. - person tripleee; 01.06.2021

Будьте проще и используйте awk, например. используя любой awk в любой оболочке на каждой машине Unix:

$ awk '$2~/""/{$2=p} {p=$2} 1' it.po
msgid "Hello"
msgstr "Hello"

msgid "Dog"
msgstr "Cane"

Если это не все, что вам нужно, отредактируйте свой вопрос, чтобы предоставить более полный пример ввода/вывода, включая случаи, для которых это не работает.

Поскольку у вас есть GNU sed для -i, у вас также есть или вы можете установить GNU awk для -i inplace, если хотите редактировать на месте, или просто выполните tmp=$(mktemp) && awk 'script' file > "$tmp" && mv "$tmp" file, как для любой другой команды.

person Ed Morton    schedule 01.06.2021