Come eseguire una sostituzione del testo in una gerarchia di cartelle grandi?


11

Voglio cercare e sostituire del testo in un ampio set di file escludendo alcune istanze. Per ogni riga, desidero un prompt che mi chieda se devo sostituire quella riga o meno. Qualcosa di simile a quello di vim :%s/from/to/gc(con la crichiesta di conferma), ma attraverso una serie di cartelle. C'è qualche buon strumento da riga di comando o script che può essere utilizzato?


Sull'importanza di una formattazione corretta: inizialmente leggevo il tuo comando come se fosse s/from/to/gun glitch di formattazione dopo di esso, piuttosto che s/from/to/gccon enfasi su ccome avresti tentato di scrivere (non puoi farlo con Markdown, puoi farlo con <code>e <strong>tag HTML).
Gilles 'SO- smetti di essere malvagio'

Risposte:


19

Perché non usare Vim?

Apri tutti i file in vim

vim $(find . -type f)

Oppure apri solo i file pertinenti (come suggerito da Caleb)

vim $(grep 'from' . -Rl)

Ed esegui quindi la sostituzione in tutti i buffer

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

Puoi anche farlo con sed, ma la mia conoscenza di sed è limitata.


Grazie, la tua risposta mi ha fatto fare un doppio take in ritardo: mi sono reso conto che mi mancava completamente il bit interattivo. Non credo sia nemmeno possibile con sed (non abbastanza canali di input / output).
Gilles 'SO- smetti di essere malvagio'

1
È possibile accelerare ciò non aprendo TUTTI i file nel buffer corrente utilizzando grepanziché anziché in findmodo da aprire solo i file con corrispondenze note. vim $(grep 'from' . -Rl)
Caleb,

Grazie. La c (astreriks intorno a c) è necessaria? o è un problema di formattazione?
Balki,

@balki è un problema di "formattazione". Risolto il problema
Gert,

5

Puoi fare qualcosa di rozzo con un piccolo script Perl che è incaricato di eseguire sostituzioni riga per riga ( -l -pe) in posizione sui file passati come argomenti ( -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

I possibili miglioramenti potrebbero essere la colorazione di parti del prompt e il supporto di elementi come "Sostituisci tutte le occorrenze nel file corrente". Promuovere separatamente ogni ricorrenza su una linea sarebbe più difficile.

Seconda parte, corrispondente ai file. se non ci sono troppi file coinvolti e stai eseguendo zsh, puoi abbinare ricorsivamente tutti i file nella directory corrente e nelle sue sottodirectory:

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

Se la shell è bash ≥4, è possibile eseguire perl … **/*, ma ciò produrrà messaggi di errore spuri perché sed tenterà (e non riuscirà) di funzionare su directory. Se si desidera eseguire la sostituzione solo in un set di file come i file C, è possibile limitare le corrispondenze (che funziona in bash ≥4 o zsh):

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

Se hai bisogno di un controllo più preciso su quali file stai sostituendo, o la tua shell non ha il costrutto di corrispondenza della directory ricorsiva **, o se hai troppi file e ottieni un errore di "riga di comando troppo lunga", usa find. Ad esempio, per eseguire una sostituzione in tutti i file denominati *.ho *.cnella directory corrente e nelle sue sottodirectory (sui sistemi più vecchi, potrebbe essere necessario utilizzare \;anziché +alla fine della riga (il +modulo è più veloce ma non disponibile ovunque).

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

Detto questo, mi limiterei a un editor interattivo se hai bisogno di interazione. Gert ha mostrato un modo per farlo in Vim , anche se richiede l'apertura di tutti i file che si desidera cercare, il che potrebbe essere un problema se ce ne sono molti.

In Emacs, ecco come puoi farlo:

  1. Raccogliere i nomi dei file con M-x find-name-dired(specificare una directory di livello superiore) o M-x find-dired(specificare una findriga di comando arbitraria ).
  2. Nella risultante Dired tampone, premere tper contrassegnare tutti i file, quindi Q( dired-do-query-replace-regexp) per effettuare una sostituzione con spingendo sui file marcati.

1

sdiff(vedi http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) potrebbe tornare utile qui. Con esso puoi eseguire patch interattive. Quindi farlo con un file temporaneo creato eseguendo operazioni di sostituzione utilizzando sedpotrebbe essere una possibile soluzione:

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

(Loop di file che utilizza la sostituzione del processo non POSIX e read -dcome supportato ad es bash. Da.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.