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.

Alexander Gladysh
źródło
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,
2
Zobacz także: stackoverflow.com/q/29613304/45375
mklement0
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.

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

scre_www
źródło
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
Ben Blank
źródło
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
Gurpartap Singh
źródło
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.

użytkownik2460464
źródło
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"}"
destenson
źródło
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
Greggster
źródło
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.

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

Francisco De Zuviria
źródło
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
Ark25
źródło
-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"
NeronLeVelu
źródło
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
Shailender Singh
źródło
-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'`;
Mark Stosberg
źródło
-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
Javonne Martin
źródło
Zawodzi i jest bardzo niebezpieczny, ponieważ REPLACE jest dostarczany przez użytkownika: REPLACE=/dajesed: -e expression #1, char 12: unknown option to `s'
Tino