Як зробити заміну тексту у великій ієрархії папок?


11

Я хочу шукати і замінювати текст у великому наборі файлів, виключаючи деякі екземпляри. Для кожного рядка я хочу запитувати, чи потрібно мені замінити цей рядок чи ні. Щось схоже на vim's :%s/from/to/gc(із cзапитом на підтвердження), але через набір папок. Чи є якийсь хороший інструмент або сценарій командного рядка, який можна використовувати?


Про важливість правильного форматування: я спочатку читав вашу команду, як s/from/to/gіз проблемою форматування після неї, а не s/from/to/gcз акцентом на те, cяк ви намагалися записати (ви не можете цього зробити з Markdown, ви могли б це зробити з <code>та <strong>теги HTML).
Жил "ТАК - перестань бути злим"

Відповіді:


19

Чому б не використовувати vim?

Відкрийте всі файли vim

vim $(find . -type f)

Або відкривати лише відповідні файли (як це запропонував Caleb)

vim $(grep 'from' . -Rl)

А потім виконайте заміну у всіх буферах

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

Ви також можете це зробити sed, але мої знання про сед обмежені.


Дякую, ваша відповідь змусила мене зробити пізній подвійний результат: я зрозумів, що повністю пропустив інтерактивний біт. Я не думаю, що це можливо навіть з sed (недостатньо каналів вводу / виводу).
Жил "ТАК - перестань бути злим"

1
Ви можете прискорити це, не відкриваючи ВСІ файли в поточному буфері, використовуючи grepзамість findцього, щоб ви відкривали лише файли, які мають відповідність. vim $(grep 'from' . -Rl)
Калеб

Thanks.The з (astreriks навколо с) необхідно? чи це проблема із форматуванням?
балки

@balki - це проблема "форматування". Виправлено це
Герт

5

Ви можете зробити щось неочищене за допомогою невеликого сценарію Perl, який вказується виконувати замін рядки за рядком ( -l -pe) на файлах, переданих як аргументи ( -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

Можливі поліпшення полягають у кольорових частинах підказки та підтримують такі речі, як "замінити всі випадки в поточному файлі". Окремо підказувати кожне виникнення на рядку було б важче.

Друга частина, що відповідає файлам. якщо не занадто багато файлів, і ви працюєте zsh, ви можете зіставити всі файли в поточному каталозі та його підкаталогах рекурсивно:

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

Якщо ваша оболонка є bash ≥4, ви можете запустити perl … **/*, але це призведе до помилкових повідомлень про помилки, оскільки sed буде намагатися (і не працювати) для запуску в каталогах. Якщо ви хочете виконати заміну лише в наборі файлів, таких як файли C, ви можете обмежити відповідність (що працює в bash ≥4 або zsh):

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

Якщо вам потрібен тонший контроль над тим, які файли ви замінюєте, або у вашій оболонці немає конструкції відповідного рекурсивного каталогу **, або якщо у вас занадто багато файлів, і помилка "командний рядок занадто довга", використовуйте find. Наприклад, для заміни у всіх файлах, названих *.hабо *.cу поточному каталозі та його підкаталогах (у старих системах вам може знадобитися використовувати \;замість +кінця рядка ( +форма швидша, але доступна не скрізь).

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

Попри це, я б дотримувався інтерактивного редактора, якщо вам потрібна взаємодія. Герт показав шлях до цього у Vim , хоча вимагає відкриття всіх файлів, які ви хочете шукати, що може бути проблемою, якщо їх багато.

У Emacs, ось як це зробити:

  1. Зберіть імена файлів за допомогою M-x find-name-dired(вкажіть каталог верхнього рівня) або M-x find-dired(вкажіть довільний findкомандний рядок).
  2. У отриманому розділеному буфері натисніть, tщоб позначити всі файли, а потім Q( dired-do-query-replace-regexp), щоб здійснити заміну із запитом на позначені файли.

1

sdiff(див. http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff ) тут може стати в нагоді. З його допомогою ви можете робити інтерактивне виправлення. Таким чином, sedможливе рішення з тимчасовим файлом, який ви створили, виконуючи операції заміни, використовуючи :

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

(Файловий цикл, використовуючи підстановку, яка не є POSIX, та read -dяк підтримується, наприклад,. bash)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.