Domanda

Vorrei aggiornare un gran numero di file sorgente C ++ con una direttiva di inclusione aggiuntiva prima di qualsiasi #includes esistente. Per questo tipo di attività, normalmente utilizzo un piccolo script bash con sed per riscrivere il file.

Come posso ottenere sed per sostituire solo la prima occorrenza di una stringa in un file anziché sostituire ogni occorrenza?

Se uso

sed s/#include/#include "newfile.h"\n#include/

sostituisce tutti #include.

Sono anche benvenuti suggerimenti alternativi per ottenere la stessa cosa.

È stato utile?

Soluzione

 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

oppure, se preferisci: Nota dell'editore: funziona solo con GNU sed.

sed '0,/RE/s//to_that/' file 

Fonte

Altri suggerimenti

Scrivi uno script sed che sostituirà solo la prima occorrenza di " Apple " di " Banana "

Esempio di input: output:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Questo è il semplice script: Nota dell'editore: funziona solo con GNU sed.

sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename

questo ha funzionato per me.

Esempio

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Nota dell'editore: entrambi funzionano solo con GNU sed.

Una panoramica delle molte utili risposte esistenti , integrate da spiegazioni :

Gli esempi qui usano un caso d'uso semplificato: sostituisci la parola 'pippo' con 'barra' solo nella prima riga corrispondente.
A causa dell'uso di stringhe quotate in C ANSI ($'...') per fornire le righe di input di esempio, bash, ksh o zsh è assunto come shell.


GNU sed solo:

La risposta di Ben Hoffstein ci mostra che GNU fornisce un'estensione al Specifica POSIX per 0,/re/ che consente il seguente modulo a 2 indirizzi: re (1,/re/ rappresenta qui un'espressione regolare arbitraria).

// consente alla regex di abbinare anche sulla prima riga . In altre parole: un tale indirizzo creerà un intervallo dalla 1a riga fino alla riga corrispondente compresa s/.../.../, indipendentemente dal fatto che s sia presente sulla 1a riga o su qualsiasi riga successiva.

  • Contrastalo con il modulo conforme a POSIX foo , che crea un intervallo che corrisponde dalla 1a riga fino alla riga corrispondente che corrisponde a t su successiva linee; in altre parole: questo non rileverà la prima occorrenza di una -e corrispondenza se si verifica sulla prima linea e anche impedisce l'uso della stenografia 1 s/foo/bar/ per il riutilizzo dell'ultima regex utilizzata (vedere il punto successivo). [1]

Se si combina un 1,// indirizzo con una 2 (sostituzione) chiamata che utilizza l'espressione regolare stessa , il comando eseguirà effettivamente la sostituzione solo sulla prima linea che corrisponde a s//.
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' fornisce un comodo collegamento per riutilizzare l'espressione regolare applicata più di recente : una coppia di delimitatori vuota , $'1bar\n2bar' .

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

A solo funzionalità POSIX 1 come BSD (macOS) /foo/ (funzionerà anche con GNU s/foo/bar/):

Poiché sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' non può essere utilizzato e il modulo sed: first RE may not be empty non rileverà sed: -e expression #1, char 0: no previous regular expression se si verifica nella prima riga (vedere sopra), è richiesta una gestione speciale per la prima riga .

La risposta di MikhailVS menziona la tecnica, qui in un esempio concreto:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Nota:

  • La regex vuota <=> viene utilizzata due volte qui: una volta per l'endpoint dell'intervallo e una volta nella chiamata <=>; in entrambi i casi, regex <=> viene implicitamente riutilizzato, consentendoci di non doverlo duplicare, il che rende il codice più breve e più gestibile.

  • POSIX <=> necessita di nuove righe effettive dopo determinate funzioni, come dopo il nome di un'etichetta o addirittura la sua omissione, come nel caso di <=> qui; La suddivisione strategica dello script in più <=> opzioni è un'alternativa all'utilizzo di una nuova riga effettiva: termina ogni <=> blocco di script dove normalmente dovrebbe andare una nuova riga.

