Wie ersetze ich einen Text in einer großen Ordnerhierarchie?

11

Ich möchte Text in einer großen Anzahl von Dateien mit Ausnahme einiger Instanzen suchen und ersetzen. Ich möchte für jede Zeile eine Eingabeaufforderung, in der ich gefragt werde, ob ich diese Zeile ersetzen muss oder nicht. Ähnlich wie bei vim :%s/from/to/gc(mit der cAufforderung zur Bestätigung), jedoch über eine Reihe von Ordnern hinweg. Gibt es ein gutes Befehlszeilentool oder Skript, das verwendet werden kann?

balki
quelle
Zur Wichtigkeit der richtigen Formatierung: Ich habe Ihren Befehl zunächst wie s/from/to/gmit einem Formatierungsfehler danach gelesen , anstatt den s/from/to/gcSchwerpunkt auf das zu legen, cwas Sie zu schreiben versucht hatten (das können Sie mit Markdown nicht tun, Sie könnten es tun <code>und <strong>HTML-Tags).
Gilles 'SO - hör auf böse zu sein'

Antworten:

19

Warum nicht vim verwenden?

Öffnen Sie alle Dateien in vim

vim $(find . -type f)

Oder öffnen Sie nur relevante Dateien (wie von Caleb vorgeschlagen)

vim $(grep 'from' . -Rl)

Führen Sie dann das Ersetzen in allen Puffern aus

:bufdo %s/from/to/gc | update

Sie können es auch tun sed, aber mein Wissen ist begrenzt.

Gert
quelle
Vielen Dank, Ihre Antwort hat mich zu einem späten Double-Take veranlasst: Mir wurde klar, dass ich das interaktive Teil komplett verpasst hatte. Ich denke nicht, dass das mit sed überhaupt möglich ist (nicht genug Ein- / Ausgabekanäle).
Gilles 'SO - hör auf böse zu sein'
1
Sie können dies beschleunigen, indem Sie nicht ALLE Dateien im aktuellen Puffer öffnen, indem Sie grepstattdessen verwenden, findsodass Sie nur Dateien öffnen, deren Übereinstimmungen bekannt sind. vim $(grep 'from' . -Rl)
Caleb
Danke. Das c (Astreriks um c) wird benötigt? oder ist es ein Formatierungsproblem?
Balki
@ Balki, das ist ein "Formatierungs" -Problem. Behoben
Gert
5

Mit einem kleinen Perl-Skript, das angewiesen wird, zeilenweise ( -l -pe) Ersetzungen für die als Argumente übergebenen Dateien durchzuführen, können Sie etwas Grobes tun -i:

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

Mögliche Verbesserungen wären, Teile der Eingabeaufforderung einzufärben und Dinge wie "Alle Vorkommen in der aktuellen Datei ersetzen" zu unterstützen. Es wäre schwieriger, jedes Vorkommen in einer Zeile separat aufzufordern.

Zweiter Teil, passend zu den Dateien. Wenn nicht zu viele Dateien beteiligt sind und Sie zsh ausführen, können Sie alle Dateien im aktuellen Verzeichnis und seinen Unterverzeichnissen rekursiv abgleichen:

perl -i -l -pe '…' **/*(.)

Wenn Ihre Shell bash ≥4 ist, können Sie ausführen perl … **/*, dies führt jedoch zu falschen Fehlermeldungen, da sed versucht (und fehlschlägt), in Verzeichnissen ausgeführt zu werden. Wenn Sie das Ersetzen nur in einer Reihe von Dateien wie C-Dateien durchführen möchten, können Sie die Übereinstimmungen einschränken (dies funktioniert entweder in Bash ≥4 oder zsh):

perl -i -l -pe '…' **/*.[hc]

Wenn Sie eine genauere Kontrolle darüber benötigen, welche Dateien Sie ersetzen, oder Ihre Shell nicht über das rekursive Verzeichnisvergleichskonstrukt **verfügt oder wenn Sie zu viele Dateien haben und den Fehler "Befehlszeile zu lang" erhalten, verwenden Sie find. Um beispielsweise eine Ersetzung in allen Dateien mit dem Namen *.hoder *.cim aktuellen Verzeichnis und seinen Unterverzeichnissen durchzuführen (auf älteren Systemen müssen Sie diese möglicherweise \;anstelle des +Zeilenendes verwenden (das +Formular ist schneller, aber nicht überall verfügbar).

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

Davon abgesehen würde ich mich an einen interaktiven Editor halten, wenn Sie Interaktion benötigen. Gert hat in Vim einen Weg dazu aufgezeigt , erfordert jedoch das Öffnen aller Dateien, die Sie durchsuchen möchten. Dies kann ein Problem sein, wenn viele vorhanden sind.

In Emacs können Sie Folgendes tun:

  1. Sammeln Sie die Dateinamen mit M-x find-name-dired(geben Sie ein Verzeichnis auf oberster Ebene an) oder M-x find-dired(geben Sie eine beliebige findBefehlszeile an).
  2. Drücken Sie im resultierenden dired- Puffer, tum alle Dateien zu markieren, und dann Q( dired-do-query-replace-regexp) , um einen Ersatz mit Aufforderung für die markierten Dateien durchzuführen.
Gilles 'SO - hör auf böse zu sein'
quelle
1

sdiff(siehe http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) könnte hier nützlich sein. Damit können Sie interaktives Patchen durchführen. Daher kann es sedeine mögliche Lösung sein , dies mit einer temporären Datei zu tun, die Sie durch Ersetzen von Ersetzungsvorgängen mit erstellt haben :

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(Dateischleife mit Nicht-POSIX-Prozessersetzung und read -dwie z bash. B. unterstützt .)

phk
quelle