Gibt es eine Möglichkeit zu verhindern, dass sed die Ersatzzeichenfolge interpretiert? [geschlossen]


14

Wenn Sie ein Schlüsselwort mit sed durch eine Zeichenfolge ersetzen möchten, versucht sed, Ihre Ersatzzeichenfolge zu interpretieren. Wenn die Ersatzzeichenfolge Zeichen enthält, die sed als besonders erachtet, z. B. ein '/' - Zeichen, schlägt dies fehl, es sei denn, Sie haben natürlich gemeint, dass Ihre Ersatzzeichenfolge Zeichen enthält, die sed angeben, wie sie sich verhalten sollen.

Ex:

VAR="hi/"

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

Gibt es eine Möglichkeit, sed anzuweisen, nicht zu versuchen, die Ersatzzeichenfolge für Sonderzeichen zu interpretieren? Ich möchte nur in der Lage sein, ein Schlüsselwort in einer Datei durch den Inhalt einer Variablen zu ersetzen, unabhängig davon, um welchen Inhalt es sich handelt.


Wenn Sie Sonderzeichen einfügen möchten sedund diese nicht besonders sein sollen, entkommen Sie ihnen einfach mit einem Backslash. VAR='hi\/'gibt kein solches Problem.
Wildcard

5
Warum all die Abstimmungen? Es scheint mir eine völlig vernünftige Frage zu sein
Roaima

sed(1)interpretiert nur, was es bekommt. In Ihrem Fall wird dies über eine Shell-Interpolation erreicht. Ich glaube, Sie können nicht tun, was Sie wollen, aber lesen Sie das Handbuch. Ich weiß, dass Sie in Perl (das einen passablen sedErsatz mit viel umfangreicheren regulären Ausdrücken darstellt) angeben können, dass eine Zeichenfolge wörtlich genommen werden soll. Überprüfen Sie erneut das Handbuch.
vonbrand

Antworten:


4

