Как я могу рекурсивно заменить строку в именах файлов и каталогов?

diff fil1 file2  

diff выводит изменения, необходимые для того, чтобы сделать file2 идентичным file1.

Изменения, необходимые для того, чтобы сделать file1 таким же, как file2, определенно отличаются от изменений, необходимых для обратного. Давайте посмотрим на простой пример: файл1 содержит только одно слово один , а файл2 содержит только одно слово два , с diff файл1 файл2 вы получите:

1c1
< one
---
> two  

и с diff file2 file1 вы получите:

1c1
< two
---
> one

Значит, diff не симметричен, и не должно быть.

Теперь относительно параметра -B в руководстве говорится:

-B, --ignore-blank-lines        ignore changes where lines are all blank 

-B не означает игнорировать пустые строки из ввода, это означает игнорировать изменения , которые являются пустой. если вы сделаете diff two.txt one.txt , вы получите:

3d2
<
5d3
<
6a5,12
>
>
>
>
>
>
>
>

все три изменения - пустые строки, поэтому он ничего не печатает.

1
11.05.2016, 00:31
3 ответа

Вы можете использовать команду переименования (см. редактировать 1 ).

Решение 1

Для разумного количества файлов / каталогов, установив параметр bash 4 globstar (не работает с рекурсивным именем, см. редактировать 3 ):

shopt -s globstar
rename -n 's/etckeeper/userkeeper/g' **

Решение 2

Для большого количества файлов / каталогов с использованием переименования и поиска в два этапа, чтобы предотвратить неудачное переименование файлов в только что переименованных каталогах (см. редактировать 2 ):

find . -type f -exec rename 's/etckeeper/userkeeper/g' {} \;
find . -type d -exec rename 's/etckeeper/userkeeper/g' {} \;

РЕДАКТИРОВАТЬ 1:

Есть две разные команды переименования . В этом ответе используется команда переименования на основе Perl, доступная в дистрибутивах на основе Debian. Чтобы установить его в дистрибутиве, отличном от Debian, вы можете установить его из cpan или взять его с собой.

РЕДАКТИРОВАТЬ 2:

Как указал Джефф Шаллер в комментариях, -depth опция поиска Обработка содержимого каждого каталога перед самим каталогом , поэтому только «упорядоченный» поиск по -depth будет достаточно:

find . -depth -exec rename 's/etckeeper/userkeeper/g' {} \;

РЕДАКТИРОВАТЬ 3

Решение 1 не работает для целей рекурсивного переименования (например. etckeeper / etckeeper / etckeeper ), поскольку внешние уровни обрабатываются до внутренних уровней, и указатель на внутренние уровни становится бесполезным. (После первого переименования etckeeper / etckeeper / etckeeper будет называться usrkeeper / etckeeper / etckeeper , поэтому переименование для etckeeper / etckeeper / и etckeeper / etckeeper / etckeeper завершится ошибкой). Та же проблема исправлена ​​в find с помощью параметров -depth .

EDIT4

Как указано в комментариях cas, я бы использовал {} + вместо {} \; - разветвление Perl-скрипта, например, многократное переименование (один раз на файл / каталог), дорого обходится.

5
27.01.2020, 23:16

На ваш первоначальный вопрос на самом деле довольно легко ответить: xargs (по крайней мере, в OS X) имеет опцию -I, которая также не требует, чтобы строка замены была уникальным аргументом. Таким образом, вызов становится простым:

% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'

Легко и просто. Теперь давайте переименуем в правильном порядке. Как выяснилось, find сначала выведет каталоги (потому что он должен обработать их, прежде чем спуститься в них), поэтому все, что нам нужно сделать, это изменить порядок имен файлов:

% find . -path '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'

Обратите внимание, что я поменял find -print0 | xargs -0 на find -print | xargs. Почему? Потому что tail -r делает обратный ход на основе новых строк, а не нулевых символов. Если бы вы не переключили его, tail -r вывел бы то же самое, что вы ввели в него! Так что теперь скрипт более корректен, но также он будет ломаться на именах файлов, содержащих, например, новые строки.

Обратите внимание, что если у вас нет tail -r, вам следует попробовать tac. См: Как изменить порядок строк в файле? на Stack Overflow.

Итак, проблема в том, что команда sed слишком агрессивна. Например, с деревом типа:

.
├── etckeeper-foo
│   └── etckeeper-bar.md
└── some-other-directory
    └── etckeeper-baz.md

Вы получите mv вызовы, которые выглядят так:

mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./usrkeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo

Первый вариант в порядке, но второй явно не подходит - поскольку мы еще не сделали mv ./etckeeper-foo ./usrkeeper-foo!

Давайте заменим только последний компонент имени пути. Мы можем сделать это, используя basename и dirname:

% find . -name '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(dirname %)/$(basename % | sed "s/etckeeper/usrkeeper/g" )'

Обратите внимание, что другое изменение, которое я сделал, это использование find -name, а не find -path.

Это приводит к правильным mv вызовам:

mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./etckeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo

Наконец. Помните, еще раз, что это не сработает на эзотерических именах файлов. Заметьте также, что если ваши имена файлов особенно длинные, xargs не будет работать правильно, потому что аргументы для mv станут слишком длинными. Вы можете (по крайней мере, в OS X) передать -x в xargs, чтобы он немедленно отказал в этом случае.

1
27.01.2020, 23:16

Другое решение - использовать небольшой скрипт и выполнить цикл for для результатов поиска и mv с заменой строки bash в найденных файлах:

IFS=$'\n'
for files in $(find . -name "*etckeeper*"); 
do 
  mv "$files ${files/etckeeper/usrkeeper}"
done

Если вы не используете ее в сценарии, лучше сохраните исходный IFS и восстановите его в конце цикла.

0
27.01.2020, 23:16

Теги

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