Как сделать текстовую замену в большой иерархии папок?

Все другие решения включают изменение Вашего рабочего процесса или выполнение команды дважды, которая не могла бы подойти, если бы это занимает много времени для выполнения или не повторяемо (например, это удаляет файл - повторное выполнение, это привело бы к другому результату).

Таким образом, вот более сложная идея при необходимости в ней:

.bashrc

exec > >(tee -a ~/$$.out)

PROMPT_COMMAND='LASTLINE=$(tail -n 1 ~/$$.out)'

trap 'rm ~/$$.out' EXIT

подсказка удара

$ python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"
/usr/lib/python2.6/dist-packages
$ cd $LASTLINE
$ pwd
/usr/lib/python2.6/dist-packages

Это имеет некоторые проблемы, таким образом, это просто предназначено как начальная точка. Например, выходной файл (~/.out) мог бы стать очень большим и заполнить Ваш диск. Кроме того, Ваша целая оболочка могла прекратить работать если tee умирает.

Это могло быть изменено, чтобы только получить вывод от предыдущего использования команды preexec и precmd рычаги в zsh или эмуляция их в ударе, но это более сложно для описания здесь.

11
03.05.2011, 17:34
3 ответа

Почему бы не использовать энергию?

Откройте все файлы в энергии

vim $(find . -type f)

Или откройте только соответствующие файлы (как предложил Caleb)

vim $(grep 'from' . -Rl)

И действительно затем выполните замену во всех буферах

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

Можно также сделать это с sed, но мое sed знание ограничено.

19
27.01.2020, 19:57
  • 1
    Спасибо, Ваш ответ заставил меня сделать, последнее двойное - берет: Я понял, что полностью пропустил интерактивный бит. Я не думаю, что это даже возможно с sed (недостаточно каналов ввода-вывода). –  Gilles 'SO- stop being evil' 03.05.2011, 17:37
  • 2
    Вы могли бы ускорить это открытие ALL files в текущем буфере при помощи grep вместо find так, чтобы Вы только открыли файлы, которые знали соответствия. vim $(grep 'from' . -Rl) –  Caleb 03.05.2011, 20:15
  • 3
    Спасибо. C (astreriks вокруг c) необходим? или это - проблема форматирования? –  balki 05.05.2011, 11:27
  • 4
    @balki, это - проблема "форматирования". Зафиксированный это –  Gert 05.05.2011, 14:04

Можно сделать что-то сырое с маленьким сценарием 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 '…' **/*(.)

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

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

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

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

Однако я придерживался бы интерактивного редактора, если Вам нужно взаимодействие. Gert показал путь к этому в Vim, хотя он требует открытия всех файлов, которые Вы хотите перерыть, который может быть проблемой, если существует много.

В Emacs вот то, как можно сделать это:

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

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

1
27.01.2020, 19:57

Теги

Похожие вопросы