Ucieczka z łańcucha, aby uzyskać wzór zastępowania sed


317

W moim skrypcie bash mam zewnętrzny (otrzymany od użytkownika) ciąg znaków, którego powinienem użyć we wzorcu sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

Jak mogę uciec od $REPLACEłańcucha, aby został bezpiecznie zaakceptowany sedjako dosłowny zamiennik?

UWAGA: To KEYWORDgłupie podłoże bez pasujących elementów itp. Nie jest dostarczane przez użytkownika.


13
Czy próbujesz uniknąć problemu „Małe tabele Bobby'ego”, jeśli mówią „/ g -e 's / PASSWORD =. * / PASSWORD = abc / g”?
Paul Tomblin

2
Jeśli używasz bash, nie potrzebujesz sed. Wystarczy użyćoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson

@destenson Myślę, że nie powinieneś umieszczać dwóch zmiennych poza cudzysłowami. Bash może odczytywać zmienne w cudzysłowach (w twoim przykładzie białe znaki mogą popsuć).
Camilo Martin,


1
@CamiloMartin, zobacz mój komentarz do mojej własnej odpowiedzi. Cytaty wewnątrz $ {} nie pasują do cytatów w środku. Te dwie zmienne nie są poza cudzysłowami.
destenson

Odpowiedzi:


268

Ostrzeżenie : nie uwzględnia to nowych linii. Aby uzyskać bardziej szczegółową odpowiedź, zobacz to pytanie SO . (Dzięki, Ed Morton i Niklas Peter)

Pamiętaj, że ucieczka od wszystkiego to zły pomysł. Sed potrzebuje wielu znaków cytowania, aby mieć się ich specjalne znaczenie. Na przykład, jeśli wybierzesz cyfrę w ciągu zastępującym, zmieni się ona w odniesienie wsteczne.

Jak powiedział Ben Blank, tylko trzy znaki muszą być poprzedzone znakiem zastępującym (same znaki ucieczki, ukośnik dla końca instrukcji i & dla zamiany wszystkich):

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

Jeśli kiedykolwiek będziesz musiał uciec z KEYWORDłańcucha, potrzebujesz tego:

sed -e 's/[]\/$*.^[]/\\&/g'

I mogą być używane przez:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

Pamiętaj, że jeśli używasz znaku innego niż /separator, musisz zastąpić ukośnik w wyrażeniach powyżej używanym znakiem. Wyjaśnienie znajduje się w komentarzu PeterJCLaw.

Edytowane: Z powodu niektórych przypadków narożników, których wcześniej nie uwzględniono, powyższe polecenia zmieniły się kilka razy. Sprawdź historię edycji, aby uzyskać szczegółowe informacje.


17
Warto zauważyć, że można uniknąć konieczności ucieczki przed ukośnikami, nie używając ich jako ograniczników. Większość (wszystkich?) Wersji sed pozwala ci na użycie dowolnej postaci, o ile pasuje ona do wzoru: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' nie działało dla mnie w OSX, ale działa: sed 's / ([\\\ / &]) / \\ & / g 'i jest nieco krótszy.
jcoffland,

1
Dla wzorca wyszukiwania KEYWORD, w GNU sed , są jeszcze 2 znaki ^, $nie wymienione powyżej:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: Naprawiono. W rzeczywistości jest to błąd, przed którym ostrzegam w pierwszym akapicie. Chyba nie praktykuję tego, co głosię.
Pianozaur

1
@NeronLeVelu: Nie jestem pewien, czy wiem, co masz na myśli, ale „nie ma specjalnego znaczenia w potokach ani zmiennych. Jest przetwarzane przez powłokę przed uruchomieniem wyniku, więc podwójne cudzysłowy w zmiennych są bezpieczne. Na przykład spróbuj uruchomić A='foo"bar' echo $A | sed s/$A/baz/w Podwójne cytaty są traktowane jak „foo” i „bar” wokół nich
Pianozaur

92

Komenda sed pozwala używać innych znaków zamiast /jako separatora:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Podwójne cudzysłowy nie stanowią problemu.


5
Nadal musisz uciec, .co inaczej ma specjalne znaczenie. Zredagowałem twoją odpowiedź.
ypid

Właśnie próbowałem zrobić: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filez sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filei to nie robi tego samego.
Dimitri Kopriwa

