Czy istnieje sposób, aby uniemożliwić sedowi interpretację ciągu zastępującego? [Zamknięte]

16

Jeśli chcesz zastąpić słowo kluczowe ciągiem znaków za pomocą sed, sed próbuje zinterpretować ciąg zastępujący. Jeśli zastępujący ciąg znaków zawiera znaki, które sed uważa za specjalne, takie jak znak „/”, to zawiedzie, chyba że oczywiście miałeś na myśli, że zastępujący ciąg znaków ma znaki, które mówią sedowi, jak postępować.

Dawny:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

Czy jest jakiś sposób, aby powiedzieć sedowi, aby nie próbował interpretować ciągu zastępującego znaki specjalne? Chcę tylko móc zastąpić słowo kluczowe w pliku zawartością zmiennej, bez względu na to, co to jest.

Tal
źródło
Jeśli chcesz wstawiać znaki specjalne sedi nie być wyjątkowymi, po prostu uciec od nich ukośnikiem odwrotnym. VAR='hi\/'nie daje takiego problemu.
Wildcard
6
Dlaczego wszystkie opinie? Wydaje mi się, że pytanie jest całkowicie uzasadnione
roaima,
sed(1)po prostu interpretuje to, co dostaje. W twoim przypadku jest to możliwe dzięki interpolacji powłoki. Uważam, że nie możesz robić tego, co chcesz, ale sprawdź instrukcję. Wiem, że w Perlu (który jest znośnym sedzamiennikiem, ze znacznie bogatszymi wyrażeniami regularnymi) możesz określić, że ciąg ma być brany dosłownie, ponownie sprawdź instrukcję.
vonbrand
powiązane stackoverflow.com/questions/407523/...
Ciro Santilli 28 病毒 审查 六四 事件 法轮功

Odpowiedzi:

5

Możesz używać Perla zamiast sed z -p(zakładaj pętlę nad wejściem) i -e(podaj program w wierszu poleceń). Dzięki Perlowi możesz uzyskać dostęp do zmiennych środowiskowych bez interpolacji ich w powłoce. Pamiętaj, że zmienną należy wyeksportować :

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

Jeśli nie chcesz eksportować zmiennej wszędzie, podaj ją tylko dla tego procesu:

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Zauważ, że składnia wyrażeń regularnych Perla domyślnie nieco różni się od składni sed.

Antti Haapala
źródło
Wydawało się to bardzo obiecujące, ale podczas testowania pojawia się błąd „Zbyt długa lista argumentów”, ponieważ mój ciąg zastępujący jest zbyt długi, co ma sens - przy użyciu tej metody wykorzystujemy cały ciąg zastępujący jako część argumentów, które podajemy na perla, więc istnieje limit czasu, jaki może to być.
Tal
1
Nie, przejdzie do PATTERN zmiennej środowiskowej , a nie argumentów. W każdym razie ten błąd byłby E2BIG, który dostaniesz równie dobrze, jeśli użyjesz sed.
Antti Haapala
4

Są tylko 4 znaki specjalne części zamiennej: \, &, znak nowej linii i separator ( ref )

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX
Glenn Jackman
źródło
Ma to ten sam problem co rozwiązanie Antti - jeśli ciąg zastępujący przekroczy określoną długość, pojawi się błąd „Zbyt długa lista argumentów”. A co jeśli ciąg zastępujący ma „[”, „]”, „*”, „.” I inne takie znaki? Czy sed naprawdę ich nie zinterpretuje?
Tal
Bok wymiana s///jest nie wyrażenie regularne, to naprawdę tylko ciąg (z wyjątkiem backslash-ucieczek i &). Jeśli zastępujący ciąg jest tak długi, jednowarstwowa powłoka nie jest twoim rozwiązaniem.
glenn jackman
Bardzo przydatna lista, jeśli na przykład zamiennym ciągiem jest tekst zakodowany w standardzie base64 (np. Zastępowanie symbolu zastępczego kluczem SHA256). To tylko ogranicznik, o który należy się martwić.
Heath Raftery
2

Najprostszym rozwiązaniem, które nadal poprawnie obsługiwałoby znaczną większość wartości zmiennych, byłoby użycie znaku niedrukowalnego jako separatora sedpolecenia zastępczego.