Es gibt nur 4 Sonderzeichen im Ersatzteil: \, &, Newline und das Trennzeichen ( 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

Dies hat das gleiche Problem wie die Lösung von Antti: Wenn die Ersetzungszeichenfolge eine bestimmte Länge überschreitet, wird der Fehler "Argumentliste zu lang" angezeigt. Was ist auch, wenn die Ersatzzeichenfolge '[', ']', '*', '.' Und andere solche Zeichen enthält? Würde sed diese wirklich nicht interpretieren?
Tal

Die Ersetzungsseite von s///ist kein regulärer Ausdruck, sondern nur eine Zeichenfolge (mit Ausnahme von Backslash-Escapezeichen und &). Wenn die Ersatzschnur so lang ist, ist ein Shell-Einzeiler nicht Ihre Lösung.
Glenn Jackman

Eine sehr nützliche Liste, wenn Ihre Ersatzzeichenfolge beispielsweise aus Base64-codiertem Text besteht (z. B. Ersetzen eines Platzhalters durch einen SHA256-Schlüssel). Dann ist es nur das Trennzeichen, um das man sich Sorgen machen muss.
Heath Raftery

4

Sie können Perl anstelle von sed mit -p(Schleife über Eingabe annehmen) und -e(Programm in der Befehlszeile angeben) verwenden. Mit Perl können Sie auf Umgebungsvariablen zugreifen, ohne diese in der Shell zu interpolieren. Beachten Sie, dass die Variable exportiert werden muss :

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

Wenn Sie die Variable nicht überall exportieren möchten, geben Sie sie nur für diesen Prozess an:

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

Beachten Sie, dass sich die Syntax für reguläre Ausdrücke von Perl standardmäßig geringfügig von der von sed unterscheidet.


Dies schien sehr vielversprechend zu sein, aber beim Testen wird der Fehler "Argumentliste zu lang" angezeigt, da meine Ersatzzeichenfolge zu lang ist. Dies ist sinnvoll. Bei dieser Methode verwenden wir die gesamte Ersatzzeichenfolge als Teil der von uns angegebenen Argumente zu perl, so gibt es eine Grenze, wie lange es sein kann.
Tal

1
Nein, es wird in die PATTERN Umgebungsvariable gehen , nicht in Argumente. In jedem Fall wäre dies ein Fehler E2BIG, den Sie ebenfalls erhalten würden, wenn Sie ihn verwenden würden sed.
Antti Haapala

2

Die einfachste Lösung, mit der die überwiegende Mehrheit der Variablenwerte immer noch korrekt behandelt wird, besteht darin, ein nicht druckbares Zeichen als Trennzeichen für sedden Ersatzbefehl zu verwenden.

In können viSie jedem Steuerzeichen entkommen, indem Sie Strg-V eingeben (häufiger geschrieben als ^V). Wenn Sie also ein Steuerzeichen verwenden ( ^Ain diesen Fällen häufig als Trennzeichen), wird Ihr sedBefehl nur unterbrochen, wenn dieses nicht druckbare Zeichen in der Variablen vorhanden ist, in die Sie einfügen.

Sie würden also tippen "s^V^AKEYWORD^V^A$VAR^V^Ag"und wie Sie (in vi) erhalten würden, würde aussehen:

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

Dies funktioniert so lange, wie $VARdas nicht druckbare Zeichen nicht enthalten ^Aist - was äußerst unwahrscheinlich ist.


Wenn Sie Benutzereingaben an den Wert von übergeben $VAR, sind natürlich alle Wetten deaktiviert, und Sie sollten Ihre Eingabe gründlich bereinigen, anstatt sich darauf zu verlassen, dass Steuerzeichen für den durchschnittlichen Benutzer schwer zu tippen sind.


Es gibt jedoch tatsächlich mehr zu beachten als die Trennzeichenfolge. Wenn es beispielsweise &in einer Ersatzzeichenfolge vorhanden ist, bedeutet dies "den gesamten übereinstimmenden Text". ZB s/stu../my&/würde "stuff" durch "mystuff", "stung" durch "mystung" usw. ersetzen. Wenn Sie also ein Zeichen in der Variablen haben, das Sie als Ersatzzeichenfolge einfügen, aber das Literal verwenden möchten Nur der Wert der Variablen, dann müssen Sie einige Daten bereinigen, bevor Sie die Variable als Ersatzzeichenfolge in verwenden können sed. (Die Datenbereinigung kann jedoch sedauch durchgeführt werden.)


Das ist mein Punkt - das Ersetzen eines Strings durch einen anderen String ist eine sehr einfache Operation. Muss es wirklich so kompliziert sein, herauszufinden, welche Charaktere sed nicht mögen, und sed zu verwenden, um seine eigenen Eingaben zu bereinigen? Das klingt lächerlich und unnötig verwickelt. Ich bin kein professioneller Programmierer, aber ich bin mir ziemlich sicher, dass ich eine kleine Funktion codieren kann, die ein Schlüsselwort durch eine Zeichenfolge in so ziemlich jeder Sprache ersetzt, auf die ich jemals gestoßen bin, einschließlich Bash - ich hatte nur auf ein einfaches Linux gehofft Lösung mit vorhandenen Tools - Ich kann nicht glauben, dass es keine gibt.
Tal

1
@Tal, wenn Ihre Ersatzzeichenfolge "100 Seiten lang" ist, wie Sie in einem anderen Kommentar erwähnen ... können Sie es kaum als "einfachen" Anwendungsfall bezeichnen. Die Antwort hier ist übrigens Perl - ich habe Perl einfach nicht gelernt. Die Komplexität ergibt sich aus der Tatsache, dass Sie JEDE beliebige Eingabe als Ersatzzeichenfolge in einem regulären Ausdruck zulassen möchten .
Wildcard

Es gibt zahlreiche andere Lösungen, von denen viele sehr einfach sind. Zum Beispiel wird , wenn Ihr Ersatz - String tatsächlich Linie basiert und muss nicht in der eingefügt wird Mitte einer Linie, die Verwendung sed‚s insert Befehl. Es sedist jedoch kein gutes Werkzeug, um große Textmengen auf komplexe Weise zu verarbeiten. Ich werde eine weitere Antwort veröffentlichen, die zeigt, wie das geht awk.
Wildcard

1

Sie könnten stattdessen ein ,oder ein |verwenden, und es wird als Trennzeichen verwendet, und technisch können Sie alles verwenden

von der Manpage

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

Wie Sie sehen, sollten Sie am Anfang mit einem \ vor Ihrem Trennzeichen beginnen, dann können Sie es als Trennzeichen verwenden.

Aus der Dokumentation http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command :

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.

Beispiel:

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


Sie sprechen davon, die Verwendung eines einzelnen, spezifischen Zeichens in der Ersatzzeichenfolge zuzulassen - in diesem Fall "/". Ich spreche davon, zu verhindern, dass versucht wird, die Ersatzzeichenfolge insgesamt zu interpretieren. Unabhängig davon, welches Zeichen Sie verwenden ("/", ",", "|" usw.), besteht immer das Risiko, dass dieses Zeichen in der Ersatzzeichenfolge angezeigt wird. Auch das ursprüngliche Zeichen ist nicht das einzige Sonderzeichen, das sed interessiert, oder?
Tal

@Tal nein, es kann alles anstelle von /und es wird das /glücklich ignorieren, wie ich gerade betont habe . In der Tat können Sie sogar danach suchen und es in einer Zeichenfolge ersetzen >>> Ich habe mit einem Beispiel >>> diese bearbeitet Sachen sind nicht so sicher und Sie werden immer einen
klügeren

@Tal warum willst du verhindern, dass es interpretiert? Ich meine, das ist sedin erster Linie die Verwendung von , was ist Ihr Projekt?
user3566929

Ich muss lediglich ein Schlüsselwort durch eine Zeichenfolge ersetzen. sed scheint bei weitem der gebräuchlichste Weg zu sein, dies unter Linux zu tun. Die Zeichenfolge kann 100 Seiten lang sein. Ich möchte nicht versuchen, die Zeichenfolge zu bereinigen, damit sed beim Lesen nicht ausflippt. Ich möchte, dass sie alle Zeichen in der Zeichenfolge verarbeiten kann, und mit "behandeln" meine ich, nicht zu versuchen, Magie zu finden Bedeutung innerhalb.
Tal

1
@Tal, bashist NICHT für die Manipulation von Zeichenfolgen. Überhaupt, überhaupt, überhaupt. Es dient zur Dateimanipulation und Befehlskoordination . Es hat zufällig einige praktische Funktionen für Strings eingebaut, aber wirklich begrenzt und überhaupt nicht sehr schnell, wenn das die Hauptsache ist, die Sie tun. Siehe "Warum wird die Verwendung einer Shell-Schleife zum Verarbeiten von Text als schlechte Praxis angesehen?" Einige Werkzeuge , die sind für die Textverarbeitung ausgelegt sind, um von den meisten Grund zu leistungsfähigste: sed, awkund Perl.
Wildcard

1

Wenn es zeilenbasiert ist und nur eine Zeile ersetzt werden muss, empfehle ich, der Datei selbst die Ersetzungszeile mit voranzustellen printf, die erste Zeile im Speicherbereich zu speichern sedund sie nach Bedarf abzulegen . Auf diese Weise müssen Sie sich überhaupt nicht um Sonderzeichen kümmern. (Die einzige Annahme hier ist, dass $VAReine einzelne Textzeile ohne Zeilenumbrüche enthalten ist, wie Sie bereits in den Kommentaren gesagt haben.) Abgesehen von Zeilenumbrüchen könnte VAR alles enthalten , und dies würde unabhängig davon funktionieren.

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

printf '%s\n'druckt den Inhalt $VARunabhängig vom Inhalt als Literalzeichenfolge, gefolgt von einer neuen Zeile. ( echoIn einigen Fällen werden andere Aufgaben ausgeführt, z. B. wenn der Inhalt von $VARmit einem Bindestrich beginnt. Er wird als Optionsflag interpretiert, an das übergeben wird echo.)

Die geschweiften Klammern werden verwendet, um die Ausgabe printfdem Inhalt von vorzustellen, an den somefilesie übergeben wird sed. Das Leerzeichen, das die geschweiften Klammern von sich aus trennt, ist hier wichtig, ebenso wie das Semikolon vor der schließenden geschweiften Klammer.

1{h;d;};wie ein sedBefehl , um die erste Zeile des Texts in gespeichert werden sed‚s Halteraum , dann dÉLETE die Linie (statt sie zu drucken).

/KEYWORD/wendet die folgenden Aktionen auf alle Zeilen an, die enthalten KEYWORD. Die Aktion ist get, bei der der Inhalt des Haltebereichs abgerufen und anstelle des Musterbereichs abgelegt wird - mit anderen Worten, die gesamte aktuelle Zeile. (Dies dient nicht zum Ersetzen nur eines Teils einer Zeile.) Der Haltebereich wird übrigens nicht geleert, sondern nur in den Musterbereich kopiert und ersetzt, was auch immer vorhanden ist.

Wenn Sie Ihren regulären Ausdruck so verankern möchten, dass er nicht mit einer Zeile übereinstimmt, die lediglich KEYWORD enthält, sondern nur mit einer Zeile, in der sich nichts anderes als KEYWORD befindet, fügen Sie einen Zeilenanker ( ^) und ein Zeilenende ( $) hinzu Ihre Regex:

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

Scheint großartig, wenn Ihr VAR eine Zeile lang ist. Ich habe in den Kommentaren tatsächlich erwähnt, dass VAR "100 Seiten lang sein kann" und nicht eine Zeile. Entschuldigung für die Verwirrung.
Tal

0

Mithilfe der Erweiterung der Musterersetzungsparameter von Bash können Sie die Schrägstriche in Ihrer Ersatzzeichenfolge mit einem umgekehrten Schrägstrich umgehen. Es ist ein wenig chaotisch, weil die Schrägstriche auch für Bash entkommen müssen.

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

Ausgabe

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

Sie können die Parametererweiterung direkt in Ihren sed-Befehl einfügen:

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

aber ich denke, die erste Form ist etwas besser lesbar. Und wenn Sie dasselbe Ersetzungsmuster in mehreren sed-Befehlen wiederverwenden möchten, ist es natürlich sinnvoll, die Konvertierung nur einmal durchzuführen.

Eine andere Möglichkeit wäre, ein in awk, perl oder Python geschriebenes Skript oder ein C-Programm zu verwenden, um Ihre Ersetzungen vorzunehmen, anstatt sed zu verwenden.


Hier ist ein einfaches Beispiel in Python, das funktioniert, wenn das zu ersetzende Schlüsselwort eine vollständige Zeile in der Eingabedatei ist (ohne die neue Zeile). Wie Sie sehen können, ist es im Wesentlichen der gleiche Algorithmus wie in Ihrem Bash-Beispiel, aber es liest die Eingabedatei effizienter.

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)

