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 ( nl
por 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 nl
pode funcionar em um modo que não requer estado acumulado, mas estou apenas usando nl
como 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.
fonte
nl
não precisa acumular estado . Vejanl -d
e verifique suasman
/info
páginas para obter informações sobrenl
o delimitador de seção .nl
como 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 gnusource-highlight
, mas isso pode mudar e posso adicionar mais filtros, como um formatador.Respostas:
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-d
elimitado 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á imprimeAlterei seu exemplo para incluir outra seção e inseri-la
./infile
. Então fica assim:Então eu executei o seguinte:
nl
pode 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,-ha
significa o número todos os cabeçalho linhas e-bn
significa há linhas do corpo - como ele começa em um corpo estado.Até eu aprender isso, costumava usar
nl
para qualquer entrada, mas depois de perceber que issonl
poderia distorcer a saída de acordo com seu-d
elimitador padrão\:
, aprendi a ter mais cuidado com ele e comecei a usargrep -nF ''
entradas não testadas. Mas outra lição aprendida naquele dia foi quenl
pode ser aplicada de maneira muito útil em outros aspectos - como este - se você apenas modificar um pouco sua entrada - como eu façosed
acima.RESULTADO
Aqui está um pouco mais sobre
nl
- você notou acima como todas as linhas, exceto as numeradas, começam com espaços? Aonl
numerar 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 (-w
idth count +-s
eparator 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 quenl
dividirá sua entrada em seções lógicas para você e que pode inserir-s
sequências arbitrárias no início de cada linha numerada, fica muito fácil lidar com sua saída:As impressões acima ...
GNU
sed
Se
nl
não é o seu aplicativo de destino, um GNUsed
podee
executar um comando shell arbitrário para você, dependendo de uma correspondência.Acima
sed
coleta a entrada no espaço do padrão até que tenha o suficiente para passar com êxito a substituiçãoT
est e parar ab
criação de gado de volta ao:l
abel. Quando isso ocorre, elee
executanl
com a entrada representada como um<<
documento aqui para todo o restante de seu espaço de padrão.O fluxo de trabalho é assim:
/^@@.*start$/!b
^
linha inteira$
que!
não/
coincidir com/
o padrão acima, então éb
criados 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.s//nl <<\\@@/
s//
campo vazio/
representa o último endereçosed
tentado corresponder - portanto, este comando substitui a@@.*start
linha inteiranl <<\\@@
.:l;N
:
comando define um rótulo de filial - aqui eu defino um chamado:l
abel. ON
comando ext anexa a próxima linha de entrada ao espaço do padrão seguido por um\n
caractere ewline. Essa é uma das poucas maneiras de obter uma linha de\n
ew em umsed
espaço de padrão - o\n
caractere de ewline é um delimitador seguro para umsed
der que faz isso há algum tempo.s/\(\n@@\)[^\n]*end$/\1/
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\n
ew final será imediatamente seguida pela@@.*end
marcação do final$
do espaço de padrão. Quando ele age, ele substitui toda a cadeia correspondente pelo\1
primeiro\(
grupo\)
, ou\n@@
.Tl
T
comando 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çoN
) . Isso significa que toda vez que uma linha de\n
ew é anexada ao espaço do padrão que não corresponde ao seu delimitador final, oT
comando est falha e se ramifica de volta ao:l
abel, o que resulta emsed
puxar aN
linha ext e girar até obter êxito.e
Quando a substituição para o jogo final é bem sucedido e o script não suporta a filial de uma falha
T
est,sed
vaie
xecute um comando quel
ooks como este:Você pode ver isso editando a última linha lá para parecer
Tl;l;e
.Imprime:
while ... read
Uma última maneira de fazer isso, e talvez a maneira mais simples, é usar um
while read
loop, mas por um bom motivo. A concha - (principalmente umabash
concha) - 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 - parareturn
informaçõ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:A primeira coisa que acontece para cada iteração é
read
puxar uma linha. Se for bem-sucedido, significa que o loop ainda não atingiu o EOF e, portanto, nocase
correspondente ao delimitador de início, odo
bloco é executado imediatamente. Else,printf
imprime a$line
eleread
esed
é chamado.sed
vaip
rint cada linha até encontrar o início marcador - quandoq
UITS entrada inteiramente. O-u
switch nbuffered é necessário para o GNUsed
porque ele pode armazenar um buffer com avidez, mas - de acordo com a especificação - outros POSIXsed
s devem funcionar sem nenhuma consideração especial - desde que<infile
seja um arquivo comum.Quando o primeiro
sed
q
sai, o shell executa odo
bloco do loop - que chama outrosed
que imprime todas as linhas até encontrar o marcador final . Ele canaliza sua saída parapaste
, porque imprime números de linhas cada um em sua própria linha. Como isso:paste
em seguida, cola os:
caracteres nos caracteres, e toda a saída se parece com: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
,head
esed
para fazer a coisa certa (embora, para o GNUsed
, você precisa do interruptor cli) e você sempre deve poder confiarread
- porque é, por natureza, muito lento . E é por isso que o loop acima chama apenas uma vez por bloco de entrada.fonte
sed
exemplo 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..)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 serIsso 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
.fonte
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).A correção mais simples que consigo pensar é não usar,
nl
mas conte as linhas você mesmo:Em seguida, você o executa no arquivo:
fonte
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:
Isso produz o seguinte para um arquivo de entrada que repete o caso de teste três vezes:
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:Ou número de palavras
echo -E "${acc:1}" | wc
:fonte
Editar adicionou uma opção para definir um filtro fornecido pelo usuário
Por padrão, o filtro é "nl". Para alterar o filtro, use a opção "-p" com algum comando fornecido pelo usuário:
ou
Este último filtro produzirá:
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:
fonte
$/
es
sinalizando) e o uso doe
sinalizador para fazer a chamada real ao comando externo. Eu realmente gosto do segundo exemplo (arte ascii)!/s
= ("." significa(.|\n)
);$/
redefine o separador de registro.Esse é um trabalho para o awk.
Quando o script vê o marcador de início, ele observa que deve começar a ser canalizado
nl
. Quando apipe
variável é verdadeira (diferente de zero), a saída é canalizada para onl
comando; 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. Aclose
funçã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
nl
comando sair, liberando seus buffers. Seu script realmente fecha o canal muito cedo: o canal é fechado assim que o primeiroecho $line >myfifo
termina a execução. No entanto, onl
comando só vê o final do arquivo se obtiver um intervalo de tempo antes da próxima vez que o script for executadoecho $line >myfifo
. Se você tiver um grande volume de dados ou se adicionarsleep 1
após a gravaçãomyfifo
, verá quenl
apenas 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.
(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.
fonte
do
. (Eu não tenho o representante aqui para fazer uma pequena edição.)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 sejaou seja, converter texto em maiúsculas; então, para uma entrada de
você quer uma saída de
Aqui está minha primeira aproximação de uma solução:
onde os espaços antes das
@@
cadeias e perto do final da última linha são tabulações. Observe que estou usandonl
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) efile1
(ativo; em uma seção). É assim que eles se parecem com a entrada acima: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.
fonte
nl
já faz a maior parte do trabalho lá - é para isso que-d
serve a opção elimitador.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:
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: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.
fonte
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
nl
apenas como exemplo de filtro)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.
fonte
M
,N
eO
seriam numerados4
,5
e6
. Isso não faz isso. Minha resposta é sim (além do fato de que, em sua encarnação atual, ela não funcionanl
como 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?)nl -p
para obterM,N,O==4,5,6
.