Filtrar ou canalizar determinadas seções de um arquivo


14

Eu tenho um arquivo de entrada com algumas seções que são demarcadas com tags de início e fim, por exemplo:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Desejo aplicar uma transformação a esse arquivo para que as linhas X, Y, Z sejam filtradas por algum comando ( nlpor exemplo), mas o restante das linhas passe inalterado. Observe que nl(número de linhas) acumula estado entre linhas, portanto, não é uma transformação estática que está sendo aplicada a cada uma das linhas X, Y, Z. ( Edit : foi apontado que nlpode funcionar em um modo que não requer estado acumulado, mas estou apenas usando nlcomo exemplo para simplificar a pergunta. Na realidade, o comando é um script personalizado mais complexo. O que estou realmente procurando for é uma solução genérica para o problema de aplicar um filtro padrão a uma subseção de um arquivo de entrada )

A saída deve se parecer com:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

Pode haver várias seções no arquivo que requerem a transformação.

Atualização 2 Não especifiquei originalmente o que deveria acontecer se houver mais uma seção, por exemplo:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

Minha expectativa é que esse estado precise ser mantido apenas dentro de uma determinada seção, fornecendo:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

mas acho que interpretar o problema como exigindo que o estado seja mantido entre seções é válido e útil em muitos contextos.

Finalizar atualização 2

Meu primeiro pensamento é construir uma máquina de estado simples que rastreie em que seção estamos:

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

Com o qual eu corro:

cat test-inline-codify | ./inline-codify

Isso não funciona, pois cada chamada para nlé independente, portanto, os números de linha não aumentam:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

Minha próxima tentativa foi usar um fifo:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

Isso fornece a saída correta, mas na ordem errada:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

Provavelmente há algum cache em andamento.

Estou fazendo tudo errado? Parece um problema bastante genérico. Eu sinto que deveria haver um pipeline simples que resolvesse isso.


nlnão precisa acumular estado . Veja nl -de verifique suas man/ infopáginas para obter informações sobre nlo delimitador de seção .
mikeserv

nl é apenas um exemplo. No meu caso, estou executando um script personalizado em vez de nl.
James Scriven

Nesse caso, esclareça o que seu script está fazendo.
terdon

Esclarei na pergunta que estou usando apenas nlcomo exemplo de filtro. Eu pensei que isso simplificaria a pergunta, encobrindo os detalhes do que exatamente o filtro estava fazendo, mas provavelmente eu apenas causei mais confusão. Na verdade, estou filtrando a subseção por meio de um marcador de código, para um gerador de blog estático caseiro. No momento, estou usando o gnu source-highlight, mas isso pode mudar e posso adicionar mais filtros, como um formatador.
James Scriven

Respostas:


7

Eu concordo com você - provavelmente é um problema genérico. Alguns utilitários comuns têm algumas facilidades para lidar com isso, no entanto.


nl

nl, por exemplo, separa a entrada em páginas lógicas, como -delimitado por um delimitador de seção de dois caracteres . Três ocorrências em uma linha sozinhas indicam o início de um cabeçalho , duas no corpo e uma no rodapé . Ele substitui qualquer um dos encontrados na entrada por uma linha em branco na saída - que são as únicas linhas em branco que já imprime

Alterei seu exemplo para incluir outra seção e inseri-la ./infile. Então fica assim:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Então eu executei o seguinte:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlpode ser dito para acumular o estado nas páginas lógicas, mas isso não ocorre por padrão. Em vez disso, numerará as linhas de sua entrada de acordo com os estilos e por seção . Então, -hasignifica o número todos os cabeçalho linhas e -bnsignifica há linhas do corpo - como ele começa em um corpo estado.

Até eu aprender isso, costumava usar nlpara qualquer entrada, mas depois de perceber que isso nlpoderia distorcer a saída de acordo com seu -delimitador padrão \:, aprendi a ter mais cuidado com ele e comecei a usar grep -nF ''entradas não testadas. Mas outra lição aprendida naquele dia foi que nlpode ser aplicada de maneira muito útil em outros aspectos - como este - se você apenas modificar um pouco sua entrada - como eu faço sedacima.

