Как заменить пробелы% 20 в подстроке строки в нескольких файлах, используя sed, awk, grep и т. Д. - PullRequest
1 голос
/ 19 апреля 2020

В недавнем обновлении neomutt изменил способ обработки соответствия регулярному выражению, и он сломал мой UMMUCH URI в моей конфигурации. Похоже, решение заключается в замене пробелов в URI на %20. Это не будет огромной проблемой, за исключением того, что у меня много виртуальных почтовых ящиков, определенных в нескольких конфигурационных файлах. Итак, вот пример одной конфигурации:

"Inbox"                 "notmuch://?query=folder:gmail/INBOX and tag:inbox" \
"Drafts"                "notmuch://?query=folder:gmail/Drafts" \
"Sent Mail"             "notmuch://?query=folder:gmail/Sent%20Mail" \
"Trash"                 "notmuch://?query=folder:gmail/Trash" \
"Today"                 "notmuch://?query=to:rsstinnett@gmail.com and date:today" \
"Yesterday"             "notmuch://?query=to:rsstinnett@gmail.com and date:yesterday" \
"This Week"             "notmuch://?query=to:rsstinnett@gmail.com and date:this_week" \
"Todo"                  "notmuch://?query=to:rsstinnett@gmail.com and tag:todo" \
"Starred"               "notmuch://?query=to:rsstinnett@gmail.com and tag:star" \
"Burning Man"           'notmuch://?query=folder:"gmail/Burning Man"' \
"  Work List"           'notmuch://?query=folder:"gmail/Burning Man/Work List"' \
"ATXHS"                 'notmuch://?query=folder:"gmail/ATX Hackerspace" and not tag:archive' \
"  ATXHS Members"       'notmuch://?query=folder:"gmail/ATX Hackerspace/Members" and not tag:archive' \
"  ATXHS Discuss"       'notmuch://?query=folder:"gmail/ATX Hackerspace/Discuss" and not tag:archive' \
"  ATXHS Announce"      'notmuch://?query=folder:"gmail/ATX Hackerspace/Announce" and not tag:archive'

Использование sed, awk, grep или что-то еще, как я могу изменить "gmail/ATX Hackerspace" на "gmail/ATX%20Hackerspace" без воздействия " and not tag:archive" ?

Я знаю, что нужно внести другие изменения, но это единственное, на чем я застрял. По сути, мне нужно изменить пробелы между folder:" и следующим экземпляром двойной кавычки. Я не знаю, можно ли это сделать разумно.

Ответы [ 4 ]

4 голосов
/ 19 апреля 2020

Исходя из Мне нужно изменить пробел s между folder:" и следующим экземпляром двойной кавычки , следующее решение кажется очень простым и достаточно читабельным:

sed -E ':a;s/(folder:"[^ "]*) /\1%20/;ta' yourinput

В основном это время l oop, где

  • тело s/(folder:"[^ "]*) /\1%20/ пытается выбрать первое, если оно есть, пробел, следующий за folder:" перед закрывая ",
  • условие для повтора l oop, это то, что попытка была успешной (то есть замена действительно была сделана); ta действительно t ests, если какая-либо команда s была успешной в текущей строке, и, если это так, она передает управление в строку, помеченную :a.

Обновление

Что касается опции -E, я протестировал ответ выше only на GNU sed. Эд Мортон проверил его на OSX / BSD, и команда, которую я предоставил, выдает неизменный вывод.

Я думал, что причиной может быть -E или, возможно, пропущенный ; после ta, но это похоже, дело не в этом, основываясь на попытках Эда Мортона.

Сначала я думал, что команда совместима с POSIX, основываясь на следующем отрывке из справочной страницы GNU sed:

-E, -r, --regexp-extended

              use extended regular expressions in the script (for portability
              use POSIX -E).

Далее на этой странице GNU , я прочитал

Исторически это было расширение GNU, но с тех пор расширение -E было добавлено в стандарт POSIX ( http://austingroupbugs.net/view.php?id=528), поэтому для переносимости используйте -E.

До этого момента, однако, это то, что GNU говорит о POSIX .

Если вы go перейдете по этой ссылке, последняя строка в разделе История ошибок датируется 2020-03-18 15: 37 и читает Resolved => Применимо , но я не знаю, как эти сайты относятся к POSIX.

Суть: Я не знаю теперь, если -E соответствует POSIX.

4 голосов
/ 19 апреля 2020

Использование любого awk в любой оболочке на каждом UNIX поле:

$ awk 'match($0,/folder:"[^"]+"/) {
    tgt = substr($0,RSTART,RLENGTH)
    gsub(/ /,"%20",tgt)
    $0 = substr($0,1,RSTART-1) tgt substr($0,RSTART+RLENGTH)
 } 1' file
