Insira nova linha antes de cada linha que corresponda a um padrão, a menos que a linha anterior já esteja vazia

5

Eu preciso adicionar uma nova linha antes de qualquer linha que contenha um padrão, onde podemos assumir que o padrão é sempre a primeira string da linha atual. Por exemplo

This is a
pattern
This is a
pattern

Eu posso adicionar uma nova linha com o comando sed

sed -i 's/pattern\+/\n&/g' file

para obter o resultado

This is a

pattern
This is a

pattern

Para evitar que novas linhas sejam adicionadas (no caso de execução múltipla), desejo verificar se a linha antes do padrão está vazia. Eu sei que posso fazer isso com

if [ "$line" == "" ]; then

Mas como determino a linha anterior, de um padrão de correspondência, em primeiro lugar?

EDIT: o padrão pode ocorrer várias vezes.

    
por Cram 14.11.2016 / 14:54

7 respostas

5

Você pode armazenar a linha anterior no espaço de armazenamento:

sed '
 /^pattern/{
   x
   /./ {
     x
     s/^/\
/
     x
   }
   x
 }
 h'

Seria mais legível com awk :

awk '!previous_empty && /pattern/ {print ""}
     {previous_empty = $0 == ""; print}'

Como as implementações GNU de sed tem uma opção -i para edição no local, a implementação GNU de awk as -i inplace para isso.

    
por 14.11.2016 / 15:36
3

But how do I determine the previous line, of a matching pattern, in the first place?

Umm ... talvez isso funcione.

Usando grep e a opção -B :

 -B num, --before-context=num
         Print num lines of leading context before each match.  See
         also the -A and -C options.

Considere este infil:

cat infile 
foo

bar
baz

Agora, se eu grep para bar , a linha anterior deve estar vazia:

if [[ $(grep -B 1 'bar' infile | head -1) == "" ]]; then echo "line is empty"; fi
line is empty

Ao contrário de usar grep on baz , onde a linha anterior é não vazia:

if [[ $(grep -B 1 'baz' infile | head -1) == "" ]]; then echo "line is empty"; fi
<no output>
    
por 14.11.2016 / 15:10
3

Outra maneira com sed :

sed -e 'tD' -e '$!N;/.\npattern/s/\n/&&/;:D' -e 'P;D' infile

Isso foi explicado em detalhes aqui : é basicamente um ciclo N;P;D , no qual contabilizamos as novas linhas que editamos em , cada vez que o script insere um \n ewline, ele executa apenas P e D sem N , de modo a ter sempre apenas duas linhas no espaço padrão.

    
por 14.11.2016 / 16:12
3

Eu sei que sua pergunta foi inicialmente sobre sed, mas há uma resposta bem simples no vim:

:g/.\npattern/norm o

Ou, se você preferir executar isso totalmente a partir da linha de comando:

vim file -c "g/.\npattern/norm o" -c "wq"

A forma como funciona, é que procura qualquer linha que corresponda à seguinte expressão regular:

.\npattern

que é qualquer linha não vazia seguida pelo seu padrão. Em seguida, para cada correspondência, aplica-se o seguinte comando norm o , que abre uma nova linha abaixo da localização atual do cursor.

    
por 14.11.2016 / 22:00
2

Se tivermos o gnused (o padrão no Linux e muitos outros, e disponível para todos)

sed -zri  's/([^\n]\n)(pattern)/\n/g' file

onde

  • ([^\n]\n)(pattern) o padrão depois de uma linha não vazia
  • -z "linhas" separadas pelo caractere nulo (slurp o arquivo)
  • -r para ter expressões regulares estendidas
por 14.11.2016 / 15:55
1

Usando ex , recursos especificados POSIX somente

printf '%s\n' 0a '' . 1d 'g/pattern/-put | -,.!uniq' x | ex file

Resumo rápido dos comandos passados para ex por printf :

0a - append after line 0
   - an empty line
.  - stop appending
1d - delete line 1 (the new empty line) into the unnamed register (a.k.a. buffer)

g/pattern/-put | -,.!uniq

g/pattern/ - for every line in the file matching "pattern"
- - on the *previous* line,
put - "put" (linewise append) the contents of the unnamed register
| - and also do the following (still part of the g// command)
-,. - take the previous and current lines
!uniq - and run them through the external command "uniq"
        (replacing the lines with the output)

x - save changes and exit

ex vale a pena aprender. :)

    
por 15.11.2016 / 01:13
0

Suponha que reformulamos os requisitos como este:

  • Temos um arquivo de parágrafos, que são regiões de linhas consecutivas não vazias, separadas por uma ou mais linhas vazias.
  • Sempre que "padrão" ocorre dentro de um parágrafo, gostaríamos que essa linha iniciasse um novo parágrafo, exceto quando "padrão" ocorre na primeira linha de um parágrafo.

  • Além disso, não nos importamos em normalizar o arquivo para que exista exatamente uma linha vazia entre os parágrafos, e nenhuma linha vazia no início ou no final. *

Se esses requisitos forem aceitáveis, podemos aproveitar o modo de parágrafo do Awk (ativado um valor vazio em RS ):

awk 'BEGIN { RS=""; FS="\n" }
           { print sep $1;
             for (i = 2; i <= NF; i++)
             { if ($i ~ /pat/) print ""; print $i }
             sep=FS }'

No modo de parágrafo, os registros são parágrafos. Como estamos usando \n como FS , os campos $1, $2, ... $NF correspondem a linhas de parágrafos. Por exemplo, se NF for 5, então estamos lidando com um parágrafo de cinco linhas. As novas linhas de separação de parágrafo são removidas e cada registro $0 contém apenas as novas linhas internas entre as linhas, e a divisão de campo é feita nelas.

Um parágrafo tem pelo menos uma linha, porque os parágrafos não podem estar vazios: um parágrafo vazio se parece com duas novas linhas consecutivas que fazem parte do mesmo parágrafo que separa a sequência.

Portanto, sem verificar se NF é pelo menos 1, apenas imprimimos a linha do primeiro parágrafo com print sep $1 . A primeira vez, sep está vazia, portanto, não tem importância; mas depois do primeiro parágrafo, definimos sep para uma nova linha, para que o próximo print sep $1 gere a separação de parágrafos.

Após imprimir a primeira linha, iteramos as linhas restantes, se houver, e as imprimimos. Aqui, nós verificamos se cada linha corresponde ao padrão. Se assim for, emitimos uma linha extra em branco antes de imprimi-lo.

    
por 18.11.2016 / 00:47