edição multilinha usando perl ou qualquer outra ferramenta

1

Estou editando meu arquivo .uncrustify.cfg para torná-lo mais legível. Eu só quero reformatar assim:

Duas linhas em:

# Add or remove between the parens in the function type: 'void (*x)(...)'
sp_after_tparen_close      = ignore   # ignore/add/remove/force

Uma linha:

sp_after_tparen_close      = ignore   # ignore/add/remove/force#   Add or remove between the parens in the function type: 'void (*x)(...)'

Perl parece ser o caminho a percorrer, mas a sintaxe me domina. Quando eu tiver uma década livre, eu vou aprender; -)

Para torná-lo um pouco mais genérico:

Duas linhas em:

#a comment line
some code

Uma linha:

some code # a comment line

==========================

João: Duas linhas feitas à mão:

nl_while_brace             = ignore   # I,A,R,F     # Add or remove newline between 'while' and '{'
nl_scope_brace             = ignore   # I,A,R,F     # Add or remove newline between 'scope (x)' and '{' (D)

... dois pares que não combinaram usando o seu awk:

# Add or remove newline between 'unittest' and '{' (D)
nl_unittest_brace          = ignore   # I,A,R,F

# Add or remove newline between 'version (x)' and '{' (D)
nl_version_brace           = ignore   # I,A,R,F
    
por Ray Andrews 20.10.2014 / 20:26

3 respostas

2
sed '/^#/N;s/\(.*\)\n\([^#].*\)/ /;P;D'

Isso tratará do seu exemplo simples na questão - qualquer linha comentada que seja seguida por uma linha que não seja um comentário e que contenha pelo menos um caractere será anexada à linha que a segue.

Portanto, execute o seu exemplo e a saída é:

sp_after_tparen_close      = ignore   # ignore/add/remove/force # Add or remove between the parens in the function type: 'void (*x)(...)'

Executando o exemplo do @ John1024 através dele e a saída é:

#
# Some comments
#

sp_after_tparen_close      = ignore   # ignore/add/remove/force # Add or remove between the parens in the function type: 'void (*x)(...)'

some code #a comment line
more code

# comment one
# comment two
still more code # comment three

Para lidar com condições como essas, sed não precisa de loop. Nesse caso, as únicas linhas que possivelmente podem conter um caractere \n ewline são aquelas que começam com% hash% porque essas são as únicas linhas às quais # adicionará uma.

