Jak użyć sed, aby zastąpić tylko pierwsze wystąpienie w pliku?

217

Chciałbym zaktualizować dużą liczbę plików źródłowych C ++ za pomocą dodatkowej dyrektywy include przed jakimkolwiek istniejącym #include. Do tego rodzaju zadań zwykle używam małego skryptu bash z sed, aby ponownie zapisać plik.

Jak mogę sedzastąpić tylko pierwsze wystąpienie ciągu w pliku zamiast zastępować każde wystąpienie?

Jeśli użyję

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

zastępuje wszystkie #include.

Mile widziane są również alternatywne sugestie, aby osiągnąć to samo.

David Dibben
źródło

Odpowiedzi:

135
 # 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---

lub, jeśli wolisz: Uwaga redakcji: działa tylko z GNU sed .

sed '0,/foo/s//bar/' file 

Źródło

Ben Hoffstein
źródło
86
Myślę, że wolę rozwiązanie „lub jeśli wolisz”. Dobrze byłoby również wyjaśnić odpowiedzi - i udzielić odpowiedzi bezpośrednio na pytanie, a następnie uogólnić, a nie tylko uogólnić. Ale dobra odpowiedź.
Jonathan Leffler
7
FYI dla użytkowników komputerów Mac, musisz zamienić 0 na 1, więc: sed '1, / RE / s // to_that /' file
mhost
1
@ host Nie, zastąpi to, jeśli wzorzec zostanie znaleziony w linii nr 1, a nie, jeśli wzorzec znajdzie się w innej linii, ale nadal będzie pierwszym znalezionym wzorcem. PS należy wspomnieć, że 0,działa to tylko zgnu sed
Jotne
11
Czy ktoś mógłby wyjaśnić rozwiązanie „lub jeśli preferujesz”? Nie wiem, gdzie umieścić wzorzec „z”.
Jean-Luc Nacif Coelho
3
@ Jean-LucNacifCoelho: using s//- czyli jest pusty regex - oznacza, że ostatnio stosowane regex jest niejawnie ponownie wykorzystane; w tym przypadku RE. Ten wygodny skrót oznacza, że ​​nie musisz powielać wyrażenia kończącego zakres w swoim społączeniu.
mklement0
289

sedSkrypt, który zastąpi tylko pierwsze wystąpienie „Apple” przez „banan”

Przykład

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

Oto prosty skrypt: Notka redaktora: działa tylko z GNU sed .

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Pierwsze dwa parametry 0i /Apple/to specyfikator zasięgu. To, s/Apple/Banana/co jest wykonywane w tym zakresie. W tym przypadku „w zakresie od początku tak ( 0) aż do pierwszej instancji Apple, należy wymienić Applez Banana. Tylko pierwszy Applezostanie zastąpiony.

Tło: W tradycyjnym sedspecyfikatorze zakresu jest również „zaczynać tutaj” i „kończyć tutaj” (włącznie). Jednak najniższy „początek” jest pierwszą linią (linia 1), a jeśli „koniec tutaj” jest wyrażeniem regularnym, wówczas próbuje się dopasować tylko w następnej linii po „początku”, więc najwcześniejszym możliwym końcem jest linia 2. Zatem, ponieważ zakres obejmuje, najmniejszy możliwy zakres to „2 linie”, a najmniejszy zakres początkowy to zarówno linie 1, jak i 2 (tzn. Jeśli wystąpi zdarzenie na linii 1, wystąpienia na linii 2 również zostaną zmienione, w tym przypadku niepożądane ). GNUsed dodaje własne rozszerzenie pozwalające na określenie początku jako „pseudo”, line 0tak aby koniec zakresu mógł być line 1, pozwalając na zakres „tylko pierwszej linii”

