sed: сохранить шаблон и изменить порядок строк

#regex #variables #sed #awk

#регулярное выражение #переменные #sed #awk

Вопрос:

Я не уверен, смогу ли я сделать это исключительно с помощью sed:

Я пытаюсь изменить порядок строк следующим образом

 GF:001,GF:00012,GF:01223<TAB>XXR
GF:001,GF:00012,GF:01223,GF:0666<TAB>XXXR3
  

Для

 GF:001<TAB>XXR
GF:00012<TAB>XXR
GF:01223<TAB>XXR
GF:001<TAB>XXXR3
GF:00012<TAB>XXXR3
GF:01223<TAB>XXXR3
GF:0666<TAB>XXXR3
  

У кого-нибудь есть какие-нибудь подсказки? Мощность GF: XXXX меняется в зависимости от длины GF: XXXX.

Я застрял с sed -n '
'/(XX.*)$/' {
s/,/t1n/
}' input
, но я не могу ссылаться на первоначально подобранный шаблон в первую очередь. есть идеи? приветствия!

Обновление: Я думаю, что это невозможно сделать, просто используя sed. Итак, я использовал perl для этого:

 perl -e 'open(IN, "< file");
while (<IN>) {
    @a = split(/t/);
    @gos = split(/,/, $a[0]);
    foreach (@gos) {
      print $_."t".$a[1];
    }
close( IN );' > output
  

Но если кто-нибудь знает способ решить это просто с помощью sed , пожалуйста, опубликуйте его здесь…

Ответ №1:

Это можно сделать в sed , хотя я, вероятно, использовал бы Perl (или Awk, или Python) для этого.

Я не претендую на элегантность этого решения, но грубая сила и невежество иногда окупаются. Я создал файл с неоригинальным названием sed.script , содержащий:

 /(GF:[0-9]*),(.*)<TAB>(.*)/{
:redo
s/(GF:[0-9]*),(.*)<TAB>(.*)/1<TAB>3@@@@@2<TAB>3/
h
s/@@@@@.*//
p
x
s/.*@@@@@//
t redo
d
}
  

Я запустил его как:

 sed -f sed.script input
  

где input содержались две строки, показанные в вопросе. Это привело к выводу:

 GF:001<TAB>XXR
GF:00012<TAB>XXR
GF:01223<TAB>XXR
GF:001<TAB>XXXR3
GF:00012<TAB>XXXR3
GF:01223<TAB>XXXR3
GF:0666<TAB>XXXR3
  

(Я взял на себя смелость намеренно неверно истолковать <TAB> как последовательность из 5 символов вместо одного символа табуляции; вы можете легко исправить ответ, чтобы вместо этого обрабатывать фактический символ табуляции.)

Объяснение sed скрипта:

  • Найдите строки с более чем одним вхождением GF:nnn , разделенные запятыми (нам не нужно обрабатывать строки, содержащие одно такое вхождение). Выполняйте остальную часть скрипта только с такими строками. Все остальное передается (печатается) без изменений.
  • Создайте метку, чтобы мы могли вернуться к ней
  • Разделите строку на 3 запоминаемые части. Первая часть — это исходная информация GF; вторая часть — любая другая информация GF; третья часть — поле после <TAB> . Замените это на первое поле, <TAB> , третье поле, неправдоподобный шаблон маркера ( @@@@@ ), второе поле, <TAB> , третье поле.
  • Скопируйте измененную строку в пространство удержания.
  • Удалите шаблон маркера до конца.
  • С принтами.
  • Замените пространство удержания на пространство шаблона.
  • Удалите все, вплоть до шаблона маркера.
  • Если мы выполнили какую-либо работу, вернитесь к redo метке.
  • Удалите то, что осталось (оно уже было напечатано).
  • Конец блока скрипта.

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

Комментарии:

1. действительно впечатляет! я думал, что sed недостаточно мощный для выполнения циклов, но если у вас есть конструктор GOTO, вы можете имитировать цикл. спасибо за доказательство, Джонатан!

2. Ах да, востребованная конструкция GOTO — мечта инженеров-программистов :-).

Ответ №2:

Вы можете сделать это прямолинейно с помощью awk:

 $ awk '{gsub(/,/, "t" $NF "n");print}' input 
  

В этом случае мы просто заменяем запятую символом табуляции, объединенным с последним полем ( NF хранит количество полей записи; $NF возвращает NF -е поле), объединенным с новой строкой. Затем выведите результат.

Это тоже можно решить с помощью sed, похожим способом, но, ИМХО, немного лучше, чем решение Jonathan (которое, должен заметить, довольно сложное).

 sed -n '
