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

#bash #awk #sed #grep

#bash #awk #sed #grep

Вопрос:

У меня есть файл, который содержит узлы и ребра графика в виде троек. Каждая строка состоит из 3 троек, но иногда текст находится между ними или в конце:

 <samplenode> <sampleEdge> <samplenode>
<samplenode> sometimestheristextinbetween<sampleEdge> <samplenode> and sometimes more at the end
<samplenode> <samplereEdge> <samplenode>
  

Мне нужна команда, которая печатает только тройки и игнорирует промежуточный текст. Поэтому он должен содержать только промежуточные символы <>

Он может включать < и > или нет. Это не имеет значения, но оно должно быть разделено. Результат может выглядеть следующим образом:

 <samplenode> <sampleEdge> <samplenode>
<samplenode> <sampleEdge> <samplenode> 
<samplenode> <sampleEdge> <samplenode>
  

Я попробовал это с sed , удалив все между двумя шаблонами (все между > и < ), но это никогда не работало так, как я хотел.

У кого-нибудь есть решение для меня? Может быть, с помощью grep или awk ?

Приветствия

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

1. Можно ли оставлять пробел в конце строки?

2. Может ли < или > отображаться в любом другом контексте, например, в sometimestheristextinbetween тексте?

3. @PaulHodges да, все в порядке.

4. @EdMorton Нет, они не отображаются ни в каком другом контексте

Ответ №1:

Вот некоторые действия awk с разделителями, протестированные с использованием образца ввода.

 awk -v RS="<" -F">" '{printf $1 (NR%3==1? "n": " ")}' file

samplenode sampleEdge samplenode
samplenode sampleEdge samplenode
samplenode samplereEdge samplenode
  

Ответ №2:

Следующее выглядит достаточно:

 sed 's/[^>]*(<[^>]*>)[^<]*/1 /g'
  

Ответ №3:

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

 sed -E 's/^.*(<[^<>]*>).*(<[^<>]*>).*1.*$/1 2 1/' file
  

Сопоставьте шаблон с триплетом (где первый также является третьим) и замените только совпадения, разделенные пробелом.

Ответ №4:

Другой вариант sed .

 sed -E 's/>[^<]*(<*)/> 1/g'
  

Это соответствует тегу закрытия либо для открытия другого (или для конца строки), и заменяет на close , пробел и все, что соответствует тесту для другого открытия (поэтому пусто в EOL).

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

Если вы предпочитаете не использовать -E расширенное сопоставление с шаблоном, то

 sed 's/>[^<]*(<*)/> 1/g'
  

Если вам не нужен пробел в EOL, добавьте обрезку.

 sed -E 's/>[^<]*(<*)/> 1/g; s/ $//;'
  

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

1. Не-GNU может не допускать точки с запятой, но вы можете заменить их новыми строками.

Ответ №5:

Я бы выбрал следующий awk подход, не могли бы вы, пожалуйста, попробовать следующее, должно работать в любом виде awk .

 awk '
{
  val=""
  while(match($0,/<[^>]*/)){
    val=(val?val OFS:"")substr($0,RSTART,RLENGTH 1)
    $0=substr($0,RSTART RLENGTH 1)
  }
  print val
}' Input_file
  

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

 awk '                                                      ##Starting awk program from here.
{
  val=""                                                   ##Nullifying val here.
  while(match($0,/<[^>]*/)){                               ##Running whole loop and mentioning match inside it to match everything from < till very first occurence of > in current line.
    val=(val?val OFS:"")substr($0,RSTART,RLENGTH 1)        ##Creating val which has sub-string of matched part here.
    $0=substr($0,RSTART RLENGTH 1)                         ##Re-creating current line where already matched part is removed.
  }
  print val                                                ##Printing val here.
}' Input_file                                              ##Mentioning Input_file name here.
  

Ответ №6:

другой awk

 $ awk 'BEGIN {b="<";e=">";FS="["b e"]"} 
             {for(i=2;i<=NF;i =2) printf "%s ", b $i e; print ""}' file

<samplenode> <sampleEdge> <samplenode>
<samplenode> <sampleEdge> <samplenode>
<samplenode> <samplereEdge> <samplenode>
  

Ответ №7:

grep -o будут напечатаны только совпадающие части строки (по одному совпадению на строку вывода), а затем вы можете использовать paste для разбиения результата на три столбца. Это зависит от наличия ровно трех совпадений в строке.

 $ grep -o '<[^>]*>' file | paste - - -
<samplenode>        <sampleEdge>        <samplenode>
<samplenode>        <sampleEdge>        <samplenode>
<samplenode>        <samplereEdge>      <samplenode>
  

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

1. если вам нужен только один разделитель пробела, добавьте -s или -s' '

2. Это работает, но если между ними более 23 символов <> , это приведет к вырезанию всего после 23-го символа.

3. Спасибо, @SBKIT, это вообще не очень приятная функция pr для данного варианта использования. Обновлено для использования paste вместо этого.

Ответ №8:

С помощью GNU awk для FPAT и предполагая, что ни < ни > не появляется ни в каком другом контексте:

 $ awk -v FPAT='<[^>] >' '{$1=$1}1' file
<samplenode> <sampleEdge> <samplenode>
<samplenode> <sampleEdge> <samplenode>
<samplenode> <samplereEdge> <samplenode>