Lub wersja uproszczona (pusty jak RE //oznacza ponowne użycie poprzedniej, więc jest to równoważne):

sed '0,/Apple/{s//Banana/}' input_filename

Nawiasy klamrowe są opcjonalne dla spolecenia, więc jest to również równoważne:

sed '0,/Apple/s//Banana/' input_filename

Wszystkie działają sedtylko na GNU .

Możesz także zainstalować GNU sed na OS X za pomocą homebrew brew install gnu-sed.

tim
źródło
166
przetłumaczone na ludzki język: zacznij od linii 0, kontynuuj aż do dopasowania „Apple”, wykonaj podstawienie w nawiasach klamrowych. cfr: grymoire.com/Unix/Sed.html#uh-29
mariotomo
8
W systemie OS X dostajęsed: 1: "…": bad flag in substitute command: '}'
ELLIOTTCABLE
5
@ELLIOTTCABLE w systemie OS X, użyj sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'. Od odpowiedzi @ MikhailVS (obecnie) na dole.
djb
8
Działa również bez wsporników:sed '0,/foo/s/foo/bar/'
Innokenty
5
Rozumiem sed: -e expression #1, char 3: unexpected , 'z tym
Jonathan
56
sed '0,/pattern/s/pattern/replacement/' filename

to działało dla mnie.

przykład

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

Uwaga edytora: oba działają tylko z GNU sed .

Sushil
źródło
2
@Landys nadal zastępuje instancje w innych wierszach; nie tylko pierwsza instancja
sarat
1
@sarat Tak, masz rację. sed '1,/pattern/s/pattern/replacement/' filenamedziała tylko wtedy, gdy „wzór nie pojawi się w pierwszym wierszu” na komputerze Mac. Usunę mój poprzedni komentarz, ponieważ nie jest dokładny. Szczegóły można znaleźć tutaj ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ). Odpowiedź Andy'ego działa tylko na GNU sed, ale nie na Macu.
Landys
45

Przegląd z wielu pomocnych istniejących odpowiedzi , uzupełnione z wyjaśnieniami :

Przykłady tutaj wykorzystują uproszczony przypadek użycia: zamień słowo „foo” na „bar” tylko w pierwszym pasującym wierszu.
Dzięki zastosowaniu ANSI C cudzysłowach ( $'...') dostarczenie próbki linii wejściowych bash, kshlub zshprzyjmuje się jako powłoki.


sedTylko GNU :

Ben Hoffstein za anwswer pokazuje nam, że GNU przewiduje się rozszerzenie do specyfikacji POSIX nased który umożliwia następujące formy 2-adres : 0,/re/( rereprezentuje dowolne wyrażenie regularne tutaj).

0,/re/pozwala dopasować wyrażenie regularne również w pierwszej linii . Innymi słowy: taki adres utworzy zakres od 1. linii do linii włącznie, która pasuje re- niezależnie reod tego, czy występuje w 1. linii, czy w dowolnej kolejnej linii.

  • Porównaj to z formularzem zgodnym z POSIX 1,/re/, który tworzy zakres, który pasuje od 1. linii do linii zawierającej, która pasuje redo kolejnych linii; innymi słowy: nie wykryje to pierwszego wystąpienia redopasowania, jeśli zdarzy się ono w pierwszej linii, a także zapobiegnie użyciu skrótu// do ponownego użycia ostatnio używanego wyrażenia regularnego (patrz następny punkt). 1

Jeśli połączysz 0,/re/adres z wywołaniem s/.../.../(podstawienia), które używa tego samego wyrażenia regularnego, twoje polecenie skutecznie wykona podstawienie tylko w pierwszym pasującym wierszu re.
sedzapewnia wygodny skrót do ponownego wykorzystania ostatnio stosowane wyrażenia regularnego : AN pusty parę ogranicznika,// .

$ 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

Tylko funkcje POSIX, sedtakie jak BSD (macOS)sed (będą również działać z GNU sed ):

Ponieważ 0,/re/nie można go użyć, a formularz 1,/re/nie wykryje, reczy zdarzy się na pierwszej linii (patrz wyżej), wymagana jest specjalna obsługa pierwszej linii .