:BEGIN
 h
 s/,.*<TAB>/<TAB>/
 p
 x
 s/^[^,]*,//
t BEGIN' input
  

Здесь мы определяем метку в начале скрипта:

 :BEGIN
  

Затем мы копируем содержимое пространства шаблонов в пространство хранения:

 h
  

Теперь мы заменяем все, начиная с первой запятой и заканчивая табуляцией, только табуляцией:

  s/,.*<TAB>/<TAB>/
  

Мы печатаем результат…

 p
  

… и извлеките содержимое области удержания:

 x
  

Поскольку мы напечатали первую строку, которая содержит первый GF:XXX шаблон, за которым следует последний XXR шаблон, мы удаляем первый GF:XXX шаблон из строки:

  s/^[^,]*,//
  

Если выполняется замена, мы переходим к началу скрипта:

 t BEGIN
  

И все снова применяется к той же строке, за исключением того, что теперь в этой строке больше нет первого GF:XXX шаблона. OTOH, если замена не произведена, то обработка текущей строки завершена, и мы больше не переходим к началу.

Комментарии:

1. решение awk было быстрым, real 0m6.496s user 0m1.555s sys 0m0.109s решение sed было медленнее real 0m27.177s user 0m23.080s sys 0m0.129s для файла в 28 тысяч строк

2. На самом деле это имеет большой смысл, поскольку sed должен выполнять итерации по каждому экземпляру шаблона в строке. Я опубликовал решение sed, потому что оно было в спецификации, но, вероятно, это не лучшее решение для данного случая. В любом случае, я думаю, что решение awk на самом деле лучше, но я нашел эту проблему отличным упражнением sed 🙂

Ответ №3:

Если вам не нужен sed, awk хорош в этом:

 awk -F't|,' '{ i=1; do { printf("%st%sn",$i,$NF); i  ;}  while ( i<NF ); }' inputfile
  

Комментарии:

1. Спасибо! я сделал это с помощью perl … хотя стоит взглянуть на awk .

Ответ №4:

Ну, мне потребовалось 3 часа, чтобы сделать это

sed -re ':a; s/(GF:[0-9]*[^,]*),([^<]*)(<TAB>[A-Z]*)/13n23/g;ta; ' file.txt

Комментарии:

1. @EdMorton это потому, что у тебя за плечами было 30 лет, а у меня было 3 дня

2. Нет, это потому, что awk имеет четкий и простой синтаксис, в то время как sed для чего-либо более сложного, чем простые замены в одной строке, требуются Розеттский камень, 3 мудреца и кольцо-декодер Бэтмена.

3. @EdMorton, мне действительно нужно принять решение. Согласно вашему совету, могу ли я выполнить 90% задач с помощью awk в одиночку. Я действительно хочу использовать только один из них, чтобы я мог перейти к деталям, но не могу решить, какой именно. Если вы говорите, что 90% задач можно выполнить с помощью awk, то я соглашусь с этим

4. Вы можете выполнить 100% задач по обработке текста только в awk. однако grep, sed и т.д. Можно использовать немного быстрее / проще для небольших задач. Большинство сложных действий, которые вы можете выполнить в awk, вы также можете выполнить в sed, но результирующий awk будет понятным, простым, быстрым в написании и простым в обслуживании, в то время как на написание эквивалентного sed потребуется на порядки больше времени, и потребуется полная перезапись даже для самого незначительного изменения требований. Изучите awk — материал, для которого вы ДОЛЖНЫ использовать sed, настолько прост, что вам не нужно прилагать никаких усилий для его изучения, вы просто узнаете это из пары примеров.

5. Я только что добавил объяснение моего awk-скрипта для вашей пользы. Обратите внимание, что все, что я делаю, это объясняю пару наиболее фундаментальных концепций awk, и из этого я ожидаю, что вы сможете понять сценарий. Сравните это со сложностью и спецификой объяснений сценариев sed на этой странице и серьезно представьте, что вы пытаетесь построить или отладить как awk, так и sed-скрипты. Примечание: Я использую sed почти каждый день, так что не думайте, что я против sed, это отличный инструмент для того, в чем он хорош, а именно для простых замен в одной строке.

Ответ №5:

 awk -F'[,t]' '{for (i=1;i<NF;i  ) print $i"t"$NF}' file
  

Awk считывает по одной строке за раз (по умолчанию) и разбивает строку на поля. Я использую -F, чтобы указать awk разделить строку на поля через каждую запятую или табуляцию. NF — это количество полей в строке, $i — содержимое поля с номером i.