Comment faire un remplacement de texte dans une grande hiérarchie de dossiers?

11

Je souhaite rechercher et remplacer du texte dans un grand ensemble de fichiers à l'exclusion de certaines instances. Pour chaque ligne, je veux une invite me demandant si je dois remplacer cette ligne ou non. Quelque chose de similaire à vim :%s/from/to/gc(avec l' cinvite de confirmation), mais sur un ensemble de dossiers. Existe-t-il un bon outil ou script de ligne de commande qui peut être utilisé?

balki
la source
Sur l'importance d'une mise en forme correcte: je lisais initialement votre commande comme s/from/to/gavec un problème de mise en forme après, plutôt s/from/to/gcqu'avec une emphase sur celle cque vous avez tenté d'écrire (vous ne pouvez pas le faire avec Markdown, vous pouvez le faire avec <code>et <strong>balises HTML).
Gilles 'SO- arrête d'être méchant'

Réponses:

19

Pourquoi ne pas utiliser vim?

Ouvrez tous les fichiers dans vim

vim $(find . -type f)

Ou ouvrez uniquement les fichiers pertinents (comme suggéré par Caleb)

vim $(grep 'from' . -Rl)

Et puis exécutez le remplacement dans tous les tampons

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

Vous pouvez également le faire avec sed, mais mes connaissances en sed sont limitées.

Gert
la source
Merci, votre réponse m'a fait faire une double prise tardive: j'ai réalisé que j'avais complètement raté le bit interactif. Je ne pense pas que ce soit même possible avec sed (pas assez de canaux d'entrée / sortie).
Gilles 'SO- arrête d'être méchant'
1
Vous pouvez accélérer cela en n'ouvrant pas TOUS les fichiers dans le tampon actuel en utilisant grepau lieu de findafin que vous n'ouvriez que les fichiers qui ont des correspondances connues. vim $(grep 'from' . -Rl)
Caleb
Merci.Le c (astreriks autour de c) est nécessaire? ou c'est un problème de formatage?
balki
@balki, c'est un problème de "formatage". Corrigé
Gert
5

Vous pouvez faire quelque chose de grossier avec un petit script Perl qui est chargé d'effectuer des remplacements ligne par ligne ( -l -pe) en place sur les fichiers passés en arguments ( -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

Les améliorations possibles seraient de colorer certaines parties de l'invite et de prendre en charge des éléments tels que «remplacer toutes les occurrences dans le fichier actuel». Demander séparément chaque occurrence sur une ligne serait plus difficile.

Deuxième partie, correspondance des fichiers. s'il n'y a pas trop de fichiers impliqués et que vous exécutez zsh, vous pouvez faire correspondre récursivement tous les fichiers du répertoire actuel et de ses sous-répertoires:

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

Si votre shell est bash ≥4, vous pouvez exécuter perl … **/*, mais cela produira des messages d'erreur parasites car sed tentera (et échouera) de s'exécuter sur les répertoires. Si vous ne souhaitez effectuer le remplacement que dans un ensemble de fichiers tels que les fichiers C, vous pouvez restreindre les correspondances (cela fonctionne en bash ≥4 ou zsh):

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

Si vous avez besoin d'un contrôle plus fin sur les fichiers que vous remplacez, ou votre shell n'a pas la construction de correspondance de répertoire récursive **, ou si vous avez trop de fichiers et obtenez une erreur «ligne de commande trop longue», utilisez find. Par exemple, pour effectuer un remplacement dans tous les fichiers nommés *.hou *.cdans le répertoire en cours et ses sous-répertoires (sur les anciens systèmes, vous devrez peut-être utiliser \;plutôt +qu'à la fin de la ligne (le +formulaire est plus rapide mais n'est pas disponible partout)).

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

Cela étant dit, je m'en tiendrai à un éditeur interactif si vous avez besoin d'interaction. Gert a montré un moyen d'y parvenir dans Vim , bien qu'il nécessite d'ouvrir tous les fichiers que vous souhaitez rechercher, ce qui peut être un problème s'il y en a beaucoup.

Dans Emacs, voici comment procéder:

  1. Rassemblez les noms de fichiers avec M-x find-name-dired(spécifiez un répertoire de niveau supérieur) ou M-x find-dired(spécifiez une findligne de commande arbitraire ).
  2. Dans le tampon dired résultant , appuyez sur tpour marquer tous les fichiers, puis sur Q( dired-do-query-replace-regexp) pour effectuer un remplacement avec invite sur les fichiers marqués.
Gilles 'SO- arrête d'être méchant'
la source
1

sdiff(voir http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) pourrait être utile ici. Avec lui, vous pouvez faire des correctifs interactifs. Donc, le faire avec un fichier temporaire que vous avez créé en effectuant des opérations de remplacement à l'aide de sedpeut être une solution possible:

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

(Boucle de fichier utilisant une substitution de processus non-POSIX et prise read -den charge par exemple par bash.)

phk
la source