RESULTADO

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Aqui está um pouco mais sobre nl- você notou acima como todas as linhas, exceto as numeradas, começam com espaços? Ao nlnumerar linhas, ele insere um certo número de caracteres na cabeça de cada um. Para essas linhas, ele não é numerado - nem mesmo em branco - ele sempre corresponde ao recuo, inserindo ( -width count + -separator len) * espaços no início das linhas não numeradas. Isso permite reproduzir o conteúdo não numerado exatamente comparando-o com o conteúdo numerado - e com pouco esforço. Quando você considera que nldividirá sua entrada em seções lógicas para você e que pode inserir -ssequências arbitrárias no início de cada linha numerada, fica muito fácil lidar com sua saída:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

As impressões acima ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

GNU sed

Se nlnão é o seu aplicativo de destino, um GNU sedpode eexecutar um comando shell arbitrário para você, dependendo de uma correspondência.

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Acima sedcoleta a entrada no espaço do padrão até que tenha o suficiente para passar com êxito a substituição Test e parar a bcriação de gado de volta ao :label. Quando isso ocorre, ele eexecuta nlcom a entrada representada como um <<documento aqui para todo o restante de seu espaço de padrão.

O fluxo de trabalho é assim:

  1. /^@@.*start$/!b
    • Se uma ^linha inteira $que !não /coincidir com /o padrão acima, então é bcriados em rancho fora do script e autoprinted - por isso a partir deste ponto estamos apenas trabalhando com uma série de linhas que começou com o padrão.
  2. s//nl <<\\@@/
    • o s//campo vazio /representa o último endereço sedtentado corresponder - portanto, este comando substitui a @@.*startlinha inteira nl <<\\@@.
  3. :l;N
    • O :comando define um rótulo de filial - aqui eu defino um chamado :label. O Ncomando ext anexa a próxima linha de entrada ao espaço do padrão seguido por um \ncaractere ewline. Essa é uma das poucas maneiras de obter uma linha de \new em um sedespaço de padrão - o \ncaractere de ewline é um delimitador seguro para um sedder que faz isso há algum tempo.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • essa s///ubstituição só pode ser bem-sucedida depois que uma partida é encontrada e somente na primeira ocorrência seguinte de uma linha final . Ele atuará apenas em um espaço de padrão no qual a linha de \new final será imediatamente seguida pela @@.*endmarcação do final $do espaço de padrão. Quando ele age, ele substitui toda a cadeia correspondente pelo \1primeiro \(grupo \), ou \n@@.
  5. Tl
    • o Tcomando est ramifica para um rótulo (se fornecido) se uma substituição bem-sucedida não ocorreu desde a última vez que uma linha de entrada foi puxada para o espaço do padrão (como eu faço N) . Isso significa que toda vez que uma linha de \new é anexada ao espaço do padrão que não corresponde ao seu delimitador final, o Tcomando est falha e se ramifica de volta ao :label, o que resulta em sedpuxar a Nlinha ext e girar até obter êxito.
  6. e

    • Quando a substituição para o jogo final é bem sucedido e o script não suporta a filial de uma falha Test, sedvai execute um comando que looks como este:

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Você pode ver isso editando a última linha lá para parecer Tl;l;e.

Imprime:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Uma última maneira de fazer isso, e talvez a maneira mais simples, é usar um while readloop, mas por um bom motivo. A concha - (principalmente uma bashconcha) - normalmente é bastante abismal ao lidar com entradas em grandes quantidades ou em fluxos constantes. Isso também faz sentido - o trabalho do shell é manipular caracteres de entrada por caractere e chamar outros comandos que possam lidar com coisas maiores.

Mas, o mais importante é que, em relação ao seu papel, o shell não deve read sobrecarregar muito a entrada - ele é especificado para não armazenar em buffer a entrada ou a saída a ponto de consumir tanto ou não retransmitir o suficiente a tempo de que os comandos que ele chama sejam deixados em falta. - para o byte. Portanto, readé um excelente teste de entrada - para returninformações sobre se há entrada restante e você deve chamar o próximo comando para lê-la - mas, de outra forma, geralmente não é o melhor caminho a percorrer.

Aqui está um exemplo, no entanto, de como alguém pode usar read e outros comandos para processar a entrada em sincronia:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

A primeira coisa que acontece para cada iteração é readpuxar uma linha. Se for bem-sucedido, significa que o loop ainda não atingiu o EOF e, portanto, no casecorrespondente ao delimitador de início, o dobloco é executado imediatamente. Else, printfimprime a $lineele reade sedé chamado.

