Wstaw linię po pierwszym dopasowaniu za pomocą sed

245

Z jakiegoś powodu nie mogę znaleźć prostej odpowiedzi na to pytanie i jestem w tej chwili trochę chwiejny. Jak mógłbym wstawić wiersz wyboru tekstu po pierwszym wierszu pasującym do określonego ciągu za pomocą sedpolecenia. Mam ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

Chcę wstawić linię po CLIENTSCRIPT=linii, co spowoduje ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
użytkownik2150250
źródło

Odpowiedzi:

379

Spróbuj to zrobić za pomocą GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

jeśli chcesz zastąpić w miejscu , użyj

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Wynik

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

Doc

  • zobacz sed doc i szukaj \a(dołącz)
Gilles Quenot
źródło
Dzięki! Czy chcesz, aby zapisał się z powrotem do pliku bez drukowania zawartości pliku?
user2150250
11
Zauważ, że oba zakładają GNU sed. To nie jest standardowa sedskładnia i nie będzie działać z żadną inną sedimplementacją.
Stephane Chazelas
Miałem problem z uruchomieniem tego dla mnie, ponieważ użyłem innego separatora. Po zakończeniu RTFM zdałem sobie sprawę, że muszę rozpocząć wyrażenie regularne z \, a następnie z ogranicznikiem.
Christy,
10
Jak to dotyczy TYLKO pierwszego meczu? jasne jest, że dołącza tekst po meczu, ale skąd on wie tylko o pierwszym dopasowaniu?
Mohammed Noureldin
7
Dla mnie to dodaje tekst po każdym meczu.
schoppenhauer,
49

