Escape eine Zeichenfolge für ein sed Ersetzungsmuster

316

In meinem Bash-Skript habe ich eine externe (vom Benutzer empfangene) Zeichenfolge, die ich in sed pattern verwenden sollte.

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

Wie kann ich der $REPLACEZeichenfolge entkommen, damit sie sicher sedals wörtlicher Ersatz akzeptiert wird ?

HINWEIS: Das KEYWORDist eine dumme Teilkette ohne Streichhölzer usw. Es ist nicht vom Benutzer bereitgestellt.

Alexander Gladysh
quelle
13
Versuchen Sie, das Problem "Little Bobby Tables" zu vermeiden, wenn dort "/ g -e 's / PASSWORD =. * / PASSWORD = abc / g'" steht?
Paul Tomblin
2
Wenn Sie Bash verwenden, brauchen Sie kein Sed. Verwenden outputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
Sie
@destenson Ich denke, Sie sollten die beiden Variablen nicht außerhalb der Anführungszeichen setzen. Bash kann Variablen in doppelten Anführungszeichen lesen (in Ihrem Beispiel könnte Leerzeichen Fehler verursachen).
Camilo Martin
2
Siehe auch: stackoverflow.com/q/29613304/45375
mklement0
1
@CamiloMartin, siehe meinen Kommentar zu meiner eigenen Antwort. Die Anführungszeichen in $ {} stimmen nicht mit den Anführungszeichen darin überein. Die beiden Variablen stehen nicht außerhalb der Anführungszeichen.
Destenson

Antworten:

267

Warnung : Dies berücksichtigt keine Zeilenumbrüche. Eine ausführlichere Antwort finden Sie stattdessen in dieser SO-Frage . (Danke, Ed Morton & Niklas Peter)

Beachten Sie, dass es eine schlechte Idee ist, alles zu entkommen. Sed braucht viele Charaktere, um entkommen zu können, um ihre besondere Bedeutung zu erhalten. Wenn Sie beispielsweise eine Ziffer in der Ersatzzeichenfolge maskieren, wird daraus eine Rückreferenz.

Wie Ben Blank sagte, müssen nur drei Zeichen in der Ersatzzeichenfolge maskiert werden (maskiert sich selbst, Schrägstrich für das Ende der Anweisung und & für alle ersetzen):

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

Wenn Sie jemals aus der KEYWORDZeichenfolge entkommen müssen, benötigen Sie Folgendes:

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