1
Ponieważ dotyczy to tylko podstawiania, powinno to brzmieć: sKomenda (jak w zastępstwie) sed pozwala na użycie innych znaków zamiast / jako separatora. Byłaby to również odpowiedź na to, jak używać sed na adresie URL ze znakami ukośnika. Nie odpowiada na pytanie OP, jak uciec od łańcucha wprowadzonego przez użytkownika, który może zawierać /, \, ale także #, jeśli zdecydujesz się go użyć. A poza tym URI może zawierać #
papo

2
zmieniło moje życie! Dziękuję Ci!
Franciscon Santos,

48

Jedynymi trzema dosłownymi znakami, które są traktowane specjalnie w klauzuli zastępującej, są /(aby zamknąć klauzulę), \(aby uciec od znaków, referencje zwrotne i c.) Oraz &(aby uwzględnić dopasowanie w zamianie). Dlatego wszystko, co musisz zrobić, to uciec od tych trzech znaków:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Przykład:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar

Myślę też, że jest to nowa linia. Jak mogę uciec od nowej linii?
Alexander Gladysh

2
Uważaj, jakie jest domyślne zachowanie echa w odniesieniu do odwrotnych ukośników. W bash echo domyślnie nie interpretuje znaków ucieczki odwrotnego ukośnika, co służy temu celowi. Z drugiej strony w myślniku (sh) echo interpretuje ucieczki odwrotne i, o ile wiem, nie ma możliwości ich stłumienia. Dlatego w myślniku (sh) zamiast echa $ x, wykonaj printf '% s \ n' $ x.
Youssef Eldakar,

Ponadto zawsze używaj opcji -r podczas odczytu, aby traktować ukośniki odwrotne w danych wejściowych użytkownika jako literały.
Youssef Eldakar,

Aby uzyskać zgodność między platformami z innymi powłokami, powinieneś zapoznać się z tym dokumentem w sprawie zamiany znaków specjalnych sed: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux Trzy znaki są jedynymi znakami specjalnymi w klauzuli replace . Znacznie więcej jest wyjątkowych w klauzuli wzorca.
lenz

33

Na podstawie wyrażeń regularnych Pianozaura stworzyłem funkcję bash, która unika zarówno słowa kluczowego, jak i zamiany.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Oto jak go używasz:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

3
dzięki! jeśli ktoś dostaje błąd składni, gdy próbuje go używać, tak jak ja, ale należy pamiętać, aby uruchomić go za pomocą bash, nie sh
Konstantin Pereiaslov

1
Czy istnieje funkcja pozwalająca na uniknięcie łańcucha dla sed zamiast owijania się wokół sed?
CMCDragonkai

Hej, tylko ogólne ostrzeżenie dotyczące uruchamiania potoków za pomocą echa: Niektóre (większość?) Implementacje echa pobierają opcje (patrz man echo), powodując, że potok zachowuje się nieoczekiwanie, gdy twój argument $1zaczyna się od myślnika. Zamiast tego możesz rozpocząć swoją fajkę printf '%s\n' "$1".
Pianozaur

17

Trochę późno jest odpowiedzieć ... ale jest O wiele prostszy sposób, aby to zrobić. Wystarczy zmienić ogranicznik (tj. Znak oddzielający pola). Zamiast tego s/foo/bar/pisz s|bar|foo.

Oto prosty sposób:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

Wynikowy wynik jest pozbawiony tej paskudnej klauzuli DEFINER.


10
Nie, &i `` wciąż trzeba uciec, podobnie jak separator, cokolwiek zostanie wybrane.
mirabilos

3
To rozwiązało mój problem, ponieważ miałem znaki „/” w ciągu zastępującym. Dzięki stary!
Evgeny Goldin

pracuje dla mnie. Próbuję uciec $w ciągu, który ma zostać zmieniony, i zachować znaczenie $w ciągu zastępującym. powiedz, że chcę zmienić $XXXna wartość zmiennej $YYY, sed -i "s|\$XXX|$YYY|g" filedziała dobrze.
hakunami

11

Okazuje się, że zadajesz złe pytanie. Zadałem też złe pytanie. Przyczyną tego jest początek pierwszego zdania: „W mojej bacie skrypcie ...”.

Miałem to samo pytanie i popełniłem ten sam błąd. Jeśli używasz bash, nie musisz używać seda, aby zamieniać ciągi (i znacznie łatwiej jest korzystać z funkcji zamiany wbudowanej w bash).