Odpowiedź MikhailVS wymienia technikę, podając tutaj konkretny przykład:

$ 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

Uwaga:

  • Pusty //skrót wyrażenia regularnego jest tutaj stosowany dwa razy: raz dla punktu końcowego zakresu i raz w swywołaniu; w obu przypadkach regex foojest domyślnie ponownie wykorzystywany, co pozwala nam nie musieć go powielać, co czyni zarówno krótszy, jak i łatwiejszy do utrzymania kod.

  • POSIX sedpotrzebuje rzeczywistych znaków nowej linii po określonych funkcjach, takich jak nazwa etykiety lub nawet jej pominięcie, jak ma to miejsce w tym przypadku t; strategiczne podzielenie skryptu na wiele -eopcji jest alternatywą dla użycia rzeczywistych znaków nowej linii: zakończ każdą -eczęść skryptu tam, gdzie normalnie musiałaby iść nowa linia.

1 s/foo/bar/zastępuje tylko foow 1. linii, jeśli ją tam znajdziesz. Jeśli tak, trozgałęzia się do końca skryptu (pomija pozostałe polecenia w wierszu). ( tFunkcja rozgałęzia się na etykietę tylko wtedy, gdy ostatnie swywołanie dokonało rzeczywistego podstawienia; przy braku etykiety, jak w tym przypadku, koniec skryptu jest rozgałęziony).

Gdy tak się stanie, adres zakresu 1,//, który zwykle znajduje pierwsze wystąpienie, zaczynając od linii 2 , nie będzie pasował, a zakres nie zostanie przetworzony, ponieważ adres jest obliczany, gdy bieżąca linia już jest 2.

I odwrotnie, jeśli nie ma dopasowania w 1. linii, 1,// zostanie wprowadzone i znajdzie prawdziwe pierwsze dopasowanie.

Efekt netto jest taki sam jak z GNU sed„s 0,/re/: tylko pierwsze wystąpienie otrzymuje, czy występuje on na 1. linii lub dowolny inny.


Podejścia poza zasięgiem

odpowiedź potonga pokazuje techniki pętli , które omijają potrzebę zasięgu ; ponieważ używa składni GNU sed , oto odpowiedniki zgodne z POSIX :

Technika pętli 1: przy pierwszym dopasowaniu wykonaj podstawienie, a następnie wejdź do pętli, która po prostu drukuje pozostałe linie bez zmian :

$ 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

Technika pętli 2, tylko dla małych plików : wczytaj całą zawartość do pamięci, a następnie wykonaj na niej pojedyncze podstawienie .

$ 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 podaje przykłady tego, co dzieje się 1,/re/z następującymi i później s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'plonami $'1bar\n2bar'; tzn. obie linie zostały zaktualizowane, ponieważ numer linii 1pasuje do 1. linii, a wyrażenie regularne /foo/- koniec zakresu - jest wtedy szukane tylko od następnego wiersza. Dlatego w tym przypadku wybiera się obie linie, a s/foo/bar/zastępowanie odbywa się na obu z nich.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' kończy się niepowodzeniem : w przypadku sed: first RE may not be empty(BSD / macOS) i sed: -e expression #1, char 0: no previous regular expression(GNU), ponieważ w czasie przetwarzania pierwszego wiersza (z powodu numeru wiersza 1rozpoczynającego zakres) nie zastosowano jeszcze wyrażenia regularnego, więc//nie odnosi się do niczego.
Z wyjątkiem sedspecjalnej 0,/re/składni GNU , każdy zakres rozpoczynający się od numeru linii skutecznie wyklucza użycie //.

mklement0
źródło
25

Możesz użyć awk, aby zrobić coś podobnego ...

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

Wyjaśnienie:

/#include/ && !done

Uruchamia instrukcję akcji między {}, gdy wiersz pasuje do „#include”, a my jeszcze jej nie przetworzyliśmy.

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

