Suchen und Ersetzen in einer Textdatei über einen Bash-Befehl

561

Was ist der einfachste Weg, um beispielsweise eine bestimmte Eingabezeichenfolge zu suchen und zu ersetzen und durch eine abcandere Zeichenfolge zu ersetzen, beispielsweise XYZin einer Datei /tmp/file.txt?

Ich schreibe eine App und verwende IronPython, um Befehle über SSH auszuführen - aber ich kenne Unix nicht so gut und weiß nicht, wonach ich suchen soll.

Ich habe gehört, dass Bash nicht nur eine Befehlszeilenschnittstelle ist, sondern auch eine sehr leistungsfähige Skriptsprache sein kann. Wenn dies zutrifft, können Sie vermutlich solche Aktionen ausführen.

Kann ich es mit Bash machen und was ist das einfachste (einzeilige) Skript, um mein Ziel zu erreichen?

Asche
quelle

Antworten:

930

Am einfachsten ist es, sed (oder perl) zu verwenden:

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Aufgrund der -iOption wird sed aufgerufen, um eine direkte Bearbeitung durchzuführen. Dies kann von bash aufgerufen werden.

Wenn Sie wirklich nur Bash verwenden möchten, kann Folgendes funktionieren:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Dies durchläuft jede Zeile, führt eine Ersetzung durch und schreibt in eine temporäre Datei (Sie möchten die Eingabe nicht blockieren). Die Verschiebung am Ende wird nur vorübergehend zum ursprünglichen Namen verschoben.

Johnny
quelle
3
Abgesehen davon, dass das Aufrufen von mv so ziemlich "non Bash" ist wie das Verwenden von sed. Ich hätte fast dasselbe von Echo gesagt, aber es ist eine eingebaute Shell.
schlank
5
Das Argument -i für sed gibt es für Solaris jedoch nicht (und ich würde mir einige andere Implementierungen vorstellen). Denken Sie also daran. Ich habe gerade ein paar Minuten damit verbracht, das herauszufinden ...
Panky
2
Hinweis an mich selbst: über den regulären Ausdruck von sed: s/..../..../ - Substituteund /g - Global
Prüfsumme
89
Hinweis für Mac-Benutzer, die eine invalid command code CFehlermeldung erhalten ... Für direkte Ersetzungen sedbenötigt BSD eine Dateierweiterung nach dem -iFlag, da eine Sicherungsdatei mit der angegebenen Erweiterung gespeichert wird. Zum Beispiel: sed -i '.bak' 's/find/replace/' /file.txt Sie können die Sicherung überspringen, indem Sie eine leere Zeichenfolge wie folgt verwenden:sed -i '' 's/find/replace/' /file.txt
Austin
8
Tipp: Wenn Sie Groß- und Kleinschreibung repalce Verwendungs/abc/XYZ/gi
Boris D. Teoharov
166

Die Dateimanipulation wird normalerweise nicht von Bash durchgeführt, sondern von Programmen, die von Bash aufgerufen werden, z.

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

Das -iFlag weist ihn an, einen direkten Austausch durchzuführen.

Sehen Sie man perlrunfür weitere Informationen, einschließlich , wie eine Sicherungskopie der Originaldatei zu übernehmen.

Alnitak
quelle
37
Der Purist in mir sagt, Sie können nicht sicher sein, ob Perl auf dem System verfügbar sein wird. Aber das ist heutzutage sehr selten der Fall. Vielleicht zeige ich mein Alter.
schlank
3
Können Sie ein komplexeres Beispiel zeigen? So etwas wie "chdir / blah" durch "chdir / blah2" ersetzen. Ich habe es versucht perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, aber es wird immer wieder die Fehlermeldung angezeigt, dass in Zeile 1 kein Leerzeichen zwischen dem Muster und dem folgenden Wort veraltet ist. Nicht übereinstimmend (in Regex; markiert mit <- HIER in m / (chdir) () (<- HIER) ?: \\ / at -e Zeile 1.
CMCDragonkai
@CMCDragonkai Überprüfen Sie diese Antwort: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

Ich war überrascht, als ich darüber stolperte ...

Es gibt einen replaceBefehl, der mit dem "mysql-server"Paket geliefert wird. Wenn Sie ihn installiert haben, probieren Sie ihn aus:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Weitere man replaceInformationen hierzu finden Sie hier.

Rayro
quelle
12
Zwei Dinge sind hier möglich: a) replaceist ein nützliches unabhängiges Tool und die MySQL-Leute sollten es separat veröffentlichen und davon abhängen b) replaceerfordert ein bisschen MySQL o_O In beiden Fällen wäre es falsch, den MySQL-Server zu installieren, um ihn zu ersetzen :)
Philip Whitehouse
funktioniert nur für mac? In meinem Ubuntu I Centos existiert dieser Befehl nicht
Paul
1
Das liegt daran, dass Sie kein mysql-serverPaket installiert haben. Wie von @rayro gezeigt, replaceist ein Teil davon.
Phius
2
"Warnung: Ersetzen ist veraltet und wird in einer zukünftigen Version entfernt."
Steven Vachon
1
Achten Sie darauf, den Befehl REPLACE nicht unter Windows auszuführen! Unter Windows dient der Befehl REPLACE zur schnellen Replikation von Dateien. Nicht relevant für diese Diskussion.
Maor
53