Quando sed encontra uma linha que começa com um hash sed , ele puxa a linha de entrada # ext e a anexa ao espaço padrão. N , em seguida, tenta sed ubstitute:

  • s/// - o máximo possível referenciado como \(.*\) seguido imediatamente por ...
  • - um caractere de nova linha imediatamente seguido por ...
  • \n - pelo menos um caractere que não é um hash \([^#].*\) e tudo o que resta no espaço padrão ...
  • com # .

then sed rints padrão space até o primeiro caractere P ewline e \n eletes mesmos antes de recomeçar para tentar novamente com o que permanece (se houver) .

    
por 21.10.2014 / 00:42
1

Eu acredito que isso faz o que você quer:

awk '/^[[:space:]]*[^#]/ && last ~ /^#/ {printf "%s %s",$0,last; last="";next} {print last; last=$0} END{print last}' sample.cfg

Como exemplo, suponha que tenhamos este arquivo de entrada:

#
# Some comments
#

# Add or remove between the parens in the function type: 'void (*x)(...)'
sp_after_tparen_close      = ignore   # ignore/add/remove/force

#a comment line
some code
more code

# comment one
# comment two
# comment three
still more code

A saída é:

$ awk '/^[[:space:]]*[^#]/ && last ~ /^#/ {printf "%s %s",$0,last; last="";next} {print last; last=$0} END{print last}' uncrustify.cfg

#
# Some comments
#

sp_after_tparen_close      = ignore   # ignore/add/remove/force # Add or remove between the parens in the function type: 'void (*x)(...)'

some code #a comment line
more code

# comment one
# comment two
still more code # comment three

Como funciona

awk implicitamente percorre todas as linhas de um arquivo.

Este script usa uma única variável, last , que mantém a linha anterior. Em resumo, à medida que passamos por cada linha, se a última linha é um comentário e a linha atual não é, então imprimimos ambas as linhas juntas. Caso contrário, imprimimos a última linha.

  • /^[[:space:]]*[^#]/ && last ~ /^#/ {printf "%s %s",$0,last; last="";next}

    Se (a) esta linha não for um comentário, e (b) a linha anterior (última) for um comentário, combine a última e a atual linha e imprima-as. Depois disso, last para esvaziar. Em seguida, pule o restante dos comandos e pule para começar de novo na linha next .

  • {print last; last=$0}

    Caso contrário, imprima a linha last . Atualize last com o conteúdo da linha atual.

  • END{print last}

    Depois que terminarmos de percorrer todas as linhas do arquivo, imprima o conteúdo da linha last .

Outro exemplo

Considere esta entrada:

# Add or remove newline between 'unittest' and '{' (D)
nl_unittest_brace          = ignore   # I,A,R,F

# Add or remove newline between 'version (x)' and '{' (D)
nl_version_brace           = ignore   # I,A,R,F

A saída é:

$ awk '/^[[:space:]]*[^#]/ && last ~ /^#/ {printf "%s %s",$0,last; last="";next} {print last; last=$0} END{print last}' new

nl_unittest_brace          = ignore   # I,A,R,F # Add or remove newline between 'unittest' and '{' (D)

nl_version_brace           = ignore   # I,A,R,F # Add or remove newline between 'version (x)' and '{' (D)
    
por 20.10.2014 / 22:53
1

De acordo com o meu comentário, é mais fácil desenvolver algo assim em um roteiro curto, em vez de mexer com o comando one-liners. Além disso, você pode mantê-lo.

#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);

my $buffer = "";
my $linesBuffered = 0;

while (<STDIN>) {
# Check if this line is a just a comment.
    if ($_ =~ /^\s*#/) {
    # Assume multi-line comments should not be appended.
        if ($buffer) { $buffer .= $_ }
        else { $buffer = $_ }
        $linesBuffered++;
    } else {
        if ($linesBuffered > 1) {
        # Print multi-line comment.
            print "$buffer$_";
        # Reset buffer.
            $buffer = "";
            $linesBuffered = 0;
        } elsif ($buffer) {
        # Print buffered line with comment trailing.
            chomp $_;
            print "$_ $buffer";
        # Reset buffer.
            $buffer = "";
            $linesBuffered = 0;
        } else { print $_ }
    }
}

print $buffer if ($buffer);  

Isso pode ser usado, por exemplo, ./filter.pl < .uncrustify.cfg > .uncrustify.copy . Observe que isso não é feito no lugar, então você teria que cp .uncrustify.copy .uncrustify.cfg depois, se estiver feliz. Como ele lê a entrada padrão, você pode testá-lo:

> ./filter.pl
what  <- stdin
what  <- stdout

Aqui ele cita a linha imediatamente, já que não é um comentário. Eu não me importarei em indicar stdin e stdout nos próximos exemplos.

#okay
then
then #okay

Nesse caso, ele armazenou em buffer a linha de comentário e a anexou à próxima linha (sem comentários).

#foo
#bar
#foo
#bar

Neste caso, ele cita um comentário de várias linhas.

Alguns pontos:

  • Em perl, uma string vazia ( "" ) é testada como falsa.
  • /^\s*#/ corresponderá a uma linha que começa com zero ou mais espaço em branco e, em seguida, # .
  • A menos que chomp ed, as linhas de entrada terão uma nova linha no final.

Aqui está a saída do exemplo de John1024:

#
# Some comments
#

sp_after_tparen_close      = ignore   # ignore/add/remove/force # Add or remove between the parens in the function type: 'void (*x)(...)'

some code #a comment line
more code

# comment one
# comment two
# comment three
still more code

Observe onde # comment three está aqui em relação à saída de John1024 e mikeserv. Isso é intencional, mas o valor disso depende dos seus requisitos - aqui minha preferência seria assumir que a última linha de um comentário de várias linhas não deve ser anexada à próxima linha de código, ou seja, somente único line comentários seguidos por código são movidos. Conseguir isso acrescentou complexidade ao script; a variável $linesBuffered e a lógica relacionada a ela não seriam necessárias de outra forma.

    
por 20.10.2014 / 22:55

Tags