Jak zrobić zamianę tekstu w dużej hierarchii folderów?

11

Chcę wyszukać i zamienić trochę tekstu w dużym zestawie plików, z wyjątkiem niektórych wystąpień. Dla każdej linii chcę monit z pytaniem, czy muszę ją zastąpić, czy nie. Coś podobnego do vima :%s/from/to/gc(z cmonitem o potwierdzenie), ale w zestawie folderów. Czy jest jakieś dobre narzędzie lub skrypt wiersza poleceń, którego można użyć?

balki
źródło
Na temat znaczenia właściwego formatowania: początkowo przeczytałbym twoje polecenie jak s/from/to/gz usterką formatowania po nim, zamiast s/from/to/gcz naciskiem na to, cjak próbowałeś pisać (nie możesz tego zrobić za pomocą Markdown, możesz to zrobić za pomocą <code>i <strong>tagi HTML).
Gilles „SO- przestań być zły”

Odpowiedzi:

19

Dlaczego nie użyć vima?

Otwórz wszystkie pliki w vimie

vim $(find . -type f)

Lub otwórz tylko odpowiednie pliki (zgodnie z sugestią Caleb)

vim $(grep 'from' . -Rl)

A następnie uruchom polecenie replace we wszystkich buforach

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

Możesz to również zrobić sed, ale moja wiedza jest ograniczona.

Gert
źródło
Dzięki, twoja odpowiedź skłoniła mnie do późnego podwójnego ujęcia: zdałem sobie sprawę, że całkowicie przegapiłem interaktywny kawałek. Nie sądzę, że jest to nawet możliwe w przypadku sed (niewystarczająca liczba kanałów wejściowych / wyjściowych).
Gilles „SO- przestań być zły”
1
Możesz to przyspieszyć, nie otwierając WSZYSTKICH plików w bieżącym buforze, używając grepzamiast find, aby otwierać tylko te pliki, które są zgodne. vim $(grep 'from' . -Rl)
Caleb
Thanks.The c (astreriks wokół c) jest potrzebne? czy jest to problem z formatowaniem?
balki
@balki to problem z „formatowaniem”. Naprawiono
Gert
5

Możesz zrobić coś prymitywnego za pomocą małego skryptu Perla, który jest instruowany do wykonywania zamiany wiersz po wierszu ( -l -pe) na plikach przekazywanych jako argumenty ( -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

Możliwe ulepszenia polegałyby na kolorowaniu części monitu i wsparciu takich rzeczy jak „zamień wszystkie wystąpienia w bieżącym pliku”. Oddzielne monitowanie o każde wystąpienie w linii byłoby trudniejsze.

Druga część, dopasowanie plików. jeśli nie ma zbyt wielu plików i korzystasz z zsh, możesz rekurencyjnie dopasować wszystkie pliki w bieżącym katalogu i jego podkatalogach:

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

Jeśli twoja powłoka ma bash ≥4, możesz uruchomić perl … **/*, ale spowoduje to fałszywe komunikaty o błędach, ponieważ sed spróbuje (i nie powiedzie się) uruchomić w katalogach. Jeśli chcesz wykonać zamianę tylko w zestawie plików, takich jak pliki C, możesz ograniczyć dopasowania (działa to zarówno w bash ≥4, jak i zsh):

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

Jeśli potrzebujesz dokładniejszej kontroli nad zamienianymi plikami lub twoja powłoka nie ma rekurencyjnej struktury dopasowywania katalogu **lub jeśli masz zbyt wiele plików i pojawia się błąd „zbyt długiej linii poleceń”, użyj find. Na przykład, aby wykonać zamianę we wszystkich nazwanych plikach *.hlub *.cw bieżącym katalogu i jego podkatalogach (w starszych systemach może być konieczne użycie \;zamiast +na końcu wiersza ( +formularz jest szybszy, ale nie wszędzie dostępny).

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

To powiedziawszy, trzymałbym się interaktywnego edytora, jeśli potrzebujesz interakcji. Gert wskazał na to sposób w Vimie , choć wymaga otwarcia wszystkich plików, które chcesz przeszukać, co może być problemem, jeśli jest ich dużo.

W Emacs możesz to zrobić w następujący sposób:

  1. Zbierz nazwy plików za pomocą M-x find-name-dired(podaj katalog najwyższego poziomu) lub M-x find-dired(podaj dowolny findwiersz poleceń).
  2. W powstałej dired buforze, należy nacisnąć t, aby zaznaczyć wszystkie pliki, a następnie Q( dired-do-query-replace-regexp) aby przeprowadzić wymianę z prośbą o zaznaczonych plików.
Gilles „SO- przestań być zły”
źródło
1

sdiff(patrz http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) może się tu przydać. Dzięki niemu możesz wykonywać interaktywne łatanie. Tak więc sedrozwiązaniem może być plik tymczasowy utworzony przez wykonanie operacji wymiany przy użyciu :

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

(Pętla plików korzystająca z zastępowania procesów innymi niż POSIX i read -dobsługiwana np bash. Przez .)

phk
źródło