"Inbox"                 "notmuch://?query=folder:gmail/INBOX and tag:inbox" \
"Drafts"                "notmuch://?query=folder:gmail/Drafts" \
"Sent Mail"             "notmuch://?query=folder:gmail/Sent%20Mail" \
"Trash"                 "notmuch://?query=folder:gmail/Trash" \
"Today"                 "notmuch://?query=to:rsstinnett@gmail.com and date:today" \
"Yesterday"             "notmuch://?query=to:rsstinnett@gmail.com and date:yesterday" \
"This Week"             "notmuch://?query=to:rsstinnett@gmail.com and date:this_week" \
"Todo"                  "notmuch://?query=to:rsstinnett@gmail.com and tag:todo" \
"Starred"               "notmuch://?query=to:rsstinnett@gmail.com and tag:star" \
"Burning Man"           'notmuch://?query=folder:"gmail/Burning%20Man"' \
"  Work List"           'notmuch://?query=folder:"gmail/Burning%20Man/Work%20List"' \
"ATXHS"                 'notmuch://?query=folder:"gmail/ATX%20Hackerspace" and not tag:archive' \
"  ATXHS Members"       'notmuch://?query=folder:"gmail/ATX%20Hackerspace/Members" and not tag:archive' \
"  ATXHS Discuss"       'notmuch://?query=folder:"gmail/ATX%20Hackerspace/Discuss" and not tag:archive' \
"  ATXHS Announce"      'notmuch://?query=folder:"gmail/ATX%20Hackerspace/Announce" and not tag:archive'
0 голосов
/ 22 апреля 2020

sed -E ':a;s/(folder:"[^ "]*) /\1%20/;ta' yourinput

Это действительно тот ответ, который я искал, и сделал бы эту работу отлично. Отсюда мне нужно будет выполнить еще 3 или 4 оператора sed для «исправления» файлов конфигурации.

Спасибо всем за ваши ответы. Это отличное упражнение для меня, чтобы интерпретировать ваши решения и улучшить мое понимание этих инструментов.

К сожалению, я закончил тем, что вернул neomutt к предыдущей версии и отправил сообщение об ошибке на github.

0 голосов
/ 20 апреля 2020

Просто для удовольствия, вот еще одно решение, использующее только sed. (Нет веских оснований для использования sed в производстве, когда доступны лучшие инструменты; это все же хорошее упражнение.)

Сравните с простым и кратким решением, опубликованным Энрико Де Анжелисом. Есть два различия между его подходом и тем, что я предлагаю ниже.

Во-первых, подход в ответе Энрико не сработает, если текст «замены» включает пробелы (если, например, каждый пробел должен быть заменен на % 20 с пробелом после знака процента). Конечно, в проблеме ОП это не так; но в более общей задаче циклический подход в решении Энрико может привести к бесконечным циклам.

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

Идея относительно проста, но решение осложняется тем, что sed имеет только два буфера, с которыми мы можем работать. Переключаясь между ними, мы можем «сохранить» часть строки, к которой нам не нужно прикасаться, и внести изменения в оставшуюся строку. Поскольку у нас есть только два буфера и три соответствующие подстроки, мы вынуждены внести «слишком много изменений» в первой половине решения, а затем отменить ненужные изменения во второй половине. Это решение также имеет явный недостаток: если в последней части строки уже было %20 (после закрывающей двойной кавычки, относящейся к folder), они будут заменены на пробел, даже если они не были пробелами в оригинал.

Интересно, есть ли лучшие подходы в этом направлении (имеется в виду, в частности, не связанные с циклическим процессом).

$ sed -E '/folder:"/{h;s/(^.*?folder:").*/\1/;x;s/^.*?folder:"//;s/ /%20/g;x;G;
> /folder:"/s/\n//;h;s/(^.*?folder:"[^"]*").*/\1/;x;s/.*?folder:"[^"]*"//;
> s/%20/ /g;x;G;/folder:"/s/\n//}' inputfile

Как обычно, ведущие $ и > - это приглашения оболочки (не являются частью команды sed).

EDIT Как отмечает Эд Мортон в комментарии ниже, ленивые квантификаторы являются функцией perl, не поддерживаются в sed. Это не было важной частью моего решения; Вот POSIX ERE - совместимая версия:

$ sed -E '/folder:"/{h;s/(^.*folder:").*/\1/;x;s/^.*folder:"//;s/ /%20/g;x;G;
> /folder:"/s/\n//;h;s/(^.*folder:"[^"]*").*/\1/;x;s/.*folder:"[^"]*"//;
> s/%20/ /g;x;G;/folder:"/s/\n//}' inputfile
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...