Zamiast czegoś takiego jak na przykład:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

możesz korzystać wyłącznie z funkcji bash:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"

BTW, podświetlanie składni tutaj jest nieprawidłowe. Cytaty zewnętrzne pasują do siebie, a cytaty wewnętrzne pasują do siebie. Innymi słowy, wygląda $Ai $Bnie jest cytowany, ale tak nie jest. Cytaty wewnątrz ${}nie pasują do cytatów poza nim.
destenson

W rzeczywistości nie musisz cytować prawej strony zadania (chyba że chcesz zrobić coś takiego var='has space') - OUTPUT=${INPUT//"$A"/"$B"}jest bezpieczny.
Benjamin W.

W rzeczywistości nie musisz cytować prawej strony zadania (chyba że chcesz, aby działało w prawdziwym świecie, a nie tylko jako zabawkowy skrypt pokazujący twój szalony skilz). Zawsze próbuję zacytować każde rozszerzenie zmiennej, którego nie chcę, aby powłoka interpretowała, chyba że mam konkretny powód, aby tego nie robić. W ten sposób rzeczy ulegają rzadszemu zepsuciu, zwłaszcza gdy są dostarczane nowe lub nieoczekiwane dane wejściowe.
destenson

1
Patrz instrukcja : „Wszystkie wartości podlegają interpretacji tyldy, interpretacji parametrów i zmiennych, zastępowaniu poleceń, interpretacji arytmetycznej i usuwaniu cytatów (szczegółowo poniżej)”. Tj. To samo co w podwójnych cudzysłowach.
Benjamin W.

1
Co zrobić, jeśli chcesz użyć sed na pliku?
Efren,

1

Użyj awk - jest czystszy:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

2
Problem awkpolega na tym, że nie ma nic podobnego sed -i, co jest niezwykle przydatne w 99% przypadków.
Tino

Jest to krok we właściwym kierunku, ale awk nadal interpretuje niektóre metaznaki w twoim podstawieniu, więc nadal nie jest bezpieczny dla użytkownika.
Jeremy Huiskamp

0

Oto przykład AWK, którego użyłem jakiś czas temu. Jest to AWK, który drukuje nowe AWKS. Ponieważ AWK i SED są podobne, może to być dobry szablon.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Wygląda na przesadne, ale jakoś ta kombinacja cytatów sprawia, że ​​„drukowane są jako literały. Więc jeśli dobrze pamiętam, zmienne są otoczone takimi cytatami: „1 $”. Wypróbuj, daj mi znać, jak to działa z SED.


0

Mam ulepszenie w stosunku do funkcji sedeasy, która BĘDZIE łamana znakami specjalnymi, takimi jak tab.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Czym się różni? $1i $2zawinięte w cudzysłów, aby uniknąć rozszerzenia powłoki i zachować tabulatory lub podwójne spacje.

Dodatkowe potokowanie | sed -e 's:\t:\\t:g'(lubię :jako token), które przekształca kartę \t.


Ale zobacz mój komentarz do dziwnej odpowiedzi dotyczącej używania echa w rurach.
Pianozaur

0

Oto kody ucieczki, które znalazłem:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

-1

nie zapomnij o całej przyjemności związanej z ograniczeniem powłoki wokół „i”

więc (w ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

dokładnie kierunek, w którym byłem potrzebny, do unikania wyników wyszukiwania, znalezionych przez google, więc może być pomocny dla kogoś - skończyłem na - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg,

-1

Jeśli chcesz zastąpić zmienną w poleceniu sed, po prostu usuń Przykład:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test

-2

Jeśli zdarza się, że generujesz losowe hasło, które ma zostać przekazane w celu sedzastąpienia wzorca, wybierz ostrożność przy wyborze zestawu znaków w losowym ciągu. Jeśli wybierzesz hasło utworzone przez zakodowanie wartości jako base64, wówczas istnieje tylko znak, który jest możliwy zarówno w base64, jak i znak specjalny we sedwzorcu zastępowania. Ten znak to „/” i można go łatwo usunąć z generowanego hasła:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

-4

Łatwiejszym sposobem na to jest zbudowanie łańcucha przed użyciem i użycie go jako parametru dla sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

Zawodzi i jest bardzo niebezpieczny, ponieważ REPLACE jest dostarczany przez użytkownika: REPLACE=/dajesed: -e expression #1, char 12: unknown option to `s'
Tino
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.