it-swarm.com.de

Wie kann ich eine Zeichenfolge in einer Datei ersetzen?

Das Ersetzen von Zeichenfolgen in Dateien anhand bestimmter Suchkriterien ist eine sehr häufige Aufgabe. Wie kann ich

  • zeichenfolge foo in allen Dateien im aktuellen Verzeichnis durch bar ersetzen?
  • dasselbe rekursiv für Unterverzeichnisse tun?
  • nur ersetzen, wenn der Dateiname mit einer anderen Zeichenfolge übereinstimmt?
  • nur ersetzen, wenn die Zeichenfolge in einem bestimmten Kontext gefunden wird?
  • ersetzen, wenn sich die Zeichenfolge in einer bestimmten Zeilennummer befindet?
  • ersetzen Sie mehrere Zeichenfolgen durch denselben Ersatz
  • ersetzen Sie mehrere Zeichenfolgen durch unterschiedliche Ersetzungen
791
terdon

1. Ersetzen Sie alle Vorkommen einer Zeichenfolge durch eine andere in allen Dateien im aktuellen Verzeichnis:

Dies ist für Fälle, in denen Sie wissen, dass das Verzeichnis nur reguläre Dateien enthält und Sie alle nicht versteckten Dateien verarbeiten möchten. Ist dies nicht der Fall, verwenden Sie die Ansätze in 2.

Alle sed Lösungen in dieser Antwort setzen GNU sed voraus. Wenn Sie FreeBSD oder OS/X verwenden, ersetzen Sie -i durch -i ''. Beachten Sie auch, dass die Verwendung von Der Schalter -i mit einer beliebigen Version von sed hat ein bestimmtes Dateisystem Auswirkungen auf die Sicherheit und wird in keinem Skript empfohlen, das Sie auf irgendeine Weise verteilen möchten.

  • Nicht rekursiv, Dateien nur in diesem Verzeichnis:

    sed -i -- 's/foo/bar/g' *
    Perl -i -pe 's/foo/bar/g' ./* 
    

    (das Perl one schlägt für Dateinamen fehl, die auf | oder Leerzeichen enden) ).

  • Rekursive, reguläre Dateien ( einschließlich versteckter Dateien ) in diesem und allen Unterverzeichnissen

    find . -type f -exec sed -i 's/foo/bar/g' {} +
    

    Wenn Sie zsh verwenden:

    sed -i -- 's/foo/bar/g' **/*(D.)
    

    (Kann fehlschlagen, wenn die Liste zu groß ist, siehe zargs, um sie zu umgehen).

    Bash kann nicht direkt nach regulären Dateien suchen, es wird eine Schleife benötigt (geschweifte Klammern vermeiden das globale Festlegen der Optionen):

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    Die Dateien werden ausgewählt, wenn es sich um tatsächliche Dateien (-f) handelt und sie beschreibbar sind (-w).

2. Ersetzen Sie nur, wenn der Dateiname mit einer anderen Zeichenfolge übereinstimmt/eine bestimmte Erweiterung hat/von einem bestimmten Typ ist usw.:

  • Nicht rekursive Dateien nur in diesem Verzeichnis:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • Rekursive, reguläre Dateien in diesem und allen Unterverzeichnissen

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
    

    Wenn Sie bash verwenden (geschweifte Klammern vermeiden, die Optionen global festzulegen):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    Wenn Sie zsh verwenden:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

    Der -- dient dazu, sed mitzuteilen, dass in der Befehlszeile keine Flags mehr angegeben werden. Dies ist nützlich, um sich vor Dateinamen zu schützen, die mit - beginnen.

  • Wenn eine Datei von einem bestimmten Typ ist, z. B. ausführbar (weitere Optionen finden Sie unter man find):

    find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
    

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)
    