Dies ist ein alter Beitrag, aber für alle, die Variablen verwenden möchten, wie @centurian sagte, bedeuten die einfachen Anführungszeichen, dass nichts erweitert wird.

Eine einfache Möglichkeit, Variablen abzurufen, besteht darin, eine Zeichenfolgenverkettung durchzuführen, da dies durch Nebeneinanderstellung in Bash erfolgt. Folgendes sollte funktionieren:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
Zcourts
quelle
39

Bash ist wie andere Shells nur ein Werkzeug zum Koordinieren anderer Befehle. Normalerweise würden Sie versuchen, Standard-UNIX-Befehle zu verwenden, aber Sie können natürlich Bash verwenden, um alles aufzurufen, einschließlich Ihrer eigenen kompilierten Programme, anderer Shell-Skripte, Python- und Perl-Skripte usw.

In diesem Fall gibt es verschiedene Möglichkeiten.

Wenn Sie eine Datei lesen und in eine andere Datei schreiben möchten, während Sie suchen / ersetzen, verwenden Sie sed:

sed 's/abc/XYZ/g' <infile >outfile

Wenn Sie die Datei an Ort und Stelle bearbeiten möchten (als ob Sie die Datei in einem Editor öffnen, bearbeiten und dann speichern würden), geben Sie dem Zeileneditor 'ex' Anweisungen.

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex ist wie vi ohne den Vollbildmodus. Sie können ihm dieselben Befehle geben, die Sie an der Eingabeaufforderung ':' von vi ausführen würden.

schlank
quelle
33

Ich habe diesen Thread unter anderem gefunden und bin damit einverstanden, dass er die vollständigsten Antworten enthält, also füge ich auch meinen hinzu:

  1. sedund edsind so nützlich ... von Hand. Schauen Sie sich diesen Code von @Johnny an:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Wenn meine Einschränkung darin besteht, es in einem Shell-Skript zu verwenden, kann keine Variable anstelle von "abc" oder "XYZ" verwendet werden. Die BashFAQ scheint zumindest mit dem übereinzustimmen , was ich verstehe. Also kann ich nicht verwenden:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    aber was können wir tun? Wie, sagte @Johnny, benutze ein while read...aber leider ist das nicht das Ende der Geschichte. Folgendes hat bei mir gut funktioniert:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. Wie man sehen kann, wenn nullglobdann gesetzt ist, verhält es sich seltsam, wenn es einen String gibt, der ein *wie in enthält:

    <VirtualHost *:80>
     ServerName www.example.com

    was wird

    <VirtualHost ServerName www.example.com

    Es gibt keine Endwinkelklammer und Apache2 kann nicht einmal geladen werden.

  4. Diese Art der Analyse sollte langsamer sein als das Suchen und Ersetzen mit einem Treffer. Wie Sie bereits gesehen haben, gibt es vier Variablen für vier verschiedene Suchmuster, die in einem Analysezyklus ausgeführt werden.

Die am besten geeignete Lösung, die ich mir mit den gegebenen Annahmen des Problems vorstellen kann.

Centurian
quelle
12
In Ihrem (2) - können Sie tun sed -e "s/$x/$y/", und es wird funktionieren. Nicht die doppelten Anführungszeichen. Es kann sehr verwirrend werden, wenn die Zeichenfolgen in den Variablen selbst Zeichen mit besonderer Bedeutung enthalten. Zum Beispiel, wenn x = "/" oder x = "\". Wenn Sie auf diese Probleme stoßen, sollten Sie wahrscheinlich aufhören, die Shell für diesen Job zu verwenden.
schlank
Hallo schlank, ich sehe, dass du auch gegen die Verwendung von Perl bist. Was ist Ihre Lösung? Weil ich tatsächlich einen Pfad in einer Datei dynamisch ändern möchte, was bedeutet, dass ich viel / in der Zeichenfolge habe!
Mahdi
20

