Gostaria de atualizar um grande número de arquivos de origem C ++ com uma diretiva de inclusão extra antes de qualquer #include existente. Para esse tipo de tarefa, normalmente uso um pequeno script bash com sed para reescrever o arquivo.
Como faço sed
para substituir apenas a primeira ocorrência de uma seqüência de caracteres em um arquivo, em vez de substituir todas as ocorrências?
Se eu usar
sed s/#include/#include "newfile.h"\n#include/
ele substitui todos os #includes.
Sugestões alternativas para alcançar a mesma coisa também são bem-vindas.
command-line
sed
text-processing
David Dibben
fonte
fonte
0,
só funciona comgnu sed
s//
- ie, um regex vazio - significa que o regex aplicado mais recentemente é implicitamente reutilizado; neste casoRE
,. Esse atalho conveniente significa que você não precisa duplicar a regex de final de intervalo na suas
chamada.UMA
sed
script que substituirá apenas a primeira ocorrência de "Apple" por "Banana"Exemplo
Este é o script simples: Nota do editor: funciona apenas com GNU
sed
.Os dois primeiros parâmetros
0
e/Apple/
são o especificador de intervalo. Os/Apple/Banana/
é o que é executado dentro desse intervalo. Portanto, neste caso "dentro do intervalo do início (0
) até a primeira instância deApple
, substituaApple
porBanana
. Somente a primeiraApple
será substituída.Antecedentes: No tradicional,
sed
o especificador de intervalo também é "comece aqui" e "termine aqui" (inclusive). No entanto, o "início" mais baixo é a primeira linha (linha 1) e, se o "fim aqui" for uma expressão regular, só será tentada a correspondência na próxima linha depois de "início", portanto, o fim mais cedo possível será a linha 2. Portanto, como o intervalo é inclusivo, o menor intervalo possível é "2 linhas" e o menor intervalo inicial é ambas as linhas 1 e 2 (ou seja, se houver uma ocorrência na linha 1, as ocorrências na linha 2 também serão alteradas, não desejadas neste caso )GNU
O sed adiciona sua própria extensão, permitindo especificar o início como o "pseudo",line 0
para que o final do intervalo possa serline 1
, permitindo um intervalo de "apenas a primeira linha"Ou uma versão simplificada (um tipo de ER vazio
//
significa reutilizar a especificada anteriormente, portanto isso é equivalente):E as chaves são opcionais para o
s
comando, então isso também é equivalente:Todos esses trabalhos no GNU
sed
apenas .Você também pode instalar o GNU sed no OS X usando o homebrew
brew install gnu-sed
.fonte
sed: 1: "…": bad flag in substitute command: '}'
sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'
. Da resposta de @ MikhailVS (atualmente) bem abaixo.sed '0,/foo/s/foo/bar/'
sed: -e expression #1, char 3: unexpected
, ' `com esteisso funcionou para mim.
exemplo
Nota do editor: ambos funcionam apenas com o GNU
sed
.fonte
sed '1,/pattern/s/pattern/replacement/' filename
só funciona se "o padrão não ocorrer na primeira linha" no Mac. Excluirei meu comentário anterior, pois não é preciso. Os detalhes podem ser encontrados aqui ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ). A resposta de Andy funciona apenas para o GNU sed, mas não para o Mac.Uma visão geral das muitas respostas existentes úteis , complementadas com explicações :
Os exemplos aqui usam um caso de uso simplificado: substitua a palavra 'foo' por 'bar' apenas na primeira linha correspondente.
Devido ao uso de cordas ANSI C-citados (
$'...'
) para proporcionar as linhas de entrada de amostra,bash
,ksh
, ouzsh
é assumida como a casca.sed
Apenas GNU :A resposta de Ben Hoffstein nos mostra que o GNU fornece uma extensão para a especificação POSIX,
sed
que permite o seguinte formato de 2 endereços :0,/re/
(re
representa uma expressão regular arbitrária aqui).0,/re/
permite que o regex corresponda também na primeira linha . Em outras palavras: esse endereço criará um intervalo da 1ª linha até a linha correspondentere
- inclusive sere
ocorrerá na 1ª linha ou em qualquer linha subsequente.1,/re/
, que cria um intervalo que corresponde da 1ª linha até a linha correspondentere
às linhas subseqüentes ; em outras palavras: isso não detectará a primeira ocorrência de umare
correspondência se ocorrer na 1ª linha e também evita o uso de taquigrafia//
para reutilizar o regex usado mais recentemente (consulte o próximo ponto). 1Se você combinar um
0,/re/
endereço com umas/.../.../
chamada (substituição) que use a mesma expressão regular, seu comando efetivamente executará a substituição apenas na primeira linha correspondentere
.sed
fornece um atalho conveniente para reutilizar a expressão regular aplicada mais recentemente : um par de delimitadores vazio//
,.Um recurso POSIX somente
sed
como BSD (macOS)sed
(também funcionará com o GNUsed
):Como
0,/re/
não pode ser usado e o formulário1,/re/
não detectaráre
se ocorrer na primeira linha (veja acima), é necessário um tratamento especial para a 1ª linha .A resposta do MikhailVS menciona a técnica, colocada em um exemplo concreto aqui:
Nota:
O
//
atalho de regex vazio é empregado duas vezes aqui: uma para o ponto final do intervalo e uma vez nas
chamada; nos dois casos, a regexfoo
é reutilizada implicitamente, permitindo que não tenhamos que duplicá-la, o que resulta em código mais curto e mais sustentável.O POSIX
sed
precisa de novas linhas reais após determinadas funções, como após o nome de um rótulo ou mesmo sua omissão, como é o casot
aqui; dividir estrategicamente o script em várias-e
opções é uma alternativa ao uso de novas linhas reais: finalize cada-e
parte do script para onde normalmente uma nova linha precisaria ir.1 s/foo/bar/
substitui apenasfoo
na 1ª linha, se encontrada lá. Nesse caso,t
ramifica para o final do script (ignora os comandos restantes na linha). (At
função ramifica para um rótulo somente se as
chamada mais recente executou uma substituição real; na ausência de um rótulo, como é o caso aqui, o final do script é ramificado).Quando isso acontecer, o endereço do intervalo
1,//
, que normalmente encontra a primeira ocorrência iniciando na linha 2 , não corresponderá e o intervalo não será processado, porque o endereço é avaliado quando a linha atual já está2
.Por outro lado, se não houver correspondência na 1ª linha,
1,//
será inserida e encontrará a primeira correspondência verdadeira.O efeito líquido é o mesmo que com GNU
sed
's0,/re/
: apenas a primeira ocorrência é substituído, se ocorre na linha 1 ou qualquer outro.Abordagens fora da faixa
a resposta de potong demonstra técnicas de loop que ignoram a necessidade de um intervalo ; como ele usa a sintaxe GNU
sed
, eis os equivalentes compatíveis com POSIX :Técnica de loop 1: Na primeira partida, execute a substituição e insira um loop que simplesmente imprima as linhas restantes como estão :
Técnica de loop 2, apenas para arquivos pequenos : leia toda a entrada na memória e execute uma única substituição nela .
1 1.61803 fornece exemplos do que acontece com
1,/re/
, com e sem subsequentess//
:-
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
rendimentos$'1bar\n2bar'
; ou seja, ambas as linhas foram atualizadas, porque o número da linha1
corresponde à 1ª linha e a regex/foo/
- o final do intervalo - é procurada apenas para iniciar na próxima linha. Portanto, as duas linhas são selecionadas nesse caso e as/foo/bar/
substituição é realizada em ambas.-
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
falha : comsed: first RE may not be empty
(BSD / macOS) esed: -e expression #1, char 0: no previous regular expression
(GNU), porque, no momento em que a 1ª linha está sendo processada (devido ao número da linha1
iniciar o intervalo), nenhuma regex foi aplicada ainda, portanto//
não se refere a nada.Com exceção da sintaxe
sed
especial do GNU0,/re/
, qualquer intervalo que comece com um número de linha efetivamente impede o uso de//
.fonte
Você pode usar o awk para fazer algo semelhante.
Explicação:
Executa a instrução de ação entre {} quando a linha corresponde a "#include" e ainda não a processamos.
Isso imprime #include "newfile.h", precisamos escapar das aspas. Em seguida, definimos a variável done como 1, para não adicionar mais inclusões.
Isso significa "imprimir a linha" - uma ação vazia é padronizada para imprimir $ 0, que imprime a linha inteira. Um liner e mais fácil de entender do que o sed IMO :-)
fonte
awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Uma coleção bastante abrangente de respostas às perguntas frequentes sobre linuxtopia sed . Ele também destaca que algumas respostas fornecidas pelas pessoas não funcionarão com a versão não-GNU do sed, por exemplo,
na versão não-GNU terá que ser
No entanto, esta versão não funcionará com o gnu sed.
Aqui está uma versão que funciona com ambos:
ex:
fonte
Como este script funciona: Para linhas entre 1 e a primeira
#include
(após a linha 1), se a linha começar com#include
, em seguida, acrescente a linha especificada.No entanto, se o primeiro
#include
estiver na linha 1, a linha 1 e o próximo subsequente#include
terão a linha anexada. Se você estiver usando o GNUsed
, ele possui uma extensão em que0,/^#include/
(em vez de1,
) fará a coisa certa.fonte
Basta adicionar o número de ocorrência no final:
fonte
sed
especifica o comando substitute com:[2addr]s/BRE/replacement/flags
e observa que "O valor dos sinalizadores deve ser zero ou mais de: n Substitua pela enésima ocorrência apenas do BRE encontrado no espaço do padrão". Portanto, pelo menos no POSIX 2008, o trailing1
não é umased
extensão GNU . De fato, mesmo no padrão SUS / POSIX 1997 , isso era suportado, então eu estava muito fora de linha em 2008.Uma solução possível:
Explicação:
fonte
sed: file me4.sed line 4: ":" lacks a label
Eu sei que este é um post antigo, mas eu tinha uma solução que costumava usar:
Basicamente, use grep para imprimir a primeira ocorrência e parar por aí. Além disso, imprima o número da linha, ou seja,
5:line
. Canalize isso para o sed e remova o: e qualquer outro item para que você fique com um número de linha. Canalize isso para o sed, que adiciona s /.*/, substitua o número final, o que resulta em um script de 1 linha que é canalizado para o último sed para ser executado como um script no arquivo.portanto, se regex =
#include
e replace =blah
e a primeira ocorrência que grep encontrar estiver na linha 5, os dados encaminhados para o último sed seriam5s/.*/blah/
.Funciona mesmo se a primeira ocorrência estiver na primeira linha.
fonte
sed -f -
que alguns não são, mas você pode trabalhar em torno dele :)Se alguém veio aqui para substituir um caractere pela primeira ocorrência em todas as linhas (como eu), use o seguinte:
Alterando 1 para 2, por exemplo, você pode substituir todos os segundos a apenas.
fonte
's/a/b/'
significamatch a
edo just first match
for every matching line
Com a
-z
opção do GNU sed, você pode processar o arquivo inteiro como se fosse apenas uma linha. Dessa forma, ums/…/…/
substituirá apenas a primeira correspondência no arquivo inteiro. Lembre-se:s/…/…/
substitui apenas a primeira correspondência em cada linha, mas com a-z
opçãosed
trata o arquivo inteiro como uma única linha.No caso geral, você deve reescrever sua expressão sed, pois o espaço do padrão agora contém o arquivo inteiro em vez de apenas uma linha. Alguns exemplos:
s/text.*//
pode ser reescrito comos/text[^\n]*//
.[^\n]
corresponde a tudo, exceto o caractere de nova linha.[^\n]*
corresponderá a todos os símbolos apóstext
uma nova linha for atingida.s/^text//
pode ser reescrito comos/(^|\n)text//
.s/text$//
pode ser reescrito comos/text(\n|$)//
.fonte
eu faria isso com um script awk:
em seguida, execute-o com o awk:
pode ser desleixado, sou novo nisso.
fonte
Como sugestão alternativa, você pode querer olhar para o
ed
comando.fonte
Finalmente consegui que isso funcionasse em um script Bash usado para inserir um registro de data e hora exclusivo em cada item em um feed RSS:
Altera apenas a primeira ocorrência.
${nowms}
é o tempo em milissegundos definido por um script Perl,$counter
é um contador usado para controle de loop dentro do script,\
permite que o comando continue na próxima linha.O arquivo é lido e o stdout é redirecionado para um arquivo de trabalho.
Do jeito que eu entendo,
1,/====RSSpermalink====/
diz ao sed quando parar definindo uma limitação de alcance e, em seguida,s/====RSSpermalink====/${nowms}/
é o comando sed conhecido para substituir a primeira corda pela segunda.No meu caso, coloquei o comando entre aspas duplas porque estou usando-o em um script Bash com variáveis.
fonte
Usando o FreeBSD
ed
e eviteed
o erro "no match" caso não exista umainclude
declaração em um arquivo a ser processado:fonte
Isso pode funcionar para você (GNU sed):
ou se a memória não for um problema:
fonte
O comando a seguir remove a primeira ocorrência de uma seqüência de caracteres em um arquivo. Também remove a linha vazia. É apresentado em um arquivo xml, mas funcionaria com qualquer arquivo.
Útil se você trabalha com arquivos xml e deseja remover uma tag. Neste exemplo, ele remove a primeira ocorrência da marca "isTag".
Comando:
Arquivo de origem (source.txt)
Arquivo de resultado (output.txt)
ps: não funcionou para mim no Solaris SunOS 5.10 (bastante antigo), mas funciona no Linux 2.6, sed versão 4.1.5
fonte
sed
(portanto, não funcionou com o Solaris). Você deve excluir isso, por favor - ele realmente não fornece novas informações distintivas para uma pergunta que já tinha 4 anos e meio de idade quando você respondeu. Concedido, ele tem um exemplo bem trabalhado, mas isso é de valor discutível quando a pergunta tem tantas respostas quanto essa.Nada de novo, mas talvez uma resposta um pouco mais concreta:
sed -rn '0,/foo(bar).*/ s%%\1%p'
Exemplo:
xwininfo -name unity-launcher
produz resultados como:Extrair o ID da janela com
xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'
produz:fonte
POSIXly (também válido no sed), apenas um regex usado, precisa de memória apenas para uma linha (como de costume):
Explicado:
fonte
Talvez o caso de uso seja que suas ocorrências estejam espalhadas por todo o arquivo, mas você sabe que sua única preocupação está nas primeiras 10, 20 ou 100 linhas.
Em seguida, o simples tratamento dessas linhas corrige o problema - mesmo que o texto do OP seja o primeiro.
fonte
Uma solução possível aqui pode ser dizer ao compilador para incluir o cabeçalho sem que seja mencionado nos arquivos de origem. No GCC, existem estas opções:
O compilador da Microsoft possui o / FI (inclusão forçada).
Esse recurso pode ser útil para alguns cabeçalhos comuns, como a configuração da plataforma. O Makefile do kernel Linux usa
-include
para isso.fonte
fonte