sedvai print cada linha até encontrar o início marcador - quando qUITS entrada inteiramente. O -uswitch nbuffered é necessário para o GNU sedporque ele pode armazenar um buffer com avidez, mas - de acordo com a especificação - outros POSIX seds devem funcionar sem nenhuma consideração especial - desde que <infileseja um arquivo comum.

Quando o primeiro sed qsai, o shell executa o dobloco do loop - que chama outro sedque imprime todas as linhas até encontrar o marcador final . Ele canaliza sua saída para paste, porque imprime números de linhas cada um em sua própria linha. Como isso:

1
line M
2
line N
3
line O

pasteem seguida, cola os :caracteres nos caracteres, e toda a saída se parece com:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

Estes são apenas exemplos - tudo poderia ser feito nos blocos de teste ou de execução aqui, mas o primeiro utilitário não deve consumir muita entrada.

Todos os utilitários envolvidos leram a mesma entrada - e imprimiram seus resultados - cada um por sua vez. Esse tipo de coisa pode ser difícil de pegar o jeito - porque diferentes utilitários vai tamponar mais do que outros - mas você pode geralmente dependem de dd, heade sedpara fazer a coisa certa (embora, para o GNU sed, você precisa do interruptor cli) e você sempre deve poder confiar read- porque é, por natureza, muito lento . E é por isso que o loop acima chama apenas uma vez por bloco de entrada.


Testei o segundo sedexemplo que você deu, e ele funciona, mas estou REALMENTE tendo problemas para entender a sintaxe. (meu sed é muito fraco e é geralmente limitado a s / findthis / replacethis / g eu vou ter que fazer um esforço para sentar e realmente entender sed..)
James Scriven

@ JamesScriven - Acabei de editar para explicar melhor. Deixe-me saber se isso não ajuda. Também mudei muito o comando - agora está em pedaços menores e mais sensíveis.
mikeserv

4

Uma possibilidade é fazer isso com o editor de texto do vim. Ele pode canalizar seções arbitrárias através de comandos do shell.

Uma maneira de fazer isso é por números de linha, usando :4,6!nl. Este comando ex será executado nl nas linhas 4-6 inclusive, alcançando o que você deseja na sua entrada de exemplo.

Outra maneira mais interativa é selecionar as linhas apropriadas usando o modo de seleção de linha (shift-V) e as teclas de seta ou pesquisar e, em seguida, usando :!nl. Uma sequência de comandos completa para sua entrada de exemplo pode ser

/@@inline-code-start
jV/@@inline-code-end
k:!nl

Isso não é muito adequado para automação (respostas usando, por exemplo, sed são melhores para isso), mas para edições pontuais é muito útil não ter que recorrer a shellscripts de 20 linhas.

Se você não conhece o vi (m), deve saber pelo menos que após essas alterações você pode salvar o arquivo usando :wq.


Sim, o vim é incrível! Mas, neste caso, estou procurando uma solução que possa ser scriptada.
James Scriven

@JamesScriven, qualquer pessoa que diga que o vim não é passível de script é insuficientemente determinada. Primeiro, crie um diretório de projeto e, nesse diretório, copie todos os arquivos de inicialização do vim do diretório inicial (ln -s funciona bem, exceto o .vimrc, que estamos prestes a modificar, e o .viminfo, que pode estar cheio de ruídos). Adicione a definição de função que fará o trabalho ao novo arquivo .vimrc e chame o vim como HOME=$(pwd) vim -c 'call Mf()' f. Se você estiver usando o xargs, poderá usar o gvim em um xserver dedicado para não danificar o seu tty (o vnc é independente da placa de vídeo e pode ser monitorado).
Hildred 10/04/2015

@hildred Hmmm ... Eu não poderia simplesmente usar [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) para simular cliques do mouse para vim?
21715 James Stiven

2

A correção mais simples que consigo pensar é não usar, nlmas conte as linhas você mesmo:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Em seguida, você o executa no arquivo:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

Obrigado, Terdon. Atualizei a pergunta para esclarecer que estou procurando uma solução genérica para filtrar uma subseção de uma entrada, e não o exemplo específico de linhas de numeração. talvez um melhor exemplo de comando teria sido "tac" (linhas reversas)
James Scriven

2