Sie können verwenden sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Verwenden -iSie diese Option für "Groß- / Kleinschreibung ignorieren", wenn Sie nicht sicher sind, ob der zu findende Text abcoder ABCoder AbCusw. ist.

Sie können verwenden findund sedwenn Sie Ihren Dateinamen nicht kennen:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Suchen und ersetzen Sie in allen Python-Dateien:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
MMParvin
quelle
12

Sie können den edBefehl auch verwenden , um in der Datei zu suchen und zu ersetzen:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Weitere Informationen finden Sie unter " Bearbeiten von Dateien über Skripte mited ".

der Blechmann
quelle
3
Diese Lösung ist unabhängig von GNU / FreeBSD-Inkompatibilitäten (Mac OSX) (im Gegensatz zu sed -i <pattern> <filename>). Sehr schön!
Peterino
11

Wenn die Datei, an der Sie arbeiten, nicht so groß ist und das vorübergehende Speichern in einer Variablen kein Problem darstellt, können Sie die Bash-String-Ersetzung für die gesamte Datei gleichzeitig verwenden. Sie müssen sie nicht Zeile für Zeile durchgehen:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Der gesamte Dateiinhalt wird als eine lange Zeichenfolge behandelt, einschließlich Zeilenumbrüchen.

XYZ kann z. B. eine Variable sein $replacement, und ein Vorteil der Nichtverwendung von sed besteht darin, dass Sie nicht befürchten müssen, dass die Such- oder Ersetzungszeichenfolge das Begrenzungszeichen sed-Muster enthält (normalerweise, aber nicht unbedingt, /). Ein Nachteil ist, dass keine regulären Ausdrücke oder komplexere Operationen von sed verwendet werden können.

Johnraff
quelle
Irgendwelche Tipps zur Verwendung mit Tabulatorzeichen? Aus irgendeinem Grund findet mein Skript nichts mit Registerkarten, nachdem es von sed gewechselt ist und viel zu dieser Methode übergegangen ist.
Brian Hannay
2
Wenn Sie der zu ersetzenden Zeichenfolge einen Tabulator hinzufügen möchten, können Sie dies mit der Bash-Syntax "Dollared Single Quotes" tun, sodass ein Tabulator durch $ '\ t' dargestellt wird und Sie $ echo 'tab' $ ausführen können '\ t''separated'> Testdatei; $ file_contents = $ (<Testdatei); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
Johnraff
5

Versuchen Sie den folgenden Shell-Befehl:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
quelle
4
Dies ist eine hervorragende Antwort auf "Wie kann ich versehentlich auch alle Dateien in allen Unterverzeichnissen bearbeiten?", Aber das scheint hier nicht gefragt zu sein.
Tripleee
Diese Syntax funktioniert nicht für die BSD-Version von sed. Verwenden Sie sed -i''stattdessen.
Kenorb
5

Um Text in der Datei nicht interaktiv zu bearbeiten, benötigen Sie einen direkten Texteditor wie vim.

Hier ist ein einfaches Beispiel für die Verwendung über die Befehlszeile:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Dies entspricht der @ lim-Antwort des Ex- Editors, was im Grunde dasselbe ist.

Hier einige expraktische Beispiele.

Ersetzen von Text foodurch barin der Datei:

ex -s +%s/foo/bar/ge -cwq file.txt

Entfernen von nachgestellten Leerzeichen für mehrere Dateien:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Fehlerbehebung (wenn das Terminal feststeckt):

  • Fügen Sie -V1param hinzu, um ausführliche Nachrichten anzuzeigen.
  • Erzwingen Sie das Beenden durch : -cwq!.

Siehe auch:

Kenorb
quelle
Wollte die Ersetzungen interaktiv durchführen. Daher versuchte "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Aber das Terminal steckt jetzt fest. Wie sollen wir die Ersetzungen interaktiv vornehmen, ohne dass sich die Bash-Shell seltsam verhält?
Vineeshvs
Zum Debuggen hinzufügen -V1, zum Beenden erzwingen, verwenden wq!.
Kenorb
2

Sie können Python auch im Bash-Skript verwenden. Ich hatte mit einigen der besten Antworten hier nicht viel Erfolg und fand, dass dies ohne die Notwendigkeit von Schleifen funktioniert:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
Micalith
quelle
1

Sie können den Befehl rpl verwenden. Zum Beispiel möchten Sie den Domainnamen im gesamten PHP-Projekt ändern.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Dies ist kein klarer Grund, aber es ist sehr schnell und nützlich. :) :)

Zalex
quelle