Und kann verwendet werden von:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(echo $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"

Denken Sie daran, wenn Sie ein anderes Zeichen /als Trennzeichen verwenden, müssen Sie den Schrägstrich in den obigen Ausdrücken durch das von Ihnen verwendete Zeichen ersetzen. Erläuterungen finden Sie im Kommentar von PeterJCLaw.

Bearbeitet: Aufgrund einiger Eckfälle, die zuvor nicht berücksichtigt wurden, haben sich die obigen Befehle mehrmals geändert. Überprüfen Sie den Bearbeitungsverlauf auf Details.

Pianosaurus
quelle
17
Es ist erwähnenswert, dass Sie vermeiden können, den Schrägstrichen entkommen zu müssen, indem Sie sie nicht als Begrenzer verwenden. In den meisten (allen?) Versionen von sed können Sie beliebige Zeichen verwenden, sofern diese zum Muster passen: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw
2
sed -es / (\ / \ | \\\ | &) / \\ & / g 'hat unter OSX bei mir nicht funktioniert, aber dies funktioniert: sed' s / ([\\\ / &]) / \\ & / g 'und es ist etwas kürzer.
Jcoffland
1
Für das Suchmuster KEYWORDin GNU sed sind hier 2 weitere Zeichen ^, $die oben nicht erwähnt wurden:s/[]\/$*.^|[]/\\&/g
Peter.O
1
@ Jesse: Behoben. In der Tat ist dies der Fehler, vor dem ich im ersten Absatz warne. Ich glaube, ich übe nicht, was ich predige.
Pianosaurus
1
@NeronLeVelu: Ich bin nicht sicher , ob ich weiß , was du meinst, aber "in Rohren oder Variablen keine besondere Bedeutung hat Es wird von der Shell analysiert , bevor das Ergebnis läuft, so doppelte Anführungszeichen innerhalb Variablen sicher sind beispielsweise versuchen , ausgeführt wird .. A='foo"bar' echo $A | sed s/$A/baz/In Bash. Die doppelten Anführungszeichen werden genauso behandelt wie die 'foo' und 'bar' um sie herum.
Pianosaurus
92

Mit dem Befehl sed können Sie andere Zeichen anstelle des /Trennzeichens verwenden:

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

Die doppelten Anführungszeichen sind kein Problem.

scre_www
quelle
5
Sie müssen noch entkommen, .was sonst eine besondere Bedeutung hat. Ich habe deine Antwort bearbeitet.
ypid
Ich habe gerade versucht: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filemit sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' fileund das macht nicht das gleiche.
Dimitri Kopriwa
1
Da dies nur für Ersatz gilt, sollte dies lauten: Mit dem sBefehl (wie im Ersatz) von sed können Sie andere Zeichen anstelle von / als Trennzeichen verwenden. Dies wäre auch eine Antwort auf die Verwendung von sed on URL mit Schrägstrichen. Es beantwortet nicht die OP-Frage, wie eine von einem Benutzer eingegebene Zeichenfolge maskiert werden kann, die /, \, aber auch # enthalten könnte, wenn Sie sich dafür entscheiden, diese zu verwenden. Und außerdem kann URI auch # enthalten
Papo
2
es hat mein Leben verändert! Vielen Dank!
Franciscon Santos
48

Die einzigen drei Literalzeichen, die in der Ersetzungsklausel speziell behandelt werden, sind /(um die Klausel zu schließen), \(um Zeichen, Rückreferenzen usw. zu umgehen) und &(um die Übereinstimmung in die Ersetzungsklausel aufzunehmen). Daher müssen Sie nur diesen drei Zeichen entkommen:

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

Beispiel:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
Ben Blank
quelle
Auch eine Newline, denke ich. Wie entkomme ich einer Newline?
Alexander Gladysh
2
Achten Sie auf das Standardverhalten von Echo in Bezug auf Backslashes. In Bash wird für das Echo standardmäßig keine Interpretation von Backslash-Escapezeichen verwendet, was hier den Zweck erfüllt. In dash (sh) hingegen interpretiert Echo Backslash Escape und hat meines Wissens keine Möglichkeit, dies zu unterdrücken. Führen Sie daher in Bindestrich (sh) anstelle von echo $ x printf '% s \ n' $ x aus.
Youssef Eldakar
Verwenden Sie beim Lesen immer die Option -r, um Backslashes in Benutzereingaben als Literale zu behandeln.
Youssef Eldakar
Um die plattformübergreifende Kompatibilität mit anderen Shells zu gewährleisten, sollten Sie dieses Dokument zum Ersetzen sed-Sonderzeichen konsultieren: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton
2
@Drux Die drei Zeichen sind die einzigen Sonderzeichen in der Ersetzungsklausel . Viel mehr ist in der Musterklausel besonders.
Lenz
33

Basierend auf den regulären Ausdrücken von Pianosaurus habe ich eine Bash-Funktion erstellt, die sowohl dem Schlüsselwort als auch der Ersetzung entgeht.

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

So verwenden Sie es:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
Gurpartap Singh
quelle
3
Vielen Dank! Wenn jemand anderes Syntaxfehler bekommt, wenn er versucht, es zu verwenden, genau wie ich, denken Sie daran, es mit bash auszuführen, nicht mit sh
Konstantin Pereiaslov
1
Gibt es eine Funktion, um nur einer Zeichenfolge für sed zu entkommen, anstatt sich um sed zu wickeln?
CMCDragonkai
Hey, nur eine allgemeine Warnung zum Starten von Pipes mit einem Echo wie diesem: Einige (die meisten?) Implementierungen von Echo nehmen Optionen (siehe man echo), was dazu führt, dass sich die Pipe unerwartet verhält, wenn Ihr Argument $1mit einem Bindestrich beginnt. Stattdessen können Sie Ihre Pipe mit beginnen printf '%s\n' "$1".
Pianosaurus
17

Es ist etwas spät zu antworten ... aber es gibt einen viel einfacheren Weg, dies zu tun. Ändern Sie einfach das Trennzeichen (dh das Zeichen, das die Felder trennt). Also, anstatt dass s/foo/bar/du schreibst s|bar|foo.

Und hier ist der einfache Weg, dies zu tun:

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

Die resultierende Ausgabe enthält keine böse DEFINER-Klausel.

user2460464
quelle
10
Nein, &und `` muss noch maskiert werden, ebenso wie das Trennzeichen, je nachdem, was gewählt wird.
Mirabilos
3
Das löste mein Problem, da ich "/" Zeichen in einer Ersatzzeichenfolge hatte. Danke, Mann!
Evgeny Goldin
funktioniert bei mir. Ich versuche, $in der zu ändernden Zeichenfolge zu entkommen und die Bedeutung $der Ersatzzeichenfolge beizubehalten . Angenommen, ich möchte zum $XXXWert der Variablen wechseln $YYY, sed -i "s|\$XXX|$YYY|g" filefunktioniert einwandfrei.
Hakamami
11

Es stellt sich heraus, dass Sie die falsche Frage stellen. Ich habe auch die falsche Frage gestellt. Der Grund, warum es falsch ist, ist der Anfang des ersten Satzes: "In meinem Bash- Skript ...".

Ich hatte die gleiche Frage und machte den gleichen Fehler. Wenn Sie bash verwenden, müssen Sie sed nicht verwenden, um Zeichenfolgen zu ersetzen (und es ist viel sauberer, die in bash integrierte Ersetzungsfunktion zu verwenden).

Anstelle von zum Beispiel:

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")"

Sie können ausschließlich Bash-Funktionen verwenden:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
destenson
quelle
Übrigens ist die Syntaxhervorhebung hier falsch. Die äußeren Anführungszeichen stimmen überein und die inneren Anführungszeichen stimmen überein. Mit anderen Worten, es sieht so aus $Aund $Bist nicht zitiert, aber sie sind es nicht. Die Anführungszeichen innerhalb von ${}stimmen nicht mit den Anführungszeichen außerhalb überein.
Destenson
Sie müssen nicht die rechte Seite einer Aufgabe zitieren (es sei denn, Sie möchten so etwas tun var='has space') - OUTPUT=${INPUT//"$A"/"$B"}ist sicher.
Benjamin W.
Sie müssen nicht unbedingt die rechte Seite einer Aufgabe zitieren (es sei denn, Sie möchten, dass sie in der realen Welt funktioniert und nicht nur als Spielzeugskript, um Ihre verrückten Fähigkeiten zu zeigen). Ich versuche immer, jede Variablenerweiterung zu zitieren, die die Shell nicht interpretieren soll, es sei denn, ich habe einen bestimmten Grund, dies nicht zu tun. Auf diese Weise neigen Dinge dazu, weniger häufig zu brechen, insbesondere wenn sie mit neuen oder unerwarteten Eingaben versehen werden.
Destenson
1
Siehe Handbuch : "Alle Werte werden einer Tilde-Erweiterung, einer Parameter- und Variablenerweiterung, einer Befehlssubstitution, einer arithmetischen Erweiterung und einer Entfernung von Anführungszeichen unterzogen ( siehe unten)." Das heißt, das gleiche wie in doppelten Anführungszeichen.
Benjamin W.
1
Was ist, wenn Sie sed für eine Datei verwenden müssen?
Efren
1

Verwenden Sie awk - es ist sauberer:

$ 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
quelle
2
Das Problem dabei awkist, dass es nichts Ähnliches gibt sed -i, was in 99% der Fälle äußerst praktisch ist.
Tino
Dies ist ein Schritt in die richtige Richtung, aber awk interpretiert immer noch einige Metazeichen in Ihrer Substitution, sodass es für Benutzereingaben immer noch nicht sicher ist.
Jeremy Huiskamp
0

Hier ist ein Beispiel für eine AWK, die ich vor einiger Zeit verwendet habe. Es ist eine AWK, die neue AWKS druckt. Da AWK und SED ähnlich sind, kann dies eine gute Vorlage sein.

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

Es sieht übertrieben aus, aber irgendwie funktioniert diese Kombination von Zitaten, um das 'als Literale gedruckt zu halten. Wenn ich mich richtig erinnere, sind die vaiables nur von Anführungszeichen wie diesem umgeben: "$ 1". Probieren Sie es aus, lassen Sie mich wissen, wie es mit SED funktioniert.

Alex
quelle
0

Ich habe eine Verbesserung gegenüber der Sedeasy-Funktion, die mit Sonderzeichen wie Tab brechen wird.

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"
}

Also, was ist anders? $1und $2in Anführungszeichen gesetzt, um Shell-Erweiterungen zu vermeiden und Tabulatoren oder doppelte Leerzeichen beizubehalten.

Zusätzliche Rohrleitungen | sed -e 's:\t:\\t:g'(ich mag :als Token), die eine Registerkarte in transformieren \t.

Francisco De Zuviria
quelle
Aber siehe meinen Kommentar zur sedeasy Antwort bezüglich der Verwendung von Echo in Rohren.
Pianosaurus
0

Dies sind die Fluchtcodes, die ich gefunden habe:

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

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

' = \x27
? = \x3f
% = \x25
^ = \x5e
Ark25
quelle
-1

Vergessen Sie nicht all das Vergnügen, das mit der Shell-Begrenzung um "und '

also (in 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
quelle
genau die Richtung, in die ich gebraucht wurde, um den Suchergebnissen zu entkommen, die über Google gefunden wurden. Dies kann für jemanden hilfreich sein, der mit - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg
-1

Wenn Sie nur den Variablenwert im Befehl sed ersetzen möchten, entfernen Sie einfach Beispiel:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
Shailender Singh
quelle
-2

Wenn Sie zufällig ein zufälliges Kennwort generieren, das zum sedErsetzen des Musters übergeben werden soll, müssen Sie vorsichtig sein, welcher Zeichensatz in der zufälligen Zeichenfolge enthalten ist. Wenn Sie ein Kennwort auswählen, das durch Codieren eines Werts als base64 erstellt wurde, gibt es nur Zeichen, die sowohl in base64 als auch als Sonderzeichen im sedErsetzungsmuster möglich sind. Dieses Zeichen ist "/" und kann leicht aus dem von Ihnen generierten Passwort entfernt werden:

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

Eine einfachere Möglichkeit, dies zu tun, besteht darin, die Zeichenfolge einfach vorher zu erstellen und als Parameter für zu verwenden sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt
Javonne Martin
quelle
Fehlgeschlagen und extrem gefährlich, da REPLACE vom Benutzer bereitgestellt wird: REPLACE=/gibtsed: -e expression #1, char 12: unknown option to `s'
Tino