Se seu objetivo é enviar o bloco de código inteiro para uma única instância do processo, você poderá acumular as linhas e adiar a tubulação até chegar ao final do bloco de código:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Isso produz o seguinte para um arquivo de entrada que repete o caso de teste três vezes:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

Para fazer outra coisa com o bloco de código, por exemplo, reverter e, em seguida, número, apenas canalizá-lo através de outra coisa: echo -E "${acc:1}" | tac | nl. Resultado:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

Ou número de palavras echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

Editar adicionou uma opção para definir um filtro fornecido pelo usuário

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

Por padrão, o filtro é "nl". Para alterar o filtro, use a opção "-p" com algum comando fornecido pelo usuário:

codify -p="wc" file

ou

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Este último filtro produzirá:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

Atualização 1 O uso do IPC :: Open2 tem problemas de dimensionamento: se o tamanho do buffer for excedido, ele poderá bloquear. (na minha máquina, o tamanho do buffer do tubo se 64K corresponder a 10_000 x "linha Y").

Se precisarmos de coisas maiores (é preciso mais da 10000 "linha Y"):

(1) instalar e usar use Forks::Super 'open2';

(2) ou substitua a função pipeit por:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

Isso é muito legal. Eu acho que os truques são que você não está processando linha por linha (redefinindo $/e ssinalizando) e o uso do esinalizador para fazer a chamada real ao comando externo. Eu realmente gosto do segundo exemplo (arte ascii)!
James Scriven

No entanto, notei que isso não parece ultrapassar alguns milhares de linhas na subseção. Suspeito que isso esteja relacionado ao tratamento da subseção como um grande bloco de texto.
precisa

Obrigado. Sim: `/ e` = eval; /s= ("." significa (.|\n)); $/redefine o separador de registro.
JJoao

@ JamesScriven, você está certo (o cano está bloqueando). Deixe-me testar o que está acontecendo ...
JJoao

@JamesScriven, consulte o meu update ...
JJoao

1

Esse é um trabalho para o awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Quando o script vê o marcador de início, ele observa que deve começar a ser canalizado nl. Quando a pipevariável é verdadeira (diferente de zero), a saída é canalizada para o nlcomando; quando a variável é falsa (não definida ou zero), a saída é impressa diretamente. O comando canalizado é bifurcado na primeira vez que a construção de canal é encontrada para cada sequência de comandos. Avaliações subsequentes do operador do tubo com a mesma coluna reutilizam o tubo existente; um valor de sequência diferente criaria um canal diferente. A closefunção fecha o canal para a sequência de comandos fornecida.


Essa é essencialmente a mesma lógica do seu script de shell usando um pipe nomeado, mas muito mais fácil de explicar, e a lógica fechada feita corretamente. Você precisa fechar o pipe no momento certo, para fazer o nlcomando sair, liberando seus buffers. Seu script realmente fecha o canal muito cedo: o canal é fechado assim que o primeiro echo $line >myfifotermina a execução. No entanto, o nlcomando só vê o final do arquivo se obtiver um intervalo de tempo antes da próxima vez que o script for executado echo $line >myfifo. Se você tiver um grande volume de dados ou se adicionar sleep 1após a gravação myfifo, verá que nlapenas processa a primeira linha ou o primeiro grupo rápido de linhas, e ele sai porque viu o final de sua entrada.

Usando sua estrutura, você precisaria manter o tubo aberto até não precisar mais dele. Você precisa ter um único redirecionamento de saída no canal.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(Eu também aproveitei a oportunidade para adicionar citações corretas e coisas do tipo - consulte Por que meu script de shell engasga com espaços em branco ou outros caracteres especiais? )

Se você estiver fazendo isso, poderá usar um pipeline em vez de um pipe nomeado.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

sua solução awk é realmente legal! Penso que essa é de longe a solução mais concisa (mas muito legível). O comportamento do awk de reutilizar o tubo para nl é garantido ou pode decidir: "ei, você já canalizou o suficiente por enquanto ... vou fechar esse canal e abrir um novo" ?. Sua solução "pipeline" também é muito boa. Originalmente, desconsiderei uma abordagem com loops incorporados, pois achei que poderia ser um pouco confuso, mas acho que o que você tem é ótimo. Falta um ponto e vírgula antes da do. (Eu não tenho o representante aqui para fazer uma pequena edição.)
James Scriven