W vimożna uciec od dowolnego znaku kontrolnego, naciskając Ctrl-V (częściej zapisywany jako ^V). Więc jeśli użyjesz jakiegoś znaku kontrolnego (często używam ^Ajako ogranicznik w tych przypadkach), twoje sedpolecenie zostanie złamane tylko, jeśli ten niedrukowalny znak jest obecny w zmiennej, w której upuszczasz.

Więc wpisz "s^V^AKEYWORD^V^A$VAR^V^Ag"i co byś (w vi) wyglądałby:

sed "s^AKEYWORD^A$VAR^Ag" somefile

Działa to tak długo, jak długo $VARnie zawiera znaku niedrukowalnego ^A- co jest niezwykle mało prawdopodobne.


Oczywiście, jeśli przekazujesz wartość wejściową od użytkownika $VAR, wszystkie zakłady są wyłączone i lepiej oczyść swoje dane wejściowe, zamiast polegać na trudnych do wpisania znakach kontrolnych dla przeciętnego użytkownika.


Jednak w rzeczywistości należy uważać na więcej niż ciąg ogranicznika. Na przykład, &gdy występuje w ciągu zastępującym, oznacza „cały dopasowany tekst”. Np. s/stu../my&/Zamieniłbym „stuff” na „mystuff”, „stung” na „mystung” itp. Więc jeśli możesz mieć dowolny znak w zmiennej, którą upuszczasz jako ciąg zastępczy, ale chcesz użyć literału tylko wartość zmiennej, musisz wykonać pewne operacje dezynfekcji danych, zanim będziesz mógł użyć zmiennej jako łańcucha zastępczego w sed. (Jednak dezynfekcję danych można również wykonać sed.)

Dzika karta
źródło
Właśnie o to mi chodzi - zastąpienie łańcucha innym łańcuchem to bardzo prosta operacja. Czy to naprawdę musi być tak skomplikowane, jak ustalenie, które postacie sed nie polubią, i użycie sed do oczyszczenia własnych danych? To brzmi śmiesznie i niepotrzebnie. Nie jestem profesjonalnym programistą, ale jestem prawie pewien, że potrafię napisać małą funkcję, która zastępuje słowo kluczowe ciągiem w prawie dowolnym języku, w jakim kiedykolwiek się zetknąłem, w tym bash - miałem tylko nadzieję na prosty Linux rozwiązanie wykorzystujące istniejące narzędzia - nie mogę uwierzyć, że takiego nie ma.
Tal
1
@Tal, jeśli ciąg zastępujący ma długość „setek stron”, jak wspomniałeś w innym komentarzu ... trudno nazwać go „prostym” przypadkiem użycia. Nawiasem mówiąc, odpowiedź brzmi: Perl - po prostu nie nauczyłem się Perla. Złożoność wynika stąd, że chcesz zezwolić na DOWOLNE dowolne dane wejściowe jako ciąg zastępczy w wyrażeniu regularnym .
Wildcard
Istnieje wiele innych rozwiązań, z których możesz skorzystać, wiele z nich jest bardzo prostych. Na przykład, jeśli ciąg zastępcza linia oparta jest faktycznie i nie musi być umieszczony w środku linii, użycie sed„s idowodzenia nsert. Ale sednie jest dobrym narzędziem do przetwarzania ogromnych ilości tekstu w złożony sposób. Opublikuję kolejną odpowiedź pokazującą, jak to zrobić awk.
Wildcard
1

Możesz zamiast tego użyć a ,lub |a, to zajmie to jako separator i technicznie możesz użyć wszystkiego

ze strony podręcznika

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

Jak widać, powinieneś zacząć od \ przed separatorem na początku, możesz użyć go jako separatora.

z dokumentacji http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022- Polecenie :

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

Przykład:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"