Wypisuje #include „newfile.h”, musimy uciec od cudzysłowów. Następnie ustawiamy zmienną done na 1, więc nie dodajemy więcej dołączeń.

1;

Oznacza to „wydrukuj linię” - domyślnie pusta akcja wypisuje 0 USD, która wypisuje całą linię. Jeden liniowiec i łatwiejszy do zrozumienia niż sed IMO :-)

richq
źródło
4
Ta odpowiedź jest bardziej przenośna niż rozwiązania sed, które opierają się na GNU sed itp. (Np. Sed w OS-X jest do bani!)
Jay Taylor
1
Jest to naprawdę bardziej zrozumiałe, ale dla mnie dodaje linię zamiast jej zastępować; użyte komendy: awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
Cereal Killer
to samo tutaj, najbardziej zrozumiałe polecenie, ale dodaje wiersz powyżej znalezionego ciągu zamiast go zastępować
Flion
1
Odpowiedź jest dość czytelna. Oto moja wersja, która zastępuje ciąg zamiast dodawać nowy wiersz. awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Orsiris de Jong
18

Całkiem obszerny zbiór odpowiedzi na linuxtopia sed FAQ . Podkreśla także, że niektóre odpowiedzi udzielane przez ludzi nie będą działać z sedem w wersji innej niż GNU, np

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

w wersji innej niż GNU będzie musiało być

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

Jednak ta wersja nie będzie działać z gnu sed.

Oto wersja, która działa zarówno:

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

dawny:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
Michaił
źródło
Rzeczywiście, przetestowane na Ubuntu Linux v16 i FreeBSD 10.2. Dzięki Ci.
Sopalajo de Arrierez
12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Jak działa ten skrypt: W przypadku linii między 1 a pierwszą #include(po linii 1), jeśli linia zaczyna się od #include, to wstaw poprzednią linię.

Jeśli jednak pierwsza #includeznajduje się w linii 1, to zarówno linia 1, jak i następna następna #includebędą miały linię poprzedzającą. Jeśli używasz GNU sed, ma rozszerzenie, w którym 0,/^#include/(zamiast 1,) zrobi to dobrze.

Chris Jester-Young
źródło
11

Wystarczy dodać liczbę wystąpień na końcu:

sed s/#include/#include "newfile.h"\n#include/1
nieistniejący
źródło
7
Niestety to nie działa. Zastępuje tylko pierwsze wystąpienie w każdym wierszu pliku, a nie pierwsze wystąpienie w pliku.
David Dibben,
1
Dodatkowo jest to rozszerzenie GNU sed, a nie standardowa funkcja sed.
Jonathan Leffler
10
Hmmm ... czas mija. POSIX 2008/2013 for sedokreśla komendę substytucyjną za pomocą: [2addr]s/BRE/replacement/flagsi zauważa, że ​​„Wartość flag powinna wynosić zero lub więcej z: n Zastępuje tylko n-te wystąpienie tylko BRE znalezionego w obszarze wzorców”. Tak więc, przynajmniej w POSIX 2008, końcowe 1nie jest sedrozszerzeniem GNU . Rzeczywiście, nawet w standardzie SUS / POSIX 1997 , było to obsługiwane, więc w 2008 roku byłem bardzo nie na linii.
Jonathan Leffler
7

Możliwe rozwiązanie:

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

Wyjaśnienie:

  • czytaj linie, aż znajdziemy #include, wypisz te linie, a następnie rozpocznij nowy cykl
  • wstaw nową linię dołączania
  • wejdź w pętlę, która po prostu odczytuje wiersze (domyślnie sed również je wydrukuje), nie wrócimy stąd do pierwszej części skryptu
mitchnull
źródło
sed: file me4.sed line 4: ":" lacks a label
rogerdpack
najwyraźniej coś się zmieniło w ostatnich wersjach sed i puste etykiety nie są już dozwolone. zaktualizowałem odpowiedź
mitchnull
4

Wiem, że to stary post, ale miałem rozwiązanie, z którego korzystałem:

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

