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ę sed
zastą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.
command-line
sed
text-processing
David Dibben
źródło
źródło
0,
działa to tylko zgnu sed
s//
- czyli jest pusty regex - oznacza, że ostatnio stosowane regex jest niejawnie ponownie wykorzystane; w tym przypadkuRE
. Ten wygodny skrót oznacza, że nie musisz powielać wyrażenia kończącego zakres w swoims
połączeniu.sed
Skrypt, który zastąpi tylko pierwsze wystąpienie „Apple” przez „banan”Przykład
Oto prosty skrypt: Notka redaktora: działa tylko z GNU
sed
.Pierwsze dwa parametry
0
i/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 instancjiApple
, należy wymienićApple
zBanana
. Tylko pierwszyApple
zostanie zastąpiony.Tło: W tradycyjnym
sed
specyfikatorze 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 ).GNU
sed dodaje własne rozszerzenie pozwalające na określenie początku jako „pseudo”,line 0
tak 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):Nawiasy klamrowe są opcjonalne dla
s
polecenia, więc jest to również równoważne:Wszystkie działają
sed
tylko na GNU .Możesz także zainstalować GNU sed na OS X za pomocą homebrew
brew install gnu-sed
.źródło
sed: 1: "…": bad flag in substitute command: '}'
sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'
. Od odpowiedzi @ MikhailVS (obecnie) na dole.sed '0,/foo/s/foo/bar/'
sed: -e expression #1, char 3: unexpected
, 'z tymto działało dla mnie.
przykład
Uwaga edytora: oba działają tylko z GNU
sed
.źródło
sed '1,/pattern/s/pattern/replacement/' filename
dział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.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ściowychbash
,ksh
lubzsh
przyjmuje się jako powłoki.sed
Tylko GNU :Ben Hoffstein za anwswer pokazuje nam, że GNU przewiduje się rozszerzenie do specyfikacji POSIX na
sed
który umożliwia następujące formy 2-adres :0,/re/
(re
reprezentuje 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 pasujere
- niezależniere
od tego, czy występuje w 1. linii, czy w dowolnej kolejnej linii.1,/re/
, który tworzy zakres, który pasuje od 1. linii do linii zawierającej, która pasujere
do kolejnych linii; innymi słowy: nie wykryje to pierwszego wystąpieniare
dopasowania, 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). 1Jeśli połączysz
0,/re/
adres z wywołaniems/.../.../
(podstawienia), które używa tego samego wyrażenia regularnego, twoje polecenie skutecznie wykona podstawienie tylko w pierwszym pasującym wierszure
.sed
zapewnia wygodny skrót do ponownego wykorzystania ostatnio stosowane wyrażenia regularnego : AN pusty parę ogranicznika,//
.Tylko funkcje POSIX,
sed
takie jak BSD (macOS)sed
(będą również działać z GNUsed
):Ponieważ
0,/re/
nie można go użyć, a formularz1,/re/
nie wykryje,re
czy 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:
Uwaga:
Pusty
//
skrót wyrażenia regularnego jest tutaj stosowany dwa razy: raz dla punktu końcowego zakresu i raz ws
wywołaniu; w obu przypadkach regexfoo
jest domyślnie ponownie wykorzystywany, co pozwala nam nie musieć go powielać, co czyni zarówno krótszy, jak i łatwiejszy do utrzymania kod.POSIX
sed
potrzebuje rzeczywistych znaków nowej linii po określonych funkcjach, takich jak nazwa etykiety lub nawet jej pominięcie, jak ma to miejsce w tym przypadkut
; strategiczne podzielenie skryptu na wiele-e
opcji jest alternatywą dla użycia rzeczywistych znaków nowej linii: zakończ każdą-e
część skryptu tam, gdzie normalnie musiałaby iść nowa linia.1 s/foo/bar/
zastępuje tylkofoo
w 1. linii, jeśli ją tam znajdziesz. Jeśli tak,t
rozgałęzia się do końca skryptu (pomija pozostałe polecenia w wierszu). (t
Funkcja rozgałęzia się na etykietę tylko wtedy, gdy ostatnies
wywoł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ż jest2
.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
„s0,/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 :
Technika pętli 2, tylko dla małych plików : wczytaj całą zawartość do pamięci, a następnie wykonaj na niej pojedyncze podstawienie .
1 1.61803 podaje przykłady tego, co dzieje się
1,/re/
z następującymi i późniejs//
:-
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
plonami$'1bar\n2bar'
; tzn. obie linie zostały zaktualizowane, ponieważ numer linii1
pasuje 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, as/foo/bar/
zastępowanie odbywa się na obu z nich.-
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
kończy się niepowodzeniem : w przypadkused: first RE may not be empty
(BSD / macOS) ised: -e expression #1, char 0: no previous regular expression
(GNU), ponieważ w czasie przetwarzania pierwszego wiersza (z powodu numeru wiersza1
rozpoczynającego zakres) nie zastosowano jeszcze wyrażenia regularnego, więc//
nie odnosi się do niczego.Z wyjątkiem
sed
specjalnej0,/re/
składni GNU , każdy zakres rozpoczynający się od numeru linii skutecznie wyklucza użycie//
.źródło
Możesz użyć awk, aby zrobić coś podobnego ...
Wyjaśnienie:
Uruchamia instrukcję akcji między {}, gdy wiersz pasuje do „#include”, a my jeszcze jej nie przetworzyliśmy.
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ń.
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 :-)
źródło
awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
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
w wersji innej niż GNU będzie musiało być
Jednak ta wersja nie będzie działać z gnu sed.
Oto wersja, która działa zarówno:
dawny:
źródło
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
#include
znajduje się w linii 1, to zarówno linia 1, jak i następna następna#include
będą miały linię poprzedzającą. Jeśli używasz GNUsed
, ma rozszerzenie, w którym0,/^#include/
(zamiast1,
) zrobi to dobrze.źródło
Wystarczy dodać liczbę wystąpień na końcu:
źródło
sed
określa komendę substytucyjną za pomocą:[2addr]s/BRE/replacement/flags
i 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ńcowe1
nie jestsed
rozszerzeniem 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.Możliwe rozwiązanie:
Wyjaśnienie:
źródło
sed: file me4.sed line 4: ":" lacks a label
Wiem, że to stary post, ale miałem rozwiązanie, z którego korzystałem:
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 =
#include
i zamień =blah
i pierwsze wystąpienie grep znajdzie się w linii 5, to dane przesyłane do ostatniej sed byłyby5s/.*/blah/
.Działa, nawet jeśli pierwsze wystąpienie jest w pierwszej linii.
źródło
sed -f -
a niektóre nie, ale możesz to obejść :)Jeśli ktoś przyszedł tutaj, aby zastąpić znak po raz pierwszy we wszystkich liniach (tak jak ja), użyj tego:
Zmieniając na przykład 1 na 2, możesz zamiast tego zastąpić tylko wszystkie sekundy.
źródło
's/a/b/'
znaczymatch a
ido just first match
for every matching line
Dzięki opcji GNU sed
-z
możesz przetwarzać cały plik tak, jakby był tylko jedną linią. W ten sposób as/…/…/
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ą-z
opcjąsed
traktuje cały plik jako pojedynczą linię.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ć jakos/text[^\n]*//
.[^\n]
dopasowuje wszystko oprócz znaku nowej linii.[^\n]*
dopasuje wszystkie symbole po,text
aż do osiągnięcia nowej linii.s/^text//
można przepisać jakos/(^|\n)text//
.s/text$//
można przepisać jakos/text(\n|$)//
.źródło
zrobiłbym to za pomocą skryptu awk:
następnie uruchom go z awk:
może być niechlujny, jestem nowy w tym.
źródło
Jako alternatywną sugestię warto przyjrzeć się
ed
poleceniu.źródło
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:
Zmienia tylko pierwsze wystąpienie.
${nowms}
to czas w milisekundach ustawiony przez skrypt Perla,$counter
jest 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ępnies/====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.
źródło
Korzystając z FreeBSD
ed
i unikajed
błędu „brak dopasowania” w przypadku, gdyinclude
w pliku nie ma instrukcji do przetworzenia:źródło
Może to działać dla ciebie (GNU sed):
lub jeśli pamięć nie stanowi problemu:
źródło
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:
Plik źródłowy (source.txt)
Plik wynikowy (output.txt)
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
źródło
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.Nic nowego, ale może bardziej konkretna odpowiedź:
sed -rn '0,/foo(bar).*/ s%%\1%p'
Przykład:
xwininfo -name unity-launcher
produkuje dane wyjściowe takie jak:Wyodrębnianie identyfikatora okna za pomocą
xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'
:źródło
POSIXly (również ważny w sed), Użyto tylko jednego wyrażenia regularnego, potrzeba pamięci tylko dla jednej linii (jak zwykle):
Wyjaśnione:
źródło
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.
źródło
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:
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
-include
do tego celu.źródło
źródło