<=> sostituisce <=> solo sulla 1a riga, se presente. In tal caso, <=> si ramifica alla fine dello script (salta i comandi rimanenti sulla riga). (La funzione <=> si ramifica su un'etichetta solo se la chiamata <=> più recente ha eseguito una sostituzione effettiva; in assenza di un'etichetta, come nel caso qui, la fine dello script è ramificata a).

Quando ciò accade, l'indirizzo di intervallo <=>, che normalmente trova la prima occorrenza a partire dalla riga 2 , non corrisponderà e l'intervallo non , poiché l'indirizzo viene valutato quando la riga corrente è già <=>.

Al contrario, se non c'è corrispondenza sulla prima riga, <=> verrà inserito e finiràd la vera prima partita.

L'effetto netto è lo stesso di GNU <=> <=>: viene sostituita solo la prima occorrenza, sia che si verifichi sulla 1a riga o su qualsiasi altra.


Approcci non-range

la risposta di potong dimostra le loop tecniche che eludere la necessità di un intervallo ; poiché usa la sintassi GNU <=>, ecco i equivalenti conformi a POSIX :

Tecnica del loop 1: al primo incontro, esegui la sostituzione, quindi inserisci un loop che stampa semplicemente le righe rimanenti così come sono :

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

Loop tecnica 2, solo per file di piccole dimensioni : leggi l'intero input in memoria, quindi esegui una singola sostituzione su di esso .

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

[1] 1.61803 fornisce esempi di ciò che accade con <=>, con e senza un successivo <=>:
- <=> rese <=>; cioè, entrambe sono state aggiornate, poiché il numero di riga <=> corrisponde alla prima riga e regex <=> - la fine dell'intervallo - viene quindi cercato solo a partire dal successivo line. Pertanto, entrambe sono selezionate in questo caso e la sostituzione <=> viene eseguita su entrambe.
- <=> fallisce : con <=> (BSD / macOS) e <=> (GNU), perché, al momento dell'elaborazione della prima riga (a causa del numero di riga <=> che inizia l'intervallo), non è stato ancora applicato alcun regex, quindi <=> non fa riferimento a nulla.
Ad eccezione della speciale sintassi <=> di GNU <=>, qualsiasi intervallo che inizia con un numero di riga preclude efficacemente l'uso di <=>.

Potresti usare awk per fare qualcosa di simile ...

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Spiegazione:

/#include/ && !done

Esegue l'istruzione action tra {} quando la riga corrisponde a " #include " e non l'abbiamo già elaborato.

{print "#include \"newfile.h\""; done=1;}

Questo stampa #include " newfile.h " ;, dobbiamo evitare le virgolette. Quindi impostiamo la variabile done su 1, quindi non aggiungiamo più inclusioni.

1;

Questo significa " stampa la riga " - per impostazione predefinita, un'azione vuota stampa $ 0, che stampa l'intera riga. Una fodera e più facile da capire rispetto a sed IMO :-)

Una raccolta completa di risposte su linuxtopia sed FAQ . Sottolinea inoltre che alcune risposte fornite dalle persone non funzioneranno con la versione non GNU di sed, ad esempio

sed '0,/RE/s//to_that/' file

nella versione non GNU dovrà essere

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

Tuttavia, questa versione non funzionerà con gnu sed.

Ecco una versione che funziona con entrambi:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

es:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

Aggiungi semplicemente il numero di occorrenze alla fine:

sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Come funziona questo script: per le righe tra 1 e la prima #include (dopo la riga 1), se la riga inizia con sed, anteporre la riga specificata.

Tuttavia, se la prima 0,/^#include/ è nella riga 1, sia la riga 1 che la successiva 1, successiva avranno la linea anteposta. Se stai usando GNU <=>, ha un'estensione in cui <=> (invece di <=>) farà la cosa giusta.

Una possibile soluzione:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :
    n
    b

Spiegazione:

  • leggi le righe fino a quando non troviamo #include, stampa queste righe e avvia un nuovo ciclo
  • inserisci la nuova linea di inclusione
  • inserisci un ciclo che legge solo le righe (di default sed stampa anche queste righe), da qui non torneremo alla prima parte dello script