1
... Não consegui que sua solução de tubulação nomeada funcionasse. Parece haver uma condição de corrida, de modo que a seção canalizada para nl às vezes se perde completamente. Além disso, se houver uma segunda seção @@ inline-code-start / end, ela sempre será perdida.
precisa

0

OK, primeiro fora; Entendo que você não está procurando uma maneira de numerar as linhas nas seções do seu arquivo. Como você não deu um exemplo real do que pode ser seu filtro (exceto nl), vamos supor que seja

tr "[[:lower:]]" "[[:upper:]]"

ou seja, converter texto em maiúsculas; então, para uma entrada de

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

você quer uma saída de

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Aqui está minha primeira aproximação de uma solução:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

onde os espaços antes das @@cadeias e perto do final da última linha são tabulações. Observe que estou usando nl para meus próprios fins . (É claro que estou fazendo isso para resolver seu problema, mas não para fornecer uma saída numerada de linha.)

Isso numera as linhas da entrada para que possamos separá-la nos marcadores de seção e saber como montá-la novamente mais tarde. O corpo principal do loop é baseado em sua primeira tentativa, levando em consideração o fato de que os marcadores de seção têm números de linhas. Ele divide a entrada em dois arquivos: file0(inativo; não em uma seção) e file1(ativo; em uma seção). É assim que eles se parecem com a entrada acima:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

Então nós corremos file1 (que é a concatenação de todas as linhas de seção) pelo filtro de capitalização; combine isso com as linhas fora de seção não filtradas; ordenar, colocá-los de volta na ordem original; e depois retire os números das linhas. Isso produz a saída mostrada perto do topo da minha resposta.

Isso pressupõe que seu filtro deixa os números de linha em paz. Se não (por exemplo, se inserir ou excluir caracteres no início da linha), acredito que essa abordagem geral ainda possa ser usada, mas exigirá uma codificação um pouco mais complicada.


nljá faz a maior parte do trabalho lá - é para isso que -dserve a opção elimitador.
mikeserv

0

Um script de shell que usa sed para gerar pedaços de linhas não demarcadas e alimentar pedaços de linhas demarcados em um programa de filtro:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

Eu escrevi este script em um detagger.sh arquivo chamado e usou-o assim: ./detagger.sh infile.txt. Criei um arquivo filter.sh separado para imitar a funcionalidade de filtragem na pergunta:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

Mas a operação de filtragem pode ser alterada no código.

Tentei seguir a idéia de uma solução genérica para que operações como linhas de numeração não exijam contagem adicional / interna. O script faz uma verificação rudimentar para ver se as tags demarcadoras estão em pares e não manipula as tags aninhadas normalmente.


-1

Obrigado por todas as grandes ideias. Eu criei minha própria solução, acompanhando a subseção em um arquivo temporário e canalizando tudo de uma vez para o meu comando externo. Isso é muito parecido com o que o Supr sugeriu (mas com uma variável de shell em vez do arquivo temporário). Além disso, eu realmente gosto da idéia de usar o sed, mas a sintaxe para este caso parece um pouco exagerada para mim.

Minha solução:

(Eu uso nlapenas como exemplo de filtro)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

Eu preferiria não ter que lidar com o gerenciamento de arquivos temporários, mas entendo que as variáveis ​​do shell podem ter limites de tamanho bastante baixos e não conheço nenhuma construção bash que funcione como um arquivo temporário, mas desaparece automaticamente quando o arquivo processo termina.


Achei que você queria ser capaz de “estado acumulam em todas as linhas”, de modo que, por exemplo, usando dados de teste do microfone, linhas M, Ne Oseriam numerados 4,5 e 6. Isso não faz isso. Minha resposta é sim (além do fato de que, em sua encarnação atual, ela não funciona nlcomo filtro). Se esta resposta está lhe dando a saída desejada, o que você quis dizer com "acumular estado nas linhas"? Você quis dizer que queria preservar o estado apenas em cada seção, mas não entre as seções? (Por que não colocar um exemplo multi-seção em sua pergunta?)
Scott

@ Scott - use nl -ppara obter M,N,O==4,5,6.
mikeserv

Atualizei a pergunta para esclarecer que só estou interessado em manter o estado dentro da subseção, embora eu ache a outra interpretação igualmente interessante.
precisa
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.