Dies ist nur eine andere Möglichkeit, die Eingabe zu bereinigen, und keine großartige, da nur ein bestimmtes Zeichen ('/') verarbeitet wird. Wie Wildcard hervorhob, gibt es mehr zu beachten als nur die Trennzeichenfolge.
Tal

Fairer Ruf. Wenn der Ersatztext beispielsweise Sequenzen mit umgekehrten Schrägstrichen enthält, werden diese interpretiert, was möglicherweise nicht wünschenswert ist. Eine Möglichkeit, dies zu umgehen, besteht darin, die problematischen Zeichen (oder das Ganze) in \xFluchtsequenzen im Stil umzuwandeln . Oder um ein Programm zu verwenden, das willkürliche Eingaben verarbeiten kann, wie ich in meinem letzten Absatz erwähnt habe.
PM 2Ring

@Tal: Ich werde meiner Antwort ein einfaches Python-Beispiel hinzufügen.
PM 2Ring

Das Python-Skript funktioniert hervorragend und scheint genau das zu tun, was meine Funktion tut, nur weitaus effizienter. Wenn das Hauptskript Bash ist (wie in meinem Fall), ist leider die Verwendung eines sekundären externen Python-Skripts erforderlich.
Tal

-1

So bin ich gegangen:

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

Dies funktioniert in meinem Fall hervorragend, da sich mein Keyword in einer eigenen Zeile befindet. Wenn sich das Schlüsselwort in einer Zeile mit einem anderen Text befindet, funktioniert dies nicht.

