Пошук з урахуванням регістру та заміну на sed


83

Я намагаюся використовувати SED для вилучення тексту з файлу журналу. Я можу виконати пошук і заміну без зайвих проблем:

sed 's/foo/bar/' mylog.txt

Однак я хочу зробити пошук нечутливим до регістру. З того, що я погуглив, схоже, додавання iдо кінця команди повинно працювати:

sed 's/foo/bar/i' mylog.txt

Однак це дає мені повідомлення про помилку:

sed: 1: "s/foo/bar/i": bad flag in substitute command: 'i'

Що тут не так, і як це виправити?


2
Чи можете ви спробувати оновити свою копію sed? I- це розширення GNU, яке може бути недоступним у вашій копії sed.
Лазер

4
EDIT : Я пройшов кваліфікацію OS X, оскільки OP прийняв відповідь, яка не працює на OS X. (Як вказується інша відповідь, sed на OS X не підтримує відповідності без урахування регістру, на відміну від документації Apple.)
danorton

1
@danorton: Дякую за це; на той випадок, якщо ви зрозуміли, що документація Apple обіцяє щось, що реалізація не дає з моєї відповіді нижче: man sedЄ відповідно до реалізації - не згадується (і не підтримується на практиці) для відповідності без урахування регістру; якщо ви знайшли документ, який стверджує інше, повідомте нас про це.
mklement0

1
@ mklement0, так, вибачте, я виправлений. Документація Apple не заявляє про відсутність відповідності регістру для sed.
Данортон

1
FWIW, версії інструментів GNU, версія BSD яких постачається з OS X, доступні у різних менеджерів пакетів. У мене є повний набір текстових утиліт, встановлених за допомогою Homebrew з gпрефіксом, тому я можу використовувати gsedабо gdateколи мені потрібна функція, не знайдена в біржовій версії.
Марк Рід

Відповіді:


73

Оновлення : Починаючи з MacOS Big Sur (11,0) , в sedданий час робить підтримку Iпрапора для регістронезавісімого узгодження , так що команда в цьому питанні повинна працювати (BSD sedне повідомляє про свою версію, але ви можете піти за датою в нижній частині на manсторінці, яка повинна бути March 27, 2017або більш пізньої); простий приклад:

# BSD sed on macOS Big Sur and above (and GNU sed, the default on Linux)
$ sed 's/ö/@/I' <<<'FÖO'
F@O   # `I` matched the uppercase Ö correctly against its lowercase counterpart

Примітка: I(верхній регістр) - це задокументована форма прапора, алеi працює.

Крім того , починаючи з MacOS Big Sur (11,0) в awkданий час є локалі відомо ( awk --versionслід повідомляти 20200816або більш пізні):

# BSD awk on macOS Big Sur and above (and GNU awk, the default on Linux)
$ awk 'tolower($0)' <<<'FÖO'
föo  # non-ASCII character Ö was properly lowercased

Наступне стосується macOS аж до Каталіни (10.15) :

Щоб бути зрозумілим: на macOS sed- який є реалізацією BSD - НЕ підтримує співставлення без урахування регістру - важко повірити, але правда. Раніше прийняв відповідь , який сам по собі показує GNU sed команду, отримав цей статус через perl-О рішенням , зазначеним в коментарях.

Щоб змусити це рішення Perl працювати і з іноземними символами , через UTF-8, використовуйте щось на зразок:

perl -C -Mutf8 -pe 's/öœ/oo/i' <<< "FÖŒ" # -> "Foo"
  • -C вмикає підтримку UTF-8 для потоків та файлів, припускаючи, що поточна локаль заснована на UTF-8.
  • -Mutf8говорить Perl інтерпретувати вихідний код як UTF-8 (у цьому випадку рядок передається -pe) - це коротший еквівалент більш багатослівного -e 'use utf8;'.Спасибі, Марк Рід

(Зверніть увагу, що використання awkтакож не є опцією , оскільки awkна macOS (тобто, BWK awk та BSD awk ), здається, зовсім не обізнані про локалі - його tolower()та toupper()функції ігнорують сторонні символи (та sub()/ gsub()не мають прапорів чутливості до регістру для початку) з).)


Примітка про взаємозв'язок sedі awkстандарт POSIX:

BSD sedта awkобмежують їх функціональність здебільшого тим, що передбачено специфікаціями POSIXsed та POSIXawk , тоді як їх аналоги GNU реалізують набагато більше розширень.



69

Примітка редактора : Це рішення не працює на macOS (нестандартно), оскільки воно стосується лише GNU sed , тоді як macOS постачається з BSD sed .

Пропишіть «Я».

sed 's/foo/bar/I' file

2
Я теж це бачив і пробував ... але все одно отримую те саме повідомлення про помилку.
Крейг Уокер

15
Здається, BSD sed має багато обмежень. Я б зробив це в PERL (тобто perl -pe 's / foo / bar / i'), якщо це так.
Wesley Rice

3
За замовчуванням установка OS X Lion видає помилку: sed: 1: "s / foo / bar / I": поганий прапор у команді, що замінює: 'I'
Бен Клейтон,