użytkownik3566929
źródło
Mówisz o zezwoleniu na użycie jednego, określonego znaku w ciągu zastępującym - w tym przypadku „/”. Mówię o zapobieganiu całkowitej interpretacji ciągu zastępującego. Bez względu na to, jakiego znaku użyjesz („/”, „,”, „|” itd.), Zawsze ryzykujesz pojawieniem się tego znaku w ciągu zastępującym. Poza tym początkowa postać nie jest jedyną postacią specjalną, o którą dba sed, prawda?
Tal
@Tal nie, zamiast tego może wziąć wszystko /i zignoruje to z /radością, jak właśnie wskazałem .. w rzeczywistości możesz nawet poszukać go i zastąpić ciągiem >>> edytowałem z przykładem >>> te rzeczy nie są tak bezpieczne i zawsze znajdziesz mądrzejszego
kolesia
@Tal, dlaczego chcesz temu zapobiec? Mam na myśli, że jest to sedpo pierwsze, jaki jest twój projekt?
user3566929,
Wszystko, czego potrzebuję, to zastąpić słowo kluczowe ciągiem. sed wydaje się być zdecydowanie najczęstszym sposobem, aby to zrobić w systemie Linux. Ciąg może mieć długość 100 stron. Nie chcę próbować dezynfekować sznurka, aby sed nie przestraszył się podczas czytania - chcę, aby mógł obsłużyć dowolne znaki w sznurku, a przez „uchwyt” mam na myśli, że nie próbuję znaleźć magii znaczenie wewnątrz.
Tal
1
@Tal NIEbash jest przeznaczony do manipulacji ciągami. W ogóle, w ogóle, w ogóle. Służy do manipulacji plikami i koordynacji poleceń . Zdarza się, aby mieć jakiś wbudowany w poręcznej funkcji ciągów, ale bardzo ograniczone i nie bardzo szybko, jeśli w ogóle to najważniejsze robisz. Zobacz „Dlaczego używanie pętli powłoki do przetwarzania tekstu jest uważane za złą praktykę?” Niektóre narzędzia, które przeznaczone do przetwarzania tekstu są w kolejności od najprostszych do najbardziej wydajne: , i Perl. sedawk
Wildcard
1

Jeśli jest oparty na linii i tylko jeden wiersz do zastąpienia, zalecam wcześniejsze przygotowanie samego pliku za pomocą wiersza zastępującego printf, przechowywanie pierwszego wiersza w polu sedwstrzymania i upuszczanie go w razie potrzeby. W ten sposób nie musisz się martwić o znaki specjalne. (Jedynym założeniem tutaj jest to, że $VARzawiera jeden wiersz tekstu bez żadnych nowych linii, co już powiedziałeś w komentarzach.) Poza nowymi liniami, VAR może zawierać cokolwiek i to zadziała niezależnie.

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'wypisze zawartość $VARjako ciąg dosłowny, niezależnie od jego zawartości, a następnie nowy wiersz. ( echow niektórych przypadkach zrobi inne rzeczy, na przykład jeśli treść $VARzaczyna się od myślnika - zostanie to zinterpretowane jako przekazanie flagi opcji echo).

Nawiasy klamrowe są używane do dodania wyjścia printfdo zawartości po somefilejej przekazaniu sed. Ważna jest tutaj biała spacja oddzielająca nawiasy klamrowe, podobnie jak średnik przed zamykającym nawias klamrowy.

1{h;d;};jako sedkomenda będzie przechowywać pierwszą linię tekstu w sed„s miejsca przechowywania , a następnie dsuĹ linię (zamiast drukowanie).

/KEYWORD/stosuje następujące działania do wszystkich wierszy, które zawierają KEYWORD. Akcja jest get, która pobiera zawartość przestrzeni wstrzymania i upuszcza ją w miejsce przestrzeni wzorca - innymi słowy, całą bieżącą linię. (To nie jest zamiana tylko części linii.) Nawiasem mówiąc, przestrzeń wstrzymania nie jest opróżniana, po prostu kopiowana do przestrzeni wzorów, zastępując wszystko, co tam jest.

Jeśli chcesz zakotwiczyć wyrażenie regularne, aby nie pasowało ono do linii, która zawiera tylko SŁOWO KLUCZOWE, ale tylko linię, w której nie ma nic innego oprócz KEYWORD, dodaj początek linii anchor ( ^) i koniec linii anchor ( $) do twoje wyrażenie regularne:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'
Dzika karta
źródło
Wygląda świetnie, jeśli twoja VAR ma jedną linię. Właściwie wspomniałem w komentarzach, że VAR „może mieć 100 stron” zamiast jednej linii. Przepraszam za zamieszanie.
Tal
0

Możesz użyć ukośnika odwrotnego do ukośników w zastępującym ciągu, używając rozszerzenia parametru podstawienia wzorca Basha. Jest to trochę bałagan, ponieważ ukośniki do przodu również muszą być poprzedzone przez Bash.

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

wynik

tha/b/cs a/b/cs a test

Państwo mogli umieścić interpretacji parametrów bezpośrednio do komendy sed:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