Zasadniczo użyj grep, aby wydrukować pierwsze wystąpienie i zatrzymać się na nim. Dodatkowo wydrukuj numer linii tj 5:line. Wklej to do sed i usuń: i cokolwiek innego, aby po prostu został ci numer linii. Wklej to do sed, który dodaje s /.*/ zamień na numer końcowy, co skutkuje 1-wierszowym skryptem, który jest dołączany do ostatniego seda, aby działał jako skrypt w pliku.

więc jeśli wyrażenie regularne = #includei zamień = blahi pierwsze wystąpienie grep znajdzie się w linii 5, to dane przesyłane do ostatniej sed byłyby 5s/.*/blah/.

Działa, nawet jeśli pierwsze wystąpienie jest w pierwszej linii.

Michael Edwards
źródło
Całkowicie nienawidzę skryptów sed wieloliniowych lub komend sed za pomocą czegokolwiek oprócz s i numeru pościeli, więc zgadzam się z tym podejściem. Oto, czego użyłem w moim przypadku użycia (działa z bash): filepath = / etc / hosts; patt = '^ \ (127 \ .0 \ .0 \ .1. * \)'; repl = '\ 1 newhostalias'; sed $ (IFS =: linearray = ($ (grep -E -m 1 -n "$ patt" "$ ścieżka pliku)) && echo $ {linearray [0]}) s /" $ patt "/" $ repl " / „$ filepath”
parzystość3
To działa. Chociaż tylko z sedami, które są wystarczająco inteligentne, aby zaakceptować, sed -f -a niektóre nie, ale możesz to obejść :)
rogerdpack
3

Jeśli ktoś przyszedł tutaj, aby zastąpić znak po raz pierwszy we wszystkich liniach (tak jak ja), użyj tego:

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

Zmieniając na przykład 1 na 2, możesz zamiast tego zastąpić tylko wszystkie sekundy.

FatihSarigol
źródło
2
Nie musisz tego wszystkiego robić, 's/a/b/'znaczy match aido just first match for every matching line
Samveen
Jestem z Samveen. To także nie odpowiada na zadane tutaj pytanie . Radziłbym usunąć tę odpowiedź.
Socowi
Pytanie brzmiało „pierwsze wystąpienie w pliku”, a nie „pierwsze wystąpienie w linii”
Manuel Romeiro,
3

Dzięki opcji GNU sed -zmożesz przetwarzać cały plik tak, jakby był tylko jedną linią. W ten sposób a s/…/…/zastąpiłoby tylko pierwsze dopasowanie w całym pliku. Pamiętaj: s/…/…/zastępuje tylko pierwsze dopasowanie w każdej linii, ale z tą -zopcją sedtraktuje cały plik jako pojedynczą linię.

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

W ogólnym przypadku musisz przepisać wyrażenie sed, ponieważ przestrzeń wzorców przechowuje teraz cały plik zamiast tylko jednej linii. Kilka przykładów:

  • s/text.*//można przepisać jako s/text[^\n]*//. [^\n]dopasowuje wszystko oprócz znaku nowej linii. [^\n]*dopasuje wszystkie symbole po, textaż do osiągnięcia nowej linii.
  • s/^text//można przepisać jako s/(^|\n)text//.
  • s/text$//można przepisać jako s/text(\n|$)//.
Socowi
źródło
2

zrobiłbym to za pomocą skryptu awk:

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

następnie uruchom go z awk:

awk -f awkscript headerfile.h > headerfilenew.h

może być niechlujny, jestem nowy w tym.

wakingrufus
źródło
2

Jako alternatywną sugestię warto przyjrzeć się edpoleceniu.

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
timo
źródło
2

W końcu udało mi się to uruchomić w skrypcie Bash używanym do wstawiania unikatowego znacznika czasu w każdym elemencie kanału RSS:

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

Zmienia tylko pierwsze wystąpienie.

${nowms}to czas w milisekundach ustawiony przez skrypt Perla, $counterjest licznikiem używanym do kontroli pętli w skrypcie, \pozwala na kontynuowanie wykonywania polecenia w następnym wierszu.

Plik jest wczytywany, a standardowe wyjście jest przekierowywane do pliku roboczego.

Sposób, w jaki to rozumiem, 1,/====RSSpermalink====/mówi sedowi, kiedy zatrzymać, ustawiając ograniczenie zasięgu, a następnie s/====RSSpermalink====/${nowms}/jest znane polecenie sed, aby zastąpić pierwszy ciąg drugim.

W moim przypadku wstawiam polecenie w cudzysłów, ponieważ używam go w skrypcie Bash ze zmiennymi.

Michael Cook
źródło
2

Korzystając z FreeBSD ed i unikaj edbłędu „brak dopasowania” w przypadku, gdy includew pliku nie ma instrukcji do przetworzenia:

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
nazq
źródło
Jest to bardzo podobne do Timo jest odpowiedzią , ale dodano ponad rok później.
Jonathan Leffler
2

Może to działać dla ciebie (GNU sed):

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

lub jeśli pamięć nie stanowi problemu:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
potong
źródło
0

Następujące polecenie usuwa pierwsze wystąpienie ciągu w pliku. Usuwa również pustą linię. Jest prezentowany na pliku xml, ale działałby z każdym plikiem.

Przydatne, jeśli pracujesz z plikami xml i chcesz usunąć tag. W tym przykładzie usuwa pierwsze wystąpienie tagu „isTag”.

Komenda:

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

Plik źródłowy (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>

Plik wynikowy (output.txt)

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

ps: nie działało to dla mnie na Solaris SunOS 5.10 (dość stary), ale działa na Linux 2.6, wersja sed 4.1.5

Andreas Panagiotidis
źródło
Wygląda to niezwykle podobnie na tę samą podstawową ideę, co kilka poprzednich odpowiedzi, z tym samym zastrzeżeniem, że działa tylko z GNU sed(stąd nie działało z Solaris). Powinieneś to usunąć, proszę - to naprawdę nie dostarcza wyróżniających się nowych informacji na pytanie, które miało już 4 i pół roku, kiedy odpowiedziałeś. To prawda, że ​​ma działający przykład, ale ma on sporną wartość, gdy pytanie ma tyle odpowiedzi, ile ma to pytanie.
Jonathan Leffler
0

Nic nowego, ale może bardziej konkretna odpowiedź: sed -rn '0,/foo(bar).*/ s%%\1%p'

Przykład: xwininfo -name unity-launcherprodukuje dane wyjściowe takie jak:

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

Wyodrębnianie identyfikatora okna za pomocą xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p':

0x2200003
Stephen Niedzielski
źródło
0

POSIXly (również ważny w sed), Użyto tylko jednego wyrażenia regularnego, potrzeba pamięci tylko dla jednej linii (jak zwykle):

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

Wyjaśnione:

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.
Izaak
źródło
0

Przypadek użycia może być taki, że twoje wystąpienia są rozłożone w całym pliku, ale wiesz , że twoja jedyna troska dotyczy pierwszych 10, 20 lub 100 wierszy.

Wtedy po prostu adresowanie tych wierszy rozwiązuje problem - nawet jeśli sformułowanie OP dotyczy tylko pierwszego.

sed '1,10s/#include/#include "newfile.h"\n#include/'
Sastorsl
źródło
0

Możliwym rozwiązaniem może być tutaj powiadomienie kompilatora o dołączeniu nagłówka, o którym nie wspomina się w plikach źródłowych. W GCC dostępne są następujące opcje:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

Kompilator Microsoft ma opcję / FI (wymuszone włączenie).

Ta funkcja może być przydatna w przypadku niektórych popularnych nagłówków, takich jak konfiguracja platformy. Makefile jądra Linux używa -includedo tego celu.

Kaz
źródło
-1
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
warhansen
źródło