Zwróć uwagę na standardową sedskładnię (jak w POSIX, więc obsługiwaną przez wszystkie zgodne sedimplementacje wokół (GNU, OS / X, BSD, Solaris ...)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

Lub w jednym wierszu:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

( -expressions (i zawartość -fplików) są łączone z nowymi wierszami, aby uzupełnić sedinterpretacje skryptu sed ).

-iOpcja edycji w miejscu jest również rozszerzenie GNU, niektóre inne implementacje (np FreeBSD) poparcie -i ''dla tego.

Możesz też perlzamiast tego użyć przenośności :

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

Lub możesz użyć edlub ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Stephane Chazelas
źródło
2
Mogę się mylić, ale prąd sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' fileucieka cytatowi na końcu pierwszego parametru i przerywa polecenie.
Opuszczony
1
@AbandonedCart, w muszlach Bourne'a cshlub rcrodziny, '...'to mocne cytaty, w których ukośnik odwrotny nie jest wyjątkowy. Jedyny znany mi wyjątek to fishpowłoka.
Stephane Chazelas,
1
Terminal na MacOS (a następnie skrypt uruchamiany w terminalu) również najwyraźniej jest wyjątkiem. Znalazłem alternatywną składnię, ale i tak dzięki.
Opuszczony wózek
1
@AbandonedCart, to coś innego. To macOS sednie jest tutaj zgodny z POSIX. To sprawia, że ​​moje stwierdzenie, że jest przenośne, jest niepoprawne (dla wariantu z jedną linią). Poproszę grupę otwartą o potwierdzenie, czy rzeczywiście jest to niezgodność lub błędna interpretacja normy z mojej strony.
Stephane Chazelas,
19

Zgodny z POSIX za pomocą spolecenia:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
źródło
1
... który obsługuje nawet wstawianie nowych linii. Kocham to!
Stephan Henningsen
1
Zobacz także, sed -e '/.../s/$/\' -e 'CLI.../'które pozwoliłyby uniknąć problemów z wierszami zawierającymi sekwencje bajtów, które nie tworzą prawidłowych znaków.
Stephane Chazelas
14

Komenda Sed, która działa zarówno na MacOS (przynajmniej OS 10), jak i na Uniksie (tzn. Nie wymaga gnu sed jak Gilles (obecnie akceptowany)):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

Działa to w bashu i być może także w innych powłokach, które znają styl cytowania oceny $ '\ n'. Wszystko może znajdować się w jednej linii i działać w starszych komendach / POSIX sed. Jeśli może być wiele wierszy pasujących do CLIENTSCRIPT = "foo" (lub odpowiednik) i chcesz dodać dodatkowy wiersz tylko za pierwszym razem, możesz go przerobić w następujący sposób:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(tworzy to pętlę po kodzie wstawiania wiersza, który po prostu przewija resztę pliku, nigdy nie wracając do pierwszej komendy sed).

Możesz zauważyć, że dodałem „^ *” do pasującego wzorca, na wypadek gdyby linia ta pojawiła się w komentarzu, powiedzmy lub była wcięta. Nie jest w 100% idealny, ale obejmuje niektóre inne sytuacje, które mogą być powszechne. Dostosuj zgodnie z wymaganiami ...

Te dwa rozwiązania również rozwiązują problem (ogólne rozwiązanie dodawania linii), że jeśli twoja nowa wstawiona linia zawiera nieokreślone ukośniki odwrotne lub znaki ampersand, zostaną one zinterpretowane przez sed i prawdopodobnie nie wyjdą tak samo, jak \n jest np. \0byłaby dopasowana pierwsza linia. Szczególnie przydatny, jeśli dodajesz wiersz pochodzący ze zmiennej, w którym inaczej musiałbyś uciec od wszystkiego, używając $ {var //} wcześniej, lub innej instrukcji sed itp.

To rozwiązanie jest nieco mniej nieporządne w skryptach (że cytowanie i \ n nie jest łatwe do odczytania), gdy nie chcesz umieszczać tekstu zastępczego polecenia na początku wiersza, jeśli powiedzmy, w funkcji z wcięciami. Skorzystałem z tego, że $ '\ n' jest oceniany przez powłokę do nowego wiersza, nie jest to zwykła wartość \ n "pojedynczego cudzysłowu.

Robi się wystarczająco długo, więc myślę, że perl / nawet awk może wygrać z powodu większej czytelności.

Breezer
źródło
1
Dziękuję za Twoją odpowiedź! Pierwszy działa również jak urok w systemie operacyjnym AIX.
abhishek
jak to zrobić, ale w przypadku wstawiania wielu wierszy?
tatsu
1
POSIX sed obsługuje dosłowne znaki nowej linii w ciągu zastępującym, jeśli „uciekniesz” je odwrotnym ukośnikiem ( źródło ). Więc: s/CLIENTSCRIPT="foo"/&\ [Enter] CLIENTSCRIPT2="hello"/ . Odwrotny ukośnik musi być ostatnim znakiem w linii (bez spacji po nim), w przeciwieństwie do tego, jak to wygląda w tym komentarzu.
TheDudeAbides,
1
@tatsu Newlines w ciągu zastępującym s///, a także w aii poleceniach można „uciec”, stosując tę ​​samą metodę, którą opisałem w moim komentarzu powyżej.
TheDudeAbides,
4

Może trochę za późno, aby opublikować odpowiedź na to pytanie, ale niektóre z powyższych rozwiązań uznałem za nieco kłopotliwe.

Próbowałem prostej zamiany sznurka w sed i zadziałało:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

IZnak odzwierciedla dopasowany ciąg, a następnie dodajesz \ n i nowy wiersz.

Jak wspomniano, jeśli chcesz to zrobić w miejscu:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Inna rzecz. Możesz dopasować za pomocą wyrażenia:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

Mam nadzieję, że to komuś pomoże

siwesam
źródło
Działa to dobrze w systemie Linux, gdzie GNU sed jest domyślny, ponieważ rozumie \nw zastępującym ciągu. Zwykły waniliowy (POSIX) sedma nie rozumieć \nw ciągu zastępowania jednak, więc to nie będzie działać na BSD lub MacOS-chyba że masz zainstalowanych GNU sed jakoś. Z wanilii sed ty może osadzić nowe linie o „ucieczce” je, to znaczy, kończący linię z odwrotnym ukośnikiem, a następnie naciskając [Enter] ( odniesienie , w rubryce o [2addr]s/BRE/replacement/flags). Oznacza to, że twoje polecenie sed musi obejmować wiele linii w terminalu.
TheDudeAbides,
Inną opcją uzyskania dosłownego nowego wiersza w ciągu zastępującym jest użycie funkcji cytowania ANSI-C w Bash, jak wspomniano w jednej z pozostałych odpowiedzi .
TheDudeAbides,
1

Miałem podobne zadanie i nie byłem w stanie uruchomić powyższego rozwiązania perla do pracy.

Oto moje rozwiązanie:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Wyjaśnienie:

Używa wyrażenia regularnego do wyszukiwania linii w moim pliku /etc/mysql/my.cnf, który zawierał tylko [mysqld]i zastąpił go

[mysqld] collation-server = utf8_unicode_ci

skutecznie dodając collation-server = utf8_unicode_ciwiersz po wierszu zawierającym [mysqld].

Caleb
źródło
0

Ostatnio musiałem to zrobić zarówno dla systemów Mac, jak i Linux. Po przejrzeniu wielu postów i wypróbowaniu wielu rzeczy, moim zdaniem nigdy nie dotarłem do miejsca, w którym chciałem: wystarczająco prostego, aby zrozumieć rozwiązanie przy użyciu dobrze znanego oraz standardowe polecenia z prostymi wzorami, jedną wkładką, przenośne, z możliwością rozbudowy, aby dodać więcej ograniczeń. Potem spróbowałem spojrzeć na to z innej perspektywy, wtedy zdałem sobie sprawę, że mogę obejść się bez opcji „one-liner”, jeśli „2-liner” spełni resztę moich kryteriów. Na koniec wymyśliłem to rozwiązanie, które podoba mi się, że działa zarówno na Ubuntu, jak i na Macu, które chciałem udostępnić wszystkim:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

W pierwszym poleceniu grep szuka numerów linii zawierających „foo”, cut / head wybiera 1. wystąpienie, a operacje arytmetyczne zwiększają numer pierwszego wystąpienia o 1, ponieważ chcę wstawić po wystąpieniu. W drugim poleceniu jest to lokalna edycja pliku „i” do wstawienia: ansi-c cytujący nowy wiersz, „bar”, a następnie kolejny nowy wiersz. Rezultatem jest dodanie nowej linii zawierającej „pasek” po linii „foo”. Każde z tych 2 poleceń można rozszerzyć do bardziej złożonych operacji i dopasowywania.

momonari8
źródło