ale myślę, że pierwsza forma jest nieco bardziej czytelna. I oczywiście, jeśli zamierzasz ponownie użyć tego samego wzorca zastępowania w wielu poleceniach sed, sensowne jest, aby wykonać konwersję tylko raz.

Inną opcją byłoby użycie skryptu napisanego w awk, perl lub Python, lub program w C, do wykonania zamiany zamiast używania sed.


Oto prosty przykład w Pythonie, który działa, jeśli zastępowane słowo kluczowe jest pełną linią w pliku wejściowym (nie licząc nowej linii). Jak widać, jest to zasadniczo ten sam algorytm, co w przykładzie Bash, ale bardziej efektywnie odczytuje plik wejściowy.

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)
PM 2 Ring
źródło
To tylko kolejny sposób na odkażanie danych wejściowych, a nie świetny, ponieważ obsługuje tylko jeden konkretny znak („/”). Jak zauważył Wildcard, należy się wystrzegać nie tylko ciągu ogranicznika.
Tal
Uczciwe połączenie. Na przykład, jeśli tekst zastępczy zawiera sekwencje specjalne z odwrotnym ukośnikiem, zostaną one zinterpretowane, co może nie być pożądane. Jednym ze sposobów byłoby przekonwertowanie problematycznych znaków (lub całej sprawy) na \xsekwencje specjalne. Lub użyć programu, który może obsłużyć dowolne dane wejściowe, jak wspomniałem w poprzednim akapicie.
PM 2Ring
@Tal: Dodam prosty przykład Pythona do mojej odpowiedzi.
PM 2,
Skrypt Pythona działa świetnie i wydaje się, że robi dokładnie to, co robi moja funkcja, tylko znacznie wydajniej. Niestety, jeśli głównym skryptem jest bash (jak w moim przypadku), wymaga to użycia dodatkowego zewnętrznego skryptu python.
Tal
-1

Tak poszedłem:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

w moim przypadku działa to świetnie, ponieważ moje słowo kluczowe samo w sobie znajduje się w wierszu. Gdyby słowo kluczowe było w linii z innym tekstem, to nie zadziałałoby.

Wciąż chciałbym wiedzieć, czy istnieje prosty sposób, który nie wymaga kodowania własnego rozwiązania.

Tal
źródło
1
Jeśli naprawdę martwisz się postaciami specjalnymi i wytrzymałością, nie powinieneś w ogóle ich używać echo. Użyj printfzamiast tego. A przetwarzanie tekstu w pętli powłoki to zły pomysł.
Wildcard
1
Byłoby pomocne, gdybyś wspomniał w pytaniu, że słowo kluczowe będzie zawsze pełną linią. FWIW, bash readjest raczej powolny. Służy do przetwarzania interaktywnych danych wejściowych użytkownika, a nie przetwarzania plików tekstowych. Jest powolny, ponieważ odczytuje stdin char po char, wykonując wywołanie systemowe dla każdego char.
PM 2, pierścień
@PM 2Ring Moje pytanie nie wspomniało, że słowo kluczowe ma swoją własną linię, ponieważ nie chcę odpowiedzi, która po prostu działa w tak ograniczonej liczbie przypadków - chciałem czegoś, co z łatwością działałoby bez względu na to, gdzie słowo kluczowe był. Nigdy też nie powiedziałem, że mój kod jest wydajny - gdyby tak było, nie szukałbym alternatywy ...
Tal
@Wildcard O ile mi czegoś nie brakuje, printf absolutnie interpretuje znaki specjalne i znacznie bardziej niż domyślne echo. printf "hi\n"sprawi, że printf wydrukuje nowy wiersz, podczas gdy echo "hi\n"wydrukuje go takim, jaki jest.
Tal
@Tal „f” printfoznacza „format” - pierwszym argumentem printfjest specyfikator formatu . Jeśli ten specyfikator %s\noznacza „ciąg po znaku nowej linii”, nic w następnym argumencie nie będzie interpretowane ani tłumaczone printf w ogóle . (Powłoka może nadal ją interpretować; najlepiej wstawić wszystko w pojedyncze cudzysłowy, jeśli jest to dosłowny ciąg, lub podwójne cudzysłowy, jeśli chcesz rozszerzenia zmiennej.) Zobacz moją odpowiedź, używającprintf więcej szczegółów.
Wildcard