Как найти/заменить и увеличить количество совпадений с помощью sed/awk?
Прямо к делу, мне интересно, как использовать grep/find/sed/awk для соответствия определенной строке (которая заканчивается номером) и увеличивать это число на 1. Ближайший я пришел, чтобы объединить a 1 до конца (который работает достаточно хорошо), потому что основной задачей является простое изменение значения. Вот что я сейчас делаю:
find . -type f | xargs sed -i 's/\(\?cache_version\=[0-9]\+\)/\11/g'
Так как я не мог понять, как увеличить число, я захватил все это и просто добавил "1". Раньше у меня было что-то вроде этого:
find . -type f | xargs sed -i 's/\?cache_version\=\([0-9]\+\)/?cache_version=\11/g'
Итак, по крайней мере, я понимаю, как захватить то, что мне нужно.
Вместо того, чтобы объяснять, для чего это, я просто объясню, что я хочу. Он должен найти текст в любом файле, рекурсивный, на основе текущего каталога (неважно, это может быть любой каталог, поэтому я бы его позже установил), который соответствует "? Cache_version =" с номером. Затем он увеличит это число и заменит его в файле.
В настоящее время материал, который у меня выше, работает, просто я не могу увеличивать число найденное в конце. Лучше было бы увеличить инкремент вместо добавления "1", чтобы будущие значения не были "11", "111", "1111", "11111" и т.д.
Я просмотрел десятки статей/объяснений, и достаточно часто предложение состоит в том, чтобы использовать awk
, но я не могу для меня смешать их. Ближе всего я пришел к использованию awk
, который на самом деле ничего не заменяет:
grep -Pro '(?<=\?cache_version=)[0-9]+' . | awk -F: '{ print "match is", $2+1 }'
Мне интересно, если есть какой-нибудь способ передать sed
в конце и передать исходное имя файла, чтобы sed
мог иметь имя файла и увеличивающийся номер (из awk
) или что угодно что xargs
имеет.
Технически это число не имеет значения; эта замена в основном заключается в том, чтобы убедиться, что там есть новый номер, 100% наверняка отличается от последнего. Так как я писал этот вопрос, я понял, что могу использовать системное время - секунды с эпохи (техника, часто используемая AJAX для устранения кэширования для последующих "идентичных" запросов). Я закончил с этим, и это кажется идеальным:
CXREPLACETIME=`date +%s`; find . -type f | xargs sed -i "s/\(\?cache_version\=\)[0-9]\+/\1$CXREPLACETIME/g"
(Я сначала храню значение, поэтому все файлы получают одинаковое значение, если оно охватывает несколько секунд по любой причине)
Но я все равно хотел бы узнать исходный вопрос, увеличивая количество совпадающих чисел. Я предполагаю, что простым решением было бы сделать его bash script, но, тем не менее, я думал, что будет проще, чем рекурсивно перебирать каждый файл и проверять его содержимое для соответствия, а затем заменять, поскольку он просто увеличивая совпадающее число... не намного больше логики. Я просто не хочу писать ни в какие другие файлы или что-то в этом роде - он должен делать это на месте, например sed
с опцией "i".
Ответы
Ответ 1
Я считаю, что поиск файла - не сложная часть для вас. Поэтому я просто перехожу к делу, чтобы выполнить расчет +1. Если у вас gnu sed, это можно сделать следующим образом:
sed -r 's/(.*)(\?cache_version=)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' file
возьмем пример:
kent$ cat test
ello
barbaz?cache_version=3fooooo
bye
kent$ sed -r 's/(.*)(\?cache_version=)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' test
ello
barbaz?cache_version=4fooooo
bye
вы можете добавить опцию -i, если хотите.
изменить
/e
позволяет передавать согласованную часть во внешнюю команду и подставлять результат выполнения. Только Gnu sed.
см. этот пример: используются внешние команды/инструменты echo
, bc
kent$ echo "result:3*3"|sed -r 's/(result:)(.*)/echo \1$(echo "\2"\|bc)/ge'
дает результат:
result:9
вы можете использовать другую мощную внешнюю команду, например cut, sed (снова), awk...
Ответ 2
Эта команда perl
будет искать все файлы в текущем каталоге (без прохождения через нее, вам понадобится модуль File::Find
или аналогичный для этой более сложной задачи) и увеличит число строк, которое соответствует cache_version=
. Он использует флаг /e
регулярного выражения, которое оценивает замещающую часть.
perl -i.bak -lpe 'BEGIN { sub inc { my ($num) = @_; ++$num } } s/(cache_version=)(\d+)/$1 . (inc($2))/eg' *
Я тестировал его с file
в текущем каталоге со следующими данными:
hello
cache_version=3
bye
Он создает резервные копии исходного файла (ls -1
):
file
file.bak
И file
теперь с помощью:
hello
cache_version=4
bye
Я надеюсь, что это может быть полезно для того, что вы ищете.
UPDATE, чтобы использовать File::Find
для перемещения каталогов. Он принимает *
как аргумент, но отбрасывает их с найденными с помощью File::Find
. Каталог для начала поиска - это текущий запуск script. Он жестко закодирован в строке find( \&wanted, "." )
.
perl -MFile::Find -i.bak -lpe '
BEGIN {
sub inc {
my ($num) = @_;
++$num
}
sub wanted {
if ( -f && ! -l ) {
push @ARGV, $File::Find::name;
}
}
@ARGV = ();
find( \&wanted, "." );
}
s/(cache_version=)(\d+)/$1 . (inc($2))/eg
' *
Ответ 3
Версия Pure sed:
Эта версия не имеет зависимости от других команд или переменных среды.
Он использует явный перенос. Для переноса я использую символ @, но другое имя можно использовать, если хотите. Используйте то, чего нет в вашем исходном файле.
Сначала он находит SEARCHSTRING<number>
и добавляет @к нему.
Он повторяет увеличивающиеся цифры, у которых есть ожидающий перенос (т.е. После него есть символ переноса: [0-9]@
)
Если 9 был увеличен, этот приращение дает само перенос, и процесс будет повторяться до тех пор, пока не будет больше ожидающих переносов.
Наконец, переносы, которые были получены, но не добавлены к цифре, но заменены на 1.
sed "s/SEARCHSTRING[0-9]*[0-9]/&@/g;:a {s/[email protected]/1/g;s/[email protected]/2/g;s/[email protected]/3/g;s/[email protected]/4/g;s/[email protected]/5/g;s/[email protected]/6/g;s/[email protected]/7/g;s/[email protected]/8/g;s/[email protected]/9/g;s/[email protected]/@0/g;t a};s/@/1/g" numbers.txt
Ответ 4
Это некрасиво (я немного ржавый), но здесь начинаем использовать sed:
orig="something1" ;
text=`echo $orig | sed "s/\([^0-9]*\)\([0-9]*\)/\1/"` ;
num=`echo $orig | sed "s/\([^0-9]*\)\([0-9]*\)/\2/"` ;
echo $text$(($num + 1))
С исходным именем файла ($orig
) "something1" sed отделяет текст и числовые части от $text
и $num
, затем они объединяются в последнем разделе с добавленным числом, что приводит к something2
.
Просто начало, так как оно не рассматривает случаи с номерами в имени файла или именах без номера в конце, но, надеюсь, помогает с вашей исходной целью использования sed.
Это действительно можно упростить в sed, используя буферы, я полагаю (sed может работать рекурсивно), но я действительно ржавый с этим аспектом.