3. Ersetzen Sie nur, wenn die Zeichenfolge in einem bestimmten Kontext gefunden wird

  • Ersetzen Sie foo nur dann durch bar, wenn sich in derselben Zeile ein baz befindet:

    sed -i 's/foo\(.*baz\)/bar\1/' file
    

    In sed speichert die Verwendung von \( \) alles, was in Klammern steht, und Sie können dann mit \1 darauf zugreifen. Es gibt viele Variationen dieses Themas. Weitere Informationen zu solchen regulären Ausdrücken finden Sie unter hier .

  • Ersetzen Sie foo nur dann durch bar, wenn foo in der 3D-Spalte (Feld) der Eingabedatei gefunden wird (unter der Annahme von durch Leerzeichen getrennten Feldern):

    gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
    

    (benötigt gawk 4.1.0 oder neuer).

  • Verwenden Sie für ein anderes Feld einfach $N, wobei N die Nummer des interessierenden Feldes ist. Verwenden Sie für ein anderes Feldtrennzeichen (in diesem Beispiel :) Folgendes:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
    

    Eine andere Lösung mit Perl:

    Perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 
    

    HINWEIS: Sowohl die Lösungen awk als auch Perl wirken sich auf den Abstand in der Datei aus (entfernen Sie die führenden und nachfolgenden Leerzeichen und konvertieren Sie Leerzeichenfolgen in ein Leerzeichen in den übereinstimmenden Zeilen). Verwenden Sie für ein anderes Feld $F[N-1], wobei N die gewünschte Feldnummer ist, und verwenden Sie für ein anderes Feldtrennzeichen ($"=":" setzt das Ausgabefeldtrennzeichen auf :):

    Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    
  • Ersetzen Sie foo durch bar nur in der 4. Zeile:

    sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    Perl -i -pe 's/foo/bar/g if $.==4' file
    

4. Mehrere Ersetzungsvorgänge: Durch verschiedene Zeichenfolgen ersetzen

  • Sie können sed Befehle kombinieren:

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    

    Beachten Sie, dass die Reihenfolge wichtig ist (sed 's/foo/bar/g; s/bar/baz/g' ersetzt foo durch baz).

  • oder Perl-Befehle

    Perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    
  • Wenn Sie eine große Anzahl von Mustern haben, ist es einfacher, Ihre Muster und deren Ersetzungen in einer sed - Skriptdatei zu speichern:

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • Wenn Sie zu viele Musterpaare haben, als dass dies möglich wäre, können Sie Musterpaare aus einer Datei lesen (zwei durch Leerzeichen getrennte Muster, $ Muster und $ Ersetzung, pro Zeile):

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • Bei langen Listen mit Mustern und großen Datendateien ist dies recht langsam. Daher möchten Sie möglicherweise die Muster lesen und stattdessen ein sed -Skript daraus erstellen. Im Folgenden wird davon ausgegangen, dass ein <Leerzeichen> Trennzeichen eine Liste von MATCH <Leerzeichen> ERSETZEN Paaren trennt, die in der Datei patterns.txt pro Zeile vorkommen :

    sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    

    Das obige Format ist weitgehend willkürlich und erlaubt beispielsweise keine <Leerzeichen> in einer der beiden Übereinstimmungen [~ # ~] [~ # ~] oder [~ # ~] ersetzen [~ # ~]. Die Methode ist jedoch sehr allgemein: Wenn Sie einen Ausgabestream erstellen können, der wie ein sed -Skript aussieht, können Sie diesen Stream als sed -Skript als Quelle angeben, indem Sie sed lautet -stdin.

  • Sie können mehrere Skripte auf ähnliche Weise kombinieren und verketten:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    Ein POSIX sed verkettet alle Skripte in der Reihenfolge, in der sie in der Befehlszeile angezeigt werden, zu einem. Keines davon muss in einer \newline enden.

  • grep kann genauso funktionieren:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • Wenn Sie mit festen Zeichenfolgen als Muster arbeiten, empfiehlt es sich, den regulären Ausdruck zu umgehen Metazeichen. Sie können dies ziemlich einfach tun:

    sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    

5. Mehrere Ersetzungsvorgänge: Ersetzen Sie mehrere Muster durch dieselbe Zeichenfolge

  • Ersetzen Sie eines von foo, bar oder baz durch foobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
    
  • oder

    Perl -i -pe 's/foo|bar|baz/foobar/g' file
    
1061
terdon

Ein gutes r e pl acement Linux-Tool ist rpl, das ursprünglich für das Debian-Projekt geschrieben wurde ist mit apt-get install rpl in jeder von Debian abgeleiteten Distribution verfügbar und kann für andere verwendet werden. Andernfalls können Sie die Datei tar.gz in SourgeForge herunterladen.

Einfachstes Anwendungsbeispiel:

 $ rpl old_string new_string test.txt

Beachten Sie, dass die Zeichenfolge, wenn sie Leerzeichen enthält, in Anführungszeichen gesetzt werden sollte. Standardmäßig kümmert sich rpl um Großbuchstaben, aber nicht um vollständige Wörter, aber Sie können diese Standardeinstellungen mit den Optionen -i Ändern (Groß-/Kleinschreibung ignorieren) ) und -w (ganze Wörter). Sie können auch mehrere Dateien angeben:

 $ rpl -i -w "old string" "new string" test.txt test2.txt

Oder geben Sie sogar die Erweiterungen (-x) An, um zu suchen, oder suchen Sie sogar rekursiv (-R) Im Verzeichnis:

 $ rpl -x .html -x .txt -R old_string new_string test*

Sie können auch in interaktiver Modus mit der Option -p (Eingabeaufforderung) suchen/ersetzen:

Die Ausgabe zeigt die Anzahl der ersetzten Dateien/Zeichenfolgen und die Art der Suche (Groß-/Kleinschreibung, ganze/teilweise Wörter), kann jedoch mit -q (leiser Modus stumm sein ) Option oder noch ausführlicher: Auflisten von Zeilennummern, die Übereinstimmungen jeder Datei und jedes Verzeichnisses mit der Option -v (ausführlicher Modus) enthalten.

Andere Optionen, an die Sie sich erinnern sollten, sind -e (Ehre e Landschaften), die regular expressions Erlauben, sodass Sie auch nach neuen Registerkarten (\t) Suchen können Zeilen (\n) usw. Sogar Sie können -f Verwenden, um Berechtigungen erzwingen (natürlich nur, wenn der Benutzer Schreibberechtigungen hat) und -d, Um die Änderungszeiten beizubehalten`).

Wenn Sie sich nicht sicher sind, welche genau das machen wird, verwenden Sie schließlich -s (Simulationsmodus).

79
Fran

So suchen und ersetzen Sie mehrere Dateien schlägt vor:

Sie könnten auch find und sed verwenden, aber ich finde, dass diese kleine Linie von Perl gut funktioniert.

Perl -pi -w -e 's/search/replace/g;' *.php
  • -e bedeutet, die folgende Codezeile auszuführen.
  • -i bedeutet direkt bearbeiten
  • -w Warnungen schreiben
  • -p Schleife über die Eingabedatei und druckt jede Zeile, nachdem das Skript darauf angewendet wurde.

Meine besten Ergebnisse kommen von der Verwendung von Perl und grep (um sicherzustellen, dass die Datei den Suchausdruck hat)

Perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

Ich habe das benutzt:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Listen Sie alle Dateien auf, die old_string Enthalten.

  2. Ersetzen Sie die Zeilenumbruch im Ergebnis durch Leerzeichen (damit die Liste der Dateien an sed übergeben werden kann.

  3. Führen Sie sed für diese Dateien aus, um die alte Zeichenfolge durch eine neue zu ersetzen.

pdate : Das obige Ergebnis schlägt bei Dateinamen fehl, die Leerzeichen enthalten. Verwenden Sie stattdessen:

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'

15
o_o_o--

Sie können Vim im Ex-Modus verwenden:

zeichenfolge ALF in allen Dateien im aktuellen Verzeichnis durch BRA ersetzen?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

dasselbe rekursiv für Unterverzeichnisse tun?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

nur ersetzen, wenn der Dateiname mit einer anderen Zeichenfolge übereinstimmt?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

nur ersetzen, wenn die Zeichenfolge in einem bestimmten Kontext gefunden wird?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

ersetzen, wenn sich die Zeichenfolge in einer bestimmten Zeilennummer befindet?

ex -sc '2s/ALF/BRA/g' -cx file

ersetzen Sie mehrere Zeichenfolgen durch denselben Ersatz

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

ersetzen Sie mehrere Zeichenfolgen durch unterschiedliche Ersetzungen

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
15
Steven Penny

Aus Anwendersicht ist qsubst ein nettes und einfaches Unix-Tool, das die Aufgabe perfekt erledigt. Zum Beispiel,

% qsubst foo bar *.c *.h

ersetzt foo durch bar in allen meinen C-Dateien. Eine nette Funktion ist, dass qsubst eine Abfrage ersetzt , dh es zeigt mir jedes Vorkommen von foo und frage, ob ich es ersetzen möchte oder nicht. [Sie können bedingungslos (ohne zu fragen) durch -go Option, und es gibt andere Optionen, z. B. -w wenn Sie foo nur ersetzen möchten, wenn es sich um ein ganzes Wort handelt.]

Wie man es bekommt: qsubst wurde von der Mouse (von McGill) erfunden und auf comp.unix.sources 11 (7) gepostet. im August 1987. Aktualisierte Versionen existieren. Zum Beispiel die NetBSD-Version qsubst.c,v 1.8 2004/11/01 kompiliert und läuft perfekt auf meinem Mac.

7
phs

ripgrep (Befehlsname rg) ist ein grep Tool, unterstützt aber auch das Suchen und Ersetzen.

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rg unterstützt keine In-Place-Option, daher müssen Sie dies selbst tun

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


Siehe Rust Regex-Dokumentation für Syntax und Funktionen für reguläre Ausdrücke. Der Schalter -P Aktiviert PCRE2 Flavour. rg unterstützt standardmäßig Unicode.

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


Wie grep ermöglicht die Option -F Das Abgleichen fester Zeichenfolgen, eine praktische Option, die meiner Meinung nach auch sed implementieren sollte.

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


Eine weitere praktische Option ist -U, Die den mehrzeiligen Abgleich ermöglicht

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rg kann auch Dateien im Dos-Stil verarbeiten

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


Ein weiterer Vorteil von rg ist, dass es wahrscheinlich schneller als sed ist

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
3
Sundeep

Ich brauchte etwas, das eine Trockenlaufoption bietet und rekursiv mit einem Glob arbeitet, und nachdem ich versucht hatte, es mit awk und sed zu tun, gab ich auf und tat es stattdessen in Python.

Das Skript durchsucht rekursiv alle Dateien, die einem Glob-Muster entsprechen (z. B. --glob="*.html") für eine Regex und ersetzt durch die Ersatz-Regex:

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

Jede lange Option wie --search-regex hat eine entsprechende kurze Option, d. h. -s. Führen Sie mit -h, um alle Optionen anzuzeigen.

Dies wird beispielsweise alle Daten von 2017-12-31 bis 31-12-2017:

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            Elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here ist eine aktualisierte Version des Skripts, die die Suchbegriffe hervorhebt und durch verschiedene Farben ersetzt.

3
ccpizza