Ich würde immer noch gerne wissen, ob es einen einfachen Weg gibt, ohne meine eigene Lösung zu codieren.


1
Wenn Sie sich wirklich Sorgen über Sonderzeichen und Robustheit machen, sollten Sie diese überhaupt nicht verwenden echo. Verwenden Sie printfstattdessen. Und Textverarbeitung in einer Shell-Schleife ist eine schlechte Idee.
Wildcard

1
Es wäre hilfreich gewesen, wenn Sie in der Frage erwähnt hätten, dass das Schlüsselwort immer eine vollständige Zeile ist. FWIW, Bash readist ziemlich langsam. Es ist für die Verarbeitung interaktiver Benutzereingaben gedacht, nicht für die Verarbeitung von Textdateien. Es ist langsam, weil es stdin char by char liest und für jedes char einen Systemaufruf ausführt.
PM 2Ring

@PM 2Ring In meiner Frage wurde nicht erwähnt, dass das Schlüsselwort in einer eigenen Zeile steht, da ich keine Antwort möchte, die nur in einer so begrenzten Anzahl von Fällen funktioniert. Ich wollte etwas, das problemlos funktioniert, unabhängig davon, wo sich das Schlüsselwort befindet war. Ich habe auch nie gesagt, dass mein Code effizient ist - wenn es so wäre, würde ich nicht nach einer Alternative suchen ...
Tal

@Wildcard Sofern mir nichts fehlt, interpretiert printf Sonderzeichen absolut und weitaus mehr als das Standard-Echo. printf "hi\n"Mit printf wird eine neue Zeile gedruckt, während echo "hi\n"sie so gedruckt wird, wie sie ist.
Tal

@Tal, das "f" in printfsteht für "Format" - das erste Argument dafür printfist ein Formatbezeichner . Wenn dieser Bezeichner ist %s\n, was bedeutet , „string durch Newline gefolgt“, nichts in die nächste Argument wird durch interpretiert oder übersetzt werden printf überhaupt . (Die Shell kann es natürlich immer noch interpretieren. Halten Sie alles am besten in einfache Anführungszeichen, wenn es sich um eine Literalzeichenfolge handelt, oder in doppelte Anführungszeichen, wenn Sie eine variable Erweiterung wünschen.) Weitere Informationen finden Sie in meiner Antwort unter Verwendungprintf von.
Wildcard
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.