So che questo è un vecchio post, ma avevo una soluzione che utilizzavo:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

Fondamentalmente usa grep per trovare la prima occorrenza e fermarti qui. Stampa anche il numero della riga, ovvero 5: riga. Inseriscilo in sed e rimuovi il: e tutto il resto, quindi ti resta solo un numero di riga. Inseriscilo in sed che aggiunge s /.*/ sostituisci alla fine che fornisce uno script a 1 riga che viene reindirizzato nell'ultimo sed per essere eseguito come uno script su file.

quindi se regex = #include e rimpiazza = blah e la prima occorrenza che grep trova è sulla linea 5, i dati inviati all'ultima sed sarebbero 5s /.*/ blah /.

Se qualcuno è venuto qui per sostituire un personaggio per la prima occorrenza in tutte le righe (come me), usa questo:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

Modificando da 1 a 2, ad esempio, è possibile sostituire solo tutte le seconde a.

lo farei con uno script awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

quindi eseguilo con awk:

awk -f awkscript headerfile.h > headerfilenew.h

potrebbe essere sciatto, io sono nuovo a questo.

Come suggerimento alternativo potresti voler guardare il comando ed.

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF

Finalmente sono riuscito a farlo funzionare in uno script Bash usato per inserire un timestamp univoco in ogni elemento in un feed RSS:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

Cambia solo la prima occorrenza.

${nowms} è il tempo in millisecondi impostato da uno script Perl, $counter è un contatore utilizzato per il controllo del ciclo all'interno dello script, \ consente di continuare il comando sulla riga successiva.

Il file viene letto e stdout viene reindirizzato a un file di lavoro.

Per come lo capisco, 1,/====RSSpermalink====/ dice a sed quando fermarsi impostando un limite di intervallo, e quindi s/====RSSpermalink====/${nowms}/ è il familiare comando sed per sostituire la prima stringa con la seconda.

Nel mio caso ho messo il comando tra virgolette doppie perché lo sto usando in uno script Bash con variabili.

Usando FreeBSD ed ed evita & il " nessuna corrispondenza " errore nel caso in cui non vi siano include istruzioni in un file da elaborare:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF

Questo potrebbe funzionare per te (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

o se la memoria non è un problema:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...

Con l'opzione -z di GNU sed puoi elaborare l'intero file come se fosse solo una riga. In questo modo un s/…/…/ sostituirà solo la prima corrispondenza nell'intero file. Ricorda: sed sostituisce solo la prima corrispondenza in ogni riga, ma con l'opzione s/text.*// s/text[^\n]*// tratta l'intero file come una singola riga.

sed -z 's/#include/#include "newfile.h"\n#include'

Nel caso generale devi riscrivere la tua espressione sed poiché lo spazio modello ora contiene l'intero file invece di una sola riga. Alcuni esempi:

  • [^\n] può essere riscritto come [^\n]*. text corrisponde a tutto tranne al carattere di nuova riga. s/^text// corrisponderà a tutti i simboli dopo s/(^|\n)text// fino al raggiungimento di una nuova riga.
  • s/text$// può essere riscritto come s/text(\n|$)//.
  • <=> può essere riscritto come <=>.

Il seguente comando rimuove la prima occorrenza di una stringa, all'interno di un file. Rimuove anche la linea vuota. Viene presentato su un file XML, ma funzionerebbe con qualsiasi file.

Utile se lavori con file xml e vuoi rimuovere un tag. In questo esempio rimuove la prima occorrenza del & Quot; isTag & Quot; tag.

Comando:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

File di origine (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

File dei risultati (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps: non ha funzionato per me su Solaris SunOS 5.10 (abbastanza vecchio), ma funziona su Linux 2.6, versione sed 4.1.5

Niente di nuovo ma forse una risposta un po 'più concreta: sed -rn '0,/foo(bar).*/ s%%\1%p'

Esempio: xwininfo -name unity-launcher produce output come:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

L'estrazione dell'ID finestra con xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' produce:

0x2200003

POSIXly (valido anche in sed), utilizzato solo uno regex, è necessaria memoria solo per una riga (come al solito):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Ha spiegato:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top