Як видалити рядок, якщо вона містить символ рівно один раз


10

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

Наприклад:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Тут символ, який я хочу видалити, є Cтаким, команда повинна видалити рядки FGTHDCі JUTDYCтому, що вони є Cрівно один раз.

Як я можу це зробити з допомогою яких sedабо awk?

Відповіді:


20

У awkви можете встановити роздільник поля ні до чого. Якщо встановити його C, ви матимете стільки полів +1, скільки подій C.

Тож якщо ви скажете, awk -F'C' '{print NF}' <<< "C1C2C3"що отримуєте 4: CCCскладається з 3 Cс, а значить, 4 полів.

Ви хочете видалити рядки, в яких Cтрапляється рівно один раз. Враховуючи це, у вашому випадку ви захочете видалити ті рядки, у яких є точно два Cполя. Тому просто пропустіть їх:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD

4
Напружене використання awkроздільника поля!
Валентин Б.

Інтерестуючи, як у випадку за замовчуванням (FS = "") він ігнорує провідні пробіли ($ 1 = перший непробіл у рядку), а також повторення (у вас може бути 5 пробілів, щоб розділити поле 1 і поле 2) ... пробіл мабуть, лікується спеціально? (Щоб побачити його, можна зробити awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'і годувати його кілька рядків, деякі , що мають кілька spces і інших begininng з простором (s))
Олів'є Дюлак

2
@OlivierDulac, так, простір обробляється спеціально, як зазначено в POSIX .
Wildcard

8

sed підхід:

sed -i '/^[^C]*C[^C]*$/d' input

-i опція дозволяє змінити файл на місці

/^[^C]*C[^C]*$/- відповідає рядкам, які містять Cлише один раз

d - видалити відповідні рядки


8

Це можна зробити за допомогою sed:

Код:

sed '/C.*C/p;/C/d' file1

Результати:

DTHGTY
HYTRHD
HTCCYD

Як?

  1. Збігайте та друкуйте будь-який рядок, принаймні, дві копії Cчерез/C.*C/p
  2. Видаліть будь-який рядок за Cдопомогою /C/dвікна, це включає рядки, вже надруковані на кроці 1
  3. Друк решти рядків за замовчуванням

2
Розумний альтернативний підхід; Мені це подобається.
Wildcard

6

При цьому видаляються лінії з точно одним випадком виникнення С.

grep -v '^[^C]*C[^C]*$' file

Регулярний вираз [^C]відповідає одному символу, який не є C (або новим рядком), і оператору повторення (він же зірка Kleene)* задає нуль або більше повторень попереднього виразу.

Вихід за замовчуванням з grep(та більшості інших інструментів, орієнтованих на текст) - стандартний вихід; перенаправити на новий файл і, можливо, перемістити його вгорі на вихідний файл, якщо це те, що ви хочете. Цей же регулярний вираз можна використовувати і sed -iдля редагування на місці:

sed -i '/^[^C]*C[^C]*$/d' file

(На деяких платформах, зокрема * BSD, включаючи macOS, для -iпараметра потрібен аргумент, наприклад -i ''.)


1
sed -i '/^[^C]*C[^C]*$/d' file- звучить так, як це було розміщено раніше, як ви думаєте, плагіат?
RomanPerekhrest

1
Дійсно, є певне дублювання. Я почав з grepвідповіді, але це, очевидно, легко поширюється на sed -iваріант. Я не бачив вашої відповіді, тому що я шукав попередні grepвідповіді.
трійчатка

1
Це безпечніше , просто явно уникнути -iз sedі замість того, щоб перенаправити в новий файл і замінити оригінал з тим , якщо sedутиліта вийшла без помилок.
Кусалаланда

2
Абоgrep -vx '[^C]*C[^C]*'
Стефан Шазелас

@Kusalananda Але ви можете також використовувати, grepоскільки вона чіткіша і надійніша (зокрема, sedмає менш інформативний код виходу).
трійчатка

4

Інструмент POSIX для сценаріїв редагування файлу (а не друку модифікованого вмісту для стандартного виходу) є ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Звичайно, ви можете використовувати,sed -i якщо ваша версія Sed підтримує її, просто пам’ятайте, що це не портативно, якщо ви пишете сценарій, який призначений для роботи в різних типах систем.


Девід Фоерстер запитав у коментарях:

Чи є причина, чому ви використовуєте, printfа ні, echoчи щось подібне ex -c COMMAND?

Відповідь: Так.

Для printfvs. echoце питання переносимості; див. Чому printf кращий за відлуння? А також легше перемежувати нові рядки між командами, використовуючи printf.

Для printf ... | exvs. ex -c ...- це питання поводження з помилками. Для цієї конкретної команди це не мало б значення, але в цілому це робить; наприклад, спробуйте поставити

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

у сценарії. Контраст із наступним:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

Перший буде висіти і чекати введення; другий вийде, коли exкоманда отримає EOF , тому сценарій продовжиться. Існують альтернативні способи вирішення, наприклад s///e, але вони не визначені POSIX. Я вважаю за краще використовувати портативну форму, яка показана вище.

Для gкоманди в кінці повинен бути новий рядок, і я вважаю за краще використовувати printfкоманду, щоб обернути команди, а не вставляти новий рядок в єдині лапки.


1
Чи є причина, чому ви використовуєте, printfа ні, echoчи щось подібне ex -c COMMAND?
Девід Фоерстер

@DavidFoerster, так. Я почав відповідати вам у коментарях, але він довго зростав, тому додав його до відповіді.
Wildcard

Дякую та +1! Я знав про printfvs. echo(хоча, як правило, я просто віддаю перевагу, echoколи аргумент жорстко закодований), але до цього часу я не використовував exшироко.
Девід Фоерстер

2

Ось пара варіантів за допомогою perl.

Оскільки ви співпадаєте лише з одним символом, ви можете використовувати tr/C//(переклад, без заміни), щоб повернути кількість збігів C:

perl -lne 'print if tr/C// != 1' file

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

perl -lne 'print if (@m = /C/g) != 1' file

Це призначає збіги регулярного виразу /C/gдо списку @mта друкує рядки, коли довжина цього списку не дорівнює 1.

-iПеремикач може бути додана можливість редагування «на місці».


2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

Зауважте, що він передбачає GNU sed, t #...як правило, розгалужується на мітку, яку називають #...у більшості інших sedреалізацій.
Стефан Шазелас

Навіть !bє GNU sed, оскільки філія не любить нічого, крім етикетки або нового рядка після неї.

Так, b, t, :, }r file, w file...) не може мати команду після них на одній і тій же лінії. Також можна використовувати окремі -eпараметри.
Стефан Шазелас

Ваша опція perl не дає правильного результату. Я думаю, ви забули додати gмодифікатор.
Том Фенех

@TomFenech Ви праві. Я це фіксую. Дякую.

1

Для тих, хто хоче awkконкретно, я пропоную

awk '/C[^C]*C/{next}//{print}'

пропустіть рядок, якщо вона відповідає шаблону, роздрукуйте її інакше. Ви насправді не потребуєте {print}, ви можете використовувати //та друкувати за замовчуванням, але я думаю, що це чіткіше прописано.

Моя перша думка полягала в тому, щоб використати egrep -vту саму схему, але це насправді не відповідає на поставлене питання.


1
Який сенс узгоджувати що-небудь після {next}? Просто скажіть, awk '/pattern/ {next} 1'і всі рядки, що не відповідають шаблону, будуть надруковані. Або, краще, awk '!/pattern/'безпосередньо надрукувати їх.
fedorqui

@fedorqui хороший пункт про !/pattern/(який якимось чином підсунув мені розум), але я б швидше побачив самопояснення, //{print}ніж криптовалюта 1. Припускайте найменшу компетенцію та вільне володіння від наступної особи для підтримки вашого коду, що не робить його серйозним менш ефективним чи ефективним.
nigel222
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.