13
IСуфікс не є переносним використанням sed. POSIX sedвикористовує лише базові регулярні вирази (BRE), які напрочуд обмежені. Вони навіть не підтримують +(потрібно використовувати \{1,\}замість цього), не кажучи вже про відповідність, нечутливу до регістру. Єдиний портативний спосіб зробити це з sed - це перевірити щось подібне /[hH][eE][lL][lL][oO]/, що часто буває непрактичним.
edam

5
Це повинно бути /gIінакше, це буде діяти лише на першому матчі.
Faheem Mitha

25

Іншим робочим процесом для sedMac OS X є встановлення gsedз MacPorts або HomeBrew, а потім створення псевдоніма sed='gsed'.


gsed "s / a / b / Ig" працює, дякую! Чому хороша робоча відповідь повинна отримати проти?
Маттіас М

3
ця відповідь чудова. використовується brew install gnu-sedпотім пішов до мого ~ / .bash_profile і додав псевдонім. Спасибі @davmat
ThinkBonobo

8
Краще зробити brew install gnu-sed --with-default-names - це замінить за замовчуванням sed.
Mar0ux

5

SED FAQ адреси тісно пов'язаний регістронезавісімий пошук . Він вказує, що а) багато версій sed підтримують для нього прапор, і b) це незручно робити в sed, вам краще скористатися awk або Perl.

Але зробити це в POSIX sed , вони пропонують три варіанти (пристосовані для заміни тут):

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

  2. Можливо, можливості обмежені FOO, Fooі foo. Вони можуть бути покриті

     s/FOO/bar/;s/[Ff]oo/bar/
    
  3. Для пошуку всіх можливих збігів можна використовувати вирази в дужках для кожного символу:

     s/[Ff][Oo][Oo]/bar/
    

pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html - це те, що ви можете переносити в sed
D.Shawley,

@ D.Shawley Це нічого не суперечить у відповіді, так? Або ви хотіли додати контекст, посилаючись на офіційну специфікацію? Я можу додати це до відповіді.
Бенджамін В.

Ш Нічого суперечливого тут. Я був радий бачити, як хтось посилався на POSIX, і хотів додати посилання. Більшість відповідей тут були зайняті прикрою "нестандартною" реалізацією macOS sed, яка мене турбувала.
Д.Шоулі,

@ D.Shawley Додав посилання на специфікацію зараз :)
Бенджамін В.

3

Якщо ви спочатку робите збіг шаблонів, наприклад,

/pattern/s/xx/yy/g

тоді ви хочете поставити Iшаблон після:

/pattern/Is/xx/yy/g

Приклад:

echo Fred | sed '/fred/Is//willma/g'

повертається willma; без I, він повертає рядок недоторканим ( Fred).


2
На MacO я отримую:sed: 1: "/fred/Is//willma/g": invalid command code I
Кріс Ф Керролл

Хороша порада. Ось як я використовую його на складний пошук: sed -r '/'"$PATTERN"'/I,${s//'$YELLOW'&'$NO_COLOR'/g;b};$q3'. Він друкує текст, і якщо був знайдений шаблон (без урахування регістру), він виділяє текст жовтим кольором (колір ansi). Якщо не знайдено - повертає код виходу 3.
Ноам Манос

1

Версія для Mac sedвидається дещо обмеженою. Один із способів обійти це - використання контейнера Linux (через Docker), який має корисну версію sed:

cat your_file.txt | docker run -i busybox /bin/sed -r 's/[0-9]{4}/****/Ig'

17
це особливо огидна справа. Якщо хтось навіть серйозно розглядає це, просто встановіть GNU sed локально.
ocodo

Надмірний, але корисний загальний підхід, щоб знати!
YvesgereY

0

У мене була подібна потреба, і я придумав таке:

ця команда, щоб просто знайти всі файли:

grep -i -l -r foo ./* 

цей, щоб виключити this_shell.sh (на випадок, якщо ви введете команду в скрипт, який називається this_shell.sh ), виведіть вихід на консоль, щоб побачити, що сталося, а потім використовуйте sed для кожного знайденого імені файлу, щоб замінити текст foo на bar :

grep -i -l -r --exclude "this_shell.sh" foo ./* | tee  /dev/fd/2 | while read -r x; do sed -b -i 's/foo/bar/gi' "$x"; done 

Я вибрав цей метод, оскільки мені не сподобалось, що всі позначки часу змінюються для файлів, не змінених. подання результату grep дозволяє переглядати лише файли з цільовим текстом (таким чином, ймовірно, це також може покращити продуктивність / швидкість)

перед використанням обов’язково зробіть резервну копію файлів і тестуйте Може не працювати в деяких середовищах для файлів із вбудованими пробілами. (?)


0

Використовуйте наступне, щоб замінити всі випадки: sed 's / foo / bar / gI' mylog.txt


Див. Stackoverflow.com/a/4412964/4294399 , що охоплює столицю I. Я також не думаю, що це насправді відповідає на питання, оскільки не стосується глобальної